01dd29fe49da0432fde0e8e7ba402a8a5ccbce07
[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, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 /* [AS] */
313 static char * safeStrCpy( char * dst, const char * src, size_t count )
314 {
315     assert( dst != NULL );
316     assert( src != NULL );
317     assert( count > 0 );
318
319     strncpy( dst, src, count );
320     dst[ count-1 ] = '\0';
321     return dst;
322 }
323
324 /* Some compiler can't cast u64 to double
325  * This function do the job for us:
326
327  * We use the highest bit for cast, this only
328  * works if the highest bit is not
329  * in use (This should not happen)
330  *
331  * We used this for all compiler
332  */
333 double
334 u64ToDouble(u64 value)
335 {
336   double r;
337   u64 tmp = value & u64Const(0x7fffffffffffffff);
338   r = (double)(s64)tmp;
339   if (value & u64Const(0x8000000000000000))
340        r +=  9.2233720368547758080e18; /* 2^63 */
341  return r;
342 }
343
344 /* Fake up flags for now, as we aren't keeping track of castling
345    availability yet. [HGM] Change of logic: the flag now only
346    indicates the type of castlings allowed by the rule of the game.
347    The actual rights themselves are maintained in the array
348    castlingRights, as part of the game history, and are not probed
349    by this function.
350  */
351 int
352 PosFlags(index)
353 {
354   int flags = F_ALL_CASTLE_OK;
355   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
356   switch (gameInfo.variant) {
357   case VariantSuicide:
358     flags &= ~F_ALL_CASTLE_OK;
359   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
360     flags |= F_IGNORE_CHECK;
361   case VariantLosers:
362     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363     break;
364   case VariantAtomic:
365     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
366     break;
367   case VariantKriegspiel:
368     flags |= F_KRIEGSPIEL_CAPTURE;
369     break;
370   case VariantCapaRandom: 
371   case VariantFischeRandom:
372     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
373   case VariantNoCastle:
374   case VariantShatranj:
375   case VariantCourier:
376   case VariantMakruk:
377     flags &= ~F_ALL_CASTLE_OK;
378     break;
379   default:
380     break;
381   }
382   return flags;
383 }
384
385 FILE *gameFileFP, *debugFP;
386
387 /* 
388     [AS] Note: sometimes, the sscanf() function is used to parse the input
389     into a fixed-size buffer. Because of this, we must be prepared to
390     receive strings as long as the size of the input buffer, which is currently
391     set to 4K for Windows and 8K for the rest.
392     So, we must either allocate sufficiently large buffers here, or
393     reduce the size of the input buffer in the input reading part.
394 */
395
396 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
397 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
398 char thinkOutput1[MSG_SIZ*10];
399
400 ChessProgramState first, second;
401
402 /* premove variables */
403 int premoveToX = 0;
404 int premoveToY = 0;
405 int premoveFromX = 0;
406 int premoveFromY = 0;
407 int premovePromoChar = 0;
408 int gotPremove = 0;
409 Boolean alarmSounded;
410 /* end premove variables */
411
412 char *ics_prefix = "$";
413 int ics_type = ICS_GENERIC;
414
415 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
416 int pauseExamForwardMostMove = 0;
417 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
418 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
419 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
420 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
421 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
422 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
423 int whiteFlag = FALSE, blackFlag = FALSE;
424 int userOfferedDraw = FALSE;
425 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
426 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
427 int cmailMoveType[CMAIL_MAX_GAMES];
428 long ics_clock_paused = 0;
429 ProcRef icsPR = NoProc, cmailPR = NoProc;
430 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
431 GameMode gameMode = BeginningOfGame;
432 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
433 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
434 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
435 int hiddenThinkOutputState = 0; /* [AS] */
436 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
437 int adjudicateLossPlies = 6;
438 char white_holding[64], black_holding[64];
439 TimeMark lastNodeCountTime;
440 long lastNodeCount=0;
441 int have_sent_ICS_logon = 0;
442 int movesPerSession;
443 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
444 long timeControl_2; /* [AS] Allow separate time controls */
445 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
446 long timeRemaining[2][MAX_MOVES];
447 int matchGame = 0;
448 TimeMark programStartTime;
449 char ics_handle[MSG_SIZ];
450 int have_set_title = 0;
451
452 /* animateTraining preserves the state of appData.animate
453  * when Training mode is activated. This allows the
454  * response to be animated when appData.animate == TRUE and
455  * appData.animateDragging == TRUE.
456  */
457 Boolean animateTraining;
458
459 GameInfo gameInfo;
460
461 AppData appData;
462
463 Board boards[MAX_MOVES];
464 /* [HGM] Following 7 needed for accurate legality tests: */
465 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
466 signed char  initialRights[BOARD_FILES];
467 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
468 int   initialRulePlies, FENrulePlies;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0; 
471 int shuffleOpenings;
472 int mute; // mute all sounds
473
474 // [HGM] vari: next 12 to save and restore variations
475 #define MAX_VARIATIONS 10
476 int framePtr = MAX_MOVES-1; // points to free stack entry
477 int storedGames = 0;
478 int savedFirst[MAX_VARIATIONS];
479 int savedLast[MAX_VARIATIONS];
480 int savedFramePtr[MAX_VARIATIONS];
481 char *savedDetails[MAX_VARIATIONS];
482 ChessMove savedResult[MAX_VARIATIONS];
483
484 void PushTail P((int firstMove, int lastMove));
485 Boolean PopTail P((Boolean annotate));
486 void CleanupTail P((void));
487
488 ChessSquare  FIDEArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare twoKingsArray[2][BOARD_FILES] = {
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
499         BlackKing, BlackKing, BlackKnight, BlackRook }
500 };
501
502 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
503     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
504         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
505     { BlackRook, BlackMan, BlackBishop, BlackQueen,
506         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
507 };
508
509 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
510     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
513         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
514 };
515
516 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
517     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
518         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
520         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 };
522
523 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
524     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
525         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
526     { BlackRook, BlackKnight, BlackMan, BlackFerz,
527         BlackKing, BlackMan, BlackKnight, BlackRook }
528 };
529
530
531 #if (BOARD_FILES>=10)
532 ChessSquare ShogiArray[2][BOARD_FILES] = {
533     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
534         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
535     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
536         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
537 };
538
539 ChessSquare XiangqiArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
541         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
543         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
544 };
545
546 ChessSquare CapablancaArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
548         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
550         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
551 };
552
553 ChessSquare GreatArray[2][BOARD_FILES] = {
554     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
555         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
556     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
557         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
558 };
559
560 ChessSquare JanusArray[2][BOARD_FILES] = {
561     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
562         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
563     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
564         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
565 };
566
567 #ifdef GOTHIC
568 ChessSquare GothicArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
570         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
572         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
573 };
574 #else // !GOTHIC
575 #define GothicArray CapablancaArray
576 #endif // !GOTHIC
577
578 #ifdef FALCON
579 ChessSquare FalconArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
581         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
583         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
584 };
585 #else // !FALCON
586 #define FalconArray CapablancaArray
587 #endif // !FALCON
588
589 #else // !(BOARD_FILES>=10)
590 #define XiangqiPosition FIDEArray
591 #define CapablancaArray FIDEArray
592 #define GothicArray FIDEArray
593 #define GreatArray FIDEArray
594 #endif // !(BOARD_FILES>=10)
595
596 #if (BOARD_FILES>=12)
597 ChessSquare CourierArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
599         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
600     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
601         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
602 };
603 #else // !(BOARD_FILES>=12)
604 #define CourierArray CapablancaArray
605 #endif // !(BOARD_FILES>=12)
606
607
608 Board initialPosition;
609
610
611 /* Convert str to a rating. Checks for special cases of "----",
612
613    "++++", etc. Also strips ()'s */
614 int
615 string_to_rating(str)
616   char *str;
617 {
618   while(*str && !isdigit(*str)) ++str;
619   if (!*str)
620     return 0;   /* One of the special "no rating" cases */
621   else
622     return atoi(str);
623 }
624
625 void
626 ClearProgramStats()
627 {
628     /* Init programStats */
629     programStats.movelist[0] = 0;
630     programStats.depth = 0;
631     programStats.nr_moves = 0;
632     programStats.moves_left = 0;
633     programStats.nodes = 0;
634     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
635     programStats.score = 0;
636     programStats.got_only_move = 0;
637     programStats.got_fail = 0;
638     programStats.line_is_book = 0;
639 }
640
641 void
642 InitBackEnd1()
643 {
644     int matched, min, sec;
645
646     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
647     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
648
649     GetTimeMark(&programStartTime);
650     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
651
652     ClearProgramStats();
653     programStats.ok_to_send = 1;
654     programStats.seen_stat = 0;
655
656     /*
657      * Initialize game list
658      */
659     ListNew(&gameList);
660
661
662     /*
663      * Internet chess server status
664      */
665     if (appData.icsActive) {
666         appData.matchMode = FALSE;
667         appData.matchGames = 0;
668 #if ZIPPY       
669         appData.noChessProgram = !appData.zippyPlay;
670 #else
671         appData.zippyPlay = FALSE;
672         appData.zippyTalk = FALSE;
673         appData.noChessProgram = TRUE;
674 #endif
675         if (*appData.icsHelper != NULLCHAR) {
676             appData.useTelnet = TRUE;
677             appData.telnetProgram = appData.icsHelper;
678         }
679     } else {
680         appData.zippyTalk = appData.zippyPlay = FALSE;
681     }
682
683     /* [AS] Initialize pv info list [HGM] and game state */
684     {
685         int i, j;
686
687         for( i=0; i<=framePtr; i++ ) {
688             pvInfoList[i].depth = -1;
689             boards[i][EP_STATUS] = EP_NONE;
690             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
691         }
692     }
693
694     /*
695      * Parse timeControl resource
696      */
697     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
698                           appData.movesPerSession)) {
699         char buf[MSG_SIZ];
700         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
701         DisplayFatalError(buf, 0, 2);
702     }
703
704     /*
705      * Parse searchTime resource
706      */
707     if (*appData.searchTime != NULLCHAR) {
708         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
709         if (matched == 1) {
710             searchTime = min * 60;
711         } else if (matched == 2) {
712             searchTime = min * 60 + sec;
713         } else {
714             char buf[MSG_SIZ];
715             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
716             DisplayFatalError(buf, 0, 2);
717         }
718     }
719
720     /* [AS] Adjudication threshold */
721     adjudicateLossThreshold = appData.adjudicateLossThreshold;
722     
723     first.which = _("first");
724     second.which = _("second");
725     first.maybeThinking = second.maybeThinking = FALSE;
726     first.pr = second.pr = NoProc;
727     first.isr = second.isr = NULL;
728     first.sendTime = second.sendTime = 2;
729     first.sendDrawOffers = 1;
730     if (appData.firstPlaysBlack) {
731         first.twoMachinesColor = "black\n";
732         second.twoMachinesColor = "white\n";
733     } else {
734         first.twoMachinesColor = "white\n";
735         second.twoMachinesColor = "black\n";
736     }
737     first.program = appData.firstChessProgram;
738     second.program = appData.secondChessProgram;
739     first.host = appData.firstHost;
740     second.host = appData.secondHost;
741     first.dir = appData.firstDirectory;
742     second.dir = appData.secondDirectory;
743     first.other = &second;
744     second.other = &first;
745     first.initString = appData.initString;
746     second.initString = appData.secondInitString;
747     first.computerString = appData.firstComputerString;
748     second.computerString = appData.secondComputerString;
749     first.useSigint = second.useSigint = TRUE;
750     first.useSigterm = second.useSigterm = TRUE;
751     first.reuse = appData.reuseFirst;
752     second.reuse = appData.reuseSecond;
753     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
754     second.nps = appData.secondNPS;
755     first.useSetboard = second.useSetboard = FALSE;
756     first.useSAN = second.useSAN = FALSE;
757     first.usePing = second.usePing = FALSE;
758     first.lastPing = second.lastPing = 0;
759     first.lastPong = second.lastPong = 0;
760     first.usePlayother = second.usePlayother = FALSE;
761     first.useColors = second.useColors = TRUE;
762     first.useUsermove = second.useUsermove = FALSE;
763     first.sendICS = second.sendICS = FALSE;
764     first.sendName = second.sendName = appData.icsActive;
765     first.sdKludge = second.sdKludge = FALSE;
766     first.stKludge = second.stKludge = FALSE;
767     TidyProgramName(first.program, first.host, first.tidy);
768     TidyProgramName(second.program, second.host, second.tidy);
769     first.matchWins = second.matchWins = 0;
770     strcpy(first.variants, appData.variant);
771     strcpy(second.variants, appData.variant);
772     first.analysisSupport = second.analysisSupport = 2; /* detect */
773     first.analyzing = second.analyzing = FALSE;
774     first.initDone = second.initDone = FALSE;
775
776     /* New features added by Tord: */
777     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
778     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
779     /* End of new features added by Tord. */
780     first.fenOverride  = appData.fenOverride1;
781     second.fenOverride = appData.fenOverride2;
782
783     /* [HGM] time odds: set factor for each machine */
784     first.timeOdds  = appData.firstTimeOdds;
785     second.timeOdds = appData.secondTimeOdds;
786     { float norm = 1;
787         if(appData.timeOddsMode) {
788             norm = first.timeOdds;
789             if(norm > second.timeOdds) norm = second.timeOdds;
790         }
791         first.timeOdds /= norm;
792         second.timeOdds /= norm;
793     }
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     first.accumulateTC = appData.firstAccumulateTC;
797     second.accumulateTC = appData.secondAccumulateTC;
798     first.maxNrOfSessions = second.maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     first.debug = second.debug = FALSE;
802     first.supportsNPS = second.supportsNPS = UNKNOWN;
803
804     /* [HGM] options */
805     first.optionSettings  = appData.firstOptions;
806     second.optionSettings = appData.secondOptions;
807
808     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
809     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
810     first.isUCI = appData.firstIsUCI; /* [AS] */
811     second.isUCI = appData.secondIsUCI; /* [AS] */
812     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
813     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
814
815     if (appData.firstProtocolVersion > PROTOVER ||
816         appData.firstProtocolVersion < 1) {
817       char buf[MSG_SIZ];
818       sprintf(buf, _("protocol version %d not supported"),
819               appData.firstProtocolVersion);
820       DisplayFatalError(buf, 0, 2);
821     } else {
822       first.protocolVersion = appData.firstProtocolVersion;
823     }
824
825     if (appData.secondProtocolVersion > PROTOVER ||
826         appData.secondProtocolVersion < 1) {
827       char buf[MSG_SIZ];
828       sprintf(buf, _("protocol version %d not supported"),
829               appData.secondProtocolVersion);
830       DisplayFatalError(buf, 0, 2);
831     } else {
832       second.protocolVersion = appData.secondProtocolVersion;
833     }
834
835     if (appData.icsActive) {
836         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
837 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
838     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
839         appData.clockMode = FALSE;
840         first.sendTime = second.sendTime = 0;
841     }
842     
843 #if ZIPPY
844     /* Override some settings from environment variables, for backward
845        compatibility.  Unfortunately it's not feasible to have the env
846        vars just set defaults, at least in xboard.  Ugh.
847     */
848     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
849       ZippyInit();
850     }
851 #endif
852     
853     if (appData.noChessProgram) {
854         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
855         sprintf(programVersion, "%s", PACKAGE_STRING);
856     } else {
857       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
858       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
859       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
860     }
861
862     if (!appData.icsActive) {
863       char buf[MSG_SIZ];
864       /* Check for variants that are supported only in ICS mode,
865          or not at all.  Some that are accepted here nevertheless
866          have bugs; see comments below.
867       */
868       VariantClass variant = StringToVariant(appData.variant);
869       switch (variant) {
870       case VariantBughouse:     /* need four players and two boards */
871       case VariantKriegspiel:   /* need to hide pieces and move details */
872       /* case VariantFischeRandom: (Fabien: moved below) */
873         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantUnknown:
878       case VariantLoadable:
879       case Variant29:
880       case Variant30:
881       case Variant31:
882       case Variant32:
883       case Variant33:
884       case Variant34:
885       case Variant35:
886       case Variant36:
887       default:
888         sprintf(buf, _("Unknown variant name %s"), appData.variant);
889         DisplayFatalError(buf, 0, 2);
890         return;
891
892       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
893       case VariantFairy:      /* [HGM] TestLegality definitely off! */
894       case VariantGothic:     /* [HGM] should work */
895       case VariantCapablanca: /* [HGM] should work */
896       case VariantCourier:    /* [HGM] initial forced moves not implemented */
897       case VariantShogi:      /* [HGM] could still mate with pawn drop */
898       case VariantKnightmate: /* [HGM] should work */
899       case VariantCylinder:   /* [HGM] untested */
900       case VariantFalcon:     /* [HGM] untested */
901       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
902                                  offboard interposition not understood */
903       case VariantNormal:     /* definitely works! */
904       case VariantWildCastle: /* pieces not automatically shuffled */
905       case VariantNoCastle:   /* pieces not automatically shuffled */
906       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
907       case VariantLosers:     /* should work except for win condition,
908                                  and doesn't know captures are mandatory */
909       case VariantSuicide:    /* should work except for win condition,
910                                  and doesn't know captures are mandatory */
911       case VariantGiveaway:   /* should work except for win condition,
912                                  and doesn't know captures are mandatory */
913       case VariantTwoKings:   /* should work */
914       case VariantAtomic:     /* should work except for win condition */
915       case Variant3Check:     /* should work except for win condition */
916       case VariantShatranj:   /* should work except for all win conditions */
917       case VariantMakruk:     /* should work except for daw countdown */
918       case VariantBerolina:   /* might work if TestLegality is off */
919       case VariantCapaRandom: /* should work */
920       case VariantJanus:      /* should work */
921       case VariantSuper:      /* experimental */
922       case VariantGreat:      /* experimental, requires legality testing to be off */
923         break;
924       }
925     }
926
927     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
928     InitEngineUCI( installDir, &second );
929 }
930
931 int NextIntegerFromString( char ** str, long * value )
932 {
933     int result = -1;
934     char * s = *str;
935
936     while( *s == ' ' || *s == '\t' ) {
937         s++;
938     }
939
940     *value = 0;
941
942     if( *s >= '0' && *s <= '9' ) {
943         while( *s >= '0' && *s <= '9' ) {
944             *value = *value * 10 + (*s - '0');
945             s++;
946         }
947
948         result = 0;
949     }
950
951     *str = s;
952
953     return result;
954 }
955
956 int NextTimeControlFromString( char ** str, long * value )
957 {
958     long temp;
959     int result = NextIntegerFromString( str, &temp );
960
961     if( result == 0 ) {
962         *value = temp * 60; /* Minutes */
963         if( **str == ':' ) {
964             (*str)++;
965             result = NextIntegerFromString( str, &temp );
966             *value += temp; /* Seconds */
967         }
968     }
969
970     return result;
971 }
972
973 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
974 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
975     int result = -1; long temp, temp2;
976
977     if(**str != '+') return -1; // old params remain in force!
978     (*str)++;
979     if( NextTimeControlFromString( str, &temp ) ) return -1;
980
981     if(**str != '/') {
982         /* time only: incremental or sudden-death time control */
983         if(**str == '+') { /* increment follows; read it */
984             (*str)++;
985             if(result = NextIntegerFromString( str, &temp2)) return -1;
986             *inc = temp2 * 1000;
987         } else *inc = 0;
988         *moves = 0; *tc = temp * 1000; 
989         return 0;
990     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
991
992     (*str)++; /* classical time control */
993     result = NextTimeControlFromString( str, &temp2);
994     if(result == 0) {
995         *moves = temp/60;
996         *tc    = temp2 * 1000;
997         *inc   = 0;
998     }
999     return result;
1000 }
1001
1002 int GetTimeQuota(int movenr)
1003 {   /* [HGM] get time to add from the multi-session time-control string */
1004     int moves=1; /* kludge to force reading of first session */
1005     long time, increment;
1006     char *s = fullTimeControlString;
1007
1008     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1009     do {
1010         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1011         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1012         if(movenr == -1) return time;    /* last move before new session     */
1013         if(!moves) return increment;     /* current session is incremental   */
1014         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1015     } while(movenr >= -1);               /* try again for next session       */
1016
1017     return 0; // no new time quota on this move
1018 }
1019
1020 int
1021 ParseTimeControl(tc, ti, mps)
1022      char *tc;
1023      int ti;
1024      int mps;
1025 {
1026   long tc1;
1027   long tc2;
1028   char buf[MSG_SIZ];
1029   
1030   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1031   if(ti > 0) {
1032     if(mps)
1033       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1034     else sprintf(buf, "+%s+%d", tc, ti);
1035   } else {
1036     if(mps)
1037              sprintf(buf, "+%d/%s", mps, tc);
1038     else sprintf(buf, "+%s", tc);
1039   }
1040   fullTimeControlString = StrSave(buf);
1041   
1042   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1043     return FALSE;
1044   }
1045   
1046   if( *tc == '/' ) {
1047     /* Parse second time control */
1048     tc++;
1049     
1050     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1051       return FALSE;
1052     }
1053     
1054     if( tc2 == 0 ) {
1055       return FALSE;
1056     }
1057     
1058     timeControl_2 = tc2 * 1000;
1059   }
1060   else {
1061     timeControl_2 = 0;
1062   }
1063   
1064   if( tc1 == 0 ) {
1065     return FALSE;
1066   }
1067   
1068   timeControl = tc1 * 1000;
1069   
1070   if (ti >= 0) {
1071     timeIncrement = ti * 1000;  /* convert to ms */
1072     movesPerSession = 0;
1073   } else {
1074     timeIncrement = 0;
1075     movesPerSession = mps;
1076   }
1077   return TRUE;
1078 }
1079
1080 void
1081 InitBackEnd2()
1082 {
1083     if (appData.debugMode) {
1084         fprintf(debugFP, "%s\n", programVersion);
1085     }
1086
1087     set_cont_sequence(appData.wrapContSeq);
1088     if (appData.matchGames > 0) {
1089         appData.matchMode = TRUE;
1090     } else if (appData.matchMode) {
1091         appData.matchGames = 1;
1092     }
1093     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1094         appData.matchGames = appData.sameColorGames;
1095     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1096         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1097         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1098     }
1099     Reset(TRUE, FALSE);
1100     if (appData.noChessProgram || first.protocolVersion == 1) {
1101       InitBackEnd3();
1102     } else {
1103       /* kludge: allow timeout for initial "feature" commands */
1104       FreezeUI();
1105       DisplayMessage("", _("Starting chess program"));
1106       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1107     }
1108 }
1109
1110 void
1111 InitBackEnd3 P((void))
1112 {
1113     GameMode initialMode;
1114     char buf[MSG_SIZ];
1115     int err;
1116
1117     InitChessProgram(&first, startedFromSetupPosition);
1118
1119     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1120         free(programVersion);
1121         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1122         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1123     }
1124
1125     if (appData.icsActive) {
1126 #ifdef WIN32
1127         /* [DM] Make a console window if needed [HGM] merged ifs */
1128         ConsoleCreate(); 
1129 #endif
1130         err = establish();
1131         if (err != 0) {
1132             if (*appData.icsCommPort != NULLCHAR) {
1133                 sprintf(buf, _("Could not open comm port %s"),  
1134                         appData.icsCommPort);
1135             } else {
1136                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1137                         appData.icsHost, appData.icsPort);
1138             }
1139             DisplayFatalError(buf, err, 1);
1140             return;
1141         }
1142         SetICSMode();
1143         telnetISR =
1144           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1145         fromUserISR =
1146           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1147         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1148             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1149     } else if (appData.noChessProgram) {
1150         SetNCPMode();
1151     } else {
1152         SetGNUMode();
1153     }
1154
1155     if (*appData.cmailGameName != NULLCHAR) {
1156         SetCmailMode();
1157         OpenLoopback(&cmailPR);
1158         cmailISR =
1159           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1160     }
1161     
1162     ThawUI();
1163     DisplayMessage("", "");
1164     if (StrCaseCmp(appData.initialMode, "") == 0) {
1165       initialMode = BeginningOfGame;
1166     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1167       initialMode = TwoMachinesPlay;
1168     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1169       initialMode = AnalyzeFile; 
1170     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1171       initialMode = AnalyzeMode;
1172     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1173       initialMode = MachinePlaysWhite;
1174     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1175       initialMode = MachinePlaysBlack;
1176     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1177       initialMode = EditGame;
1178     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1179       initialMode = EditPosition;
1180     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1181       initialMode = Training;
1182     } else {
1183       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1184       DisplayFatalError(buf, 0, 2);
1185       return;
1186     }
1187
1188     if (appData.matchMode) {
1189         /* Set up machine vs. machine match */
1190         if (appData.noChessProgram) {
1191             DisplayFatalError(_("Can't have a match with no chess programs"),
1192                               0, 2);
1193             return;
1194         }
1195         matchMode = TRUE;
1196         matchGame = 1;
1197         if (*appData.loadGameFile != NULLCHAR) {
1198             int index = appData.loadGameIndex; // [HGM] autoinc
1199             if(index<0) lastIndex = index = 1;
1200             if (!LoadGameFromFile(appData.loadGameFile,
1201                                   index,
1202                                   appData.loadGameFile, FALSE)) {
1203                 DisplayFatalError(_("Bad game file"), 0, 1);
1204                 return;
1205             }
1206         } else if (*appData.loadPositionFile != NULLCHAR) {
1207             int index = appData.loadPositionIndex; // [HGM] autoinc
1208             if(index<0) lastIndex = index = 1;
1209             if (!LoadPositionFromFile(appData.loadPositionFile,
1210                                       index,
1211                                       appData.loadPositionFile)) {
1212                 DisplayFatalError(_("Bad position file"), 0, 1);
1213                 return;
1214             }
1215         }
1216         TwoMachinesEvent();
1217     } else if (*appData.cmailGameName != NULLCHAR) {
1218         /* Set up cmail mode */
1219         ReloadCmailMsgEvent(TRUE);
1220     } else {
1221         /* Set up other modes */
1222         if (initialMode == AnalyzeFile) {
1223           if (*appData.loadGameFile == NULLCHAR) {
1224             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1225             return;
1226           }
1227         }
1228         if (*appData.loadGameFile != NULLCHAR) {
1229             (void) LoadGameFromFile(appData.loadGameFile,
1230                                     appData.loadGameIndex,
1231                                     appData.loadGameFile, TRUE);
1232         } else if (*appData.loadPositionFile != NULLCHAR) {
1233             (void) LoadPositionFromFile(appData.loadPositionFile,
1234                                         appData.loadPositionIndex,
1235                                         appData.loadPositionFile);
1236             /* [HGM] try to make self-starting even after FEN load */
1237             /* to allow automatic setup of fairy variants with wtm */
1238             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1239                 gameMode = BeginningOfGame;
1240                 setboardSpoiledMachineBlack = 1;
1241             }
1242             /* [HGM] loadPos: make that every new game uses the setup */
1243             /* from file as long as we do not switch variant          */
1244             if(!blackPlaysFirst) {
1245                 startedFromPositionFile = TRUE;
1246                 CopyBoard(filePosition, boards[0]);
1247             }
1248         }
1249         if (initialMode == AnalyzeMode) {
1250           if (appData.noChessProgram) {
1251             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1252             return;
1253           }
1254           if (appData.icsActive) {
1255             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1256             return;
1257           }
1258           AnalyzeModeEvent();
1259         } else if (initialMode == AnalyzeFile) {
1260           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1261           ShowThinkingEvent();
1262           AnalyzeFileEvent();
1263           AnalysisPeriodicEvent(1);
1264         } else if (initialMode == MachinePlaysWhite) {
1265           if (appData.noChessProgram) {
1266             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1267                               0, 2);
1268             return;
1269           }
1270           if (appData.icsActive) {
1271             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1272                               0, 2);
1273             return;
1274           }
1275           MachineWhiteEvent();
1276         } else if (initialMode == MachinePlaysBlack) {
1277           if (appData.noChessProgram) {
1278             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1279                               0, 2);
1280             return;
1281           }
1282           if (appData.icsActive) {
1283             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1284                               0, 2);
1285             return;
1286           }
1287           MachineBlackEvent();
1288         } else if (initialMode == TwoMachinesPlay) {
1289           if (appData.noChessProgram) {
1290             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1291                               0, 2);
1292             return;
1293           }
1294           if (appData.icsActive) {
1295             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1296                               0, 2);
1297             return;
1298           }
1299           TwoMachinesEvent();
1300         } else if (initialMode == EditGame) {
1301           EditGameEvent();
1302         } else if (initialMode == EditPosition) {
1303           EditPositionEvent();
1304         } else if (initialMode == Training) {
1305           if (*appData.loadGameFile == NULLCHAR) {
1306             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1307             return;
1308           }
1309           TrainingEvent();
1310         }
1311     }
1312 }
1313
1314 /*
1315  * Establish will establish a contact to a remote host.port.
1316  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1317  *  used to talk to the host.
1318  * Returns 0 if okay, error code if not.
1319  */
1320 int
1321 establish()
1322 {
1323     char buf[MSG_SIZ];
1324
1325     if (*appData.icsCommPort != NULLCHAR) {
1326         /* Talk to the host through a serial comm port */
1327         return OpenCommPort(appData.icsCommPort, &icsPR);
1328
1329     } else if (*appData.gateway != NULLCHAR) {
1330         if (*appData.remoteShell == NULLCHAR) {
1331             /* Use the rcmd protocol to run telnet program on a gateway host */
1332             snprintf(buf, sizeof(buf), "%s %s %s",
1333                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1334             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1335
1336         } else {
1337             /* Use the rsh program to run telnet program on a gateway host */
1338             if (*appData.remoteUser == NULLCHAR) {
1339                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1340                         appData.gateway, appData.telnetProgram,
1341                         appData.icsHost, appData.icsPort);
1342             } else {
1343                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1344                         appData.remoteShell, appData.gateway, 
1345                         appData.remoteUser, appData.telnetProgram,
1346                         appData.icsHost, appData.icsPort);
1347             }
1348             return StartChildProcess(buf, "", &icsPR);
1349
1350         }
1351     } else if (appData.useTelnet) {
1352         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1353
1354     } else {
1355         /* TCP socket interface differs somewhat between
1356            Unix and NT; handle details in the front end.
1357            */
1358         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1359     }
1360 }
1361
1362 void EscapeExpand(char *p, char *q)
1363 {       // [HGM] initstring: routine to shape up string arguments
1364         while(*p++ = *q++) if(p[-1] == '\\')
1365             switch(*q++) {
1366                 case 'n': p[-1] = '\n'; break;
1367                 case 'r': p[-1] = '\r'; break;
1368                 case 't': p[-1] = '\t'; break;
1369                 case '\\': p[-1] = '\\'; break;
1370                 case 0: *p = 0; return;
1371                 default: p[-1] = q[-1]; break;
1372             }
1373 }
1374
1375 void
1376 show_bytes(fp, buf, count)
1377      FILE *fp;
1378      char *buf;
1379      int count;
1380 {
1381     while (count--) {
1382         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1383             fprintf(fp, "\\%03o", *buf & 0xff);
1384         } else {
1385             putc(*buf, fp);
1386         }
1387         buf++;
1388     }
1389     fflush(fp);
1390 }
1391
1392 /* Returns an errno value */
1393 int
1394 OutputMaybeTelnet(pr, message, count, outError)
1395      ProcRef pr;
1396      char *message;
1397      int count;
1398      int *outError;
1399 {
1400     char buf[8192], *p, *q, *buflim;
1401     int left, newcount, outcount;
1402
1403     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1404         *appData.gateway != NULLCHAR) {
1405         if (appData.debugMode) {
1406             fprintf(debugFP, ">ICS: ");
1407             show_bytes(debugFP, message, count);
1408             fprintf(debugFP, "\n");
1409         }
1410         return OutputToProcess(pr, message, count, outError);
1411     }
1412
1413     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1414     p = message;
1415     q = buf;
1416     left = count;
1417     newcount = 0;
1418     while (left) {
1419         if (q >= buflim) {
1420             if (appData.debugMode) {
1421                 fprintf(debugFP, ">ICS: ");
1422                 show_bytes(debugFP, buf, newcount);
1423                 fprintf(debugFP, "\n");
1424             }
1425             outcount = OutputToProcess(pr, buf, newcount, outError);
1426             if (outcount < newcount) return -1; /* to be sure */
1427             q = buf;
1428             newcount = 0;
1429         }
1430         if (*p == '\n') {
1431             *q++ = '\r';
1432             newcount++;
1433         } else if (((unsigned char) *p) == TN_IAC) {
1434             *q++ = (char) TN_IAC;
1435             newcount ++;
1436         }
1437         *q++ = *p++;
1438         newcount++;
1439         left--;
1440     }
1441     if (appData.debugMode) {
1442         fprintf(debugFP, ">ICS: ");
1443         show_bytes(debugFP, buf, newcount);
1444         fprintf(debugFP, "\n");
1445     }
1446     outcount = OutputToProcess(pr, buf, newcount, outError);
1447     if (outcount < newcount) return -1; /* to be sure */
1448     return count;
1449 }
1450
1451 void
1452 read_from_player(isr, closure, message, count, error)
1453      InputSourceRef isr;
1454      VOIDSTAR closure;
1455      char *message;
1456      int count;
1457      int error;
1458 {
1459     int outError, outCount;
1460     static int gotEof = 0;
1461
1462     /* Pass data read from player on to ICS */
1463     if (count > 0) {
1464         gotEof = 0;
1465         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1466         if (outCount < count) {
1467             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1468         }
1469     } else if (count < 0) {
1470         RemoveInputSource(isr);
1471         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1472     } else if (gotEof++ > 0) {
1473         RemoveInputSource(isr);
1474         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1475     }
1476 }
1477
1478 void
1479 KeepAlive()
1480 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1481     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1482     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1483     SendToICS("date\n");
1484     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1485 }
1486
1487 /* added routine for printf style output to ics */
1488 void ics_printf(char *format, ...)
1489 {
1490     char buffer[MSG_SIZ];
1491     va_list args;
1492
1493     va_start(args, format);
1494     vsnprintf(buffer, sizeof(buffer), format, args);
1495     buffer[sizeof(buffer)-1] = '\0';
1496     SendToICS(buffer);
1497     va_end(args);
1498 }
1499
1500 void
1501 SendToICS(s)
1502      char *s;
1503 {
1504     int count, outCount, outError;
1505
1506     if (icsPR == NULL) return;
1507
1508     count = strlen(s);
1509     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1510     if (outCount < count) {
1511         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1512     }
1513 }
1514
1515 /* This is used for sending logon scripts to the ICS. Sending
1516    without a delay causes problems when using timestamp on ICC
1517    (at least on my machine). */
1518 void
1519 SendToICSDelayed(s,msdelay)
1520      char *s;
1521      long msdelay;
1522 {
1523     int count, outCount, outError;
1524
1525     if (icsPR == NULL) return;
1526
1527     count = strlen(s);
1528     if (appData.debugMode) {
1529         fprintf(debugFP, ">ICS: ");
1530         show_bytes(debugFP, s, count);
1531         fprintf(debugFP, "\n");
1532     }
1533     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1534                                       msdelay);
1535     if (outCount < count) {
1536         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1537     }
1538 }
1539
1540
1541 /* Remove all highlighting escape sequences in s
1542    Also deletes any suffix starting with '(' 
1543    */
1544 char *
1545 StripHighlightAndTitle(s)
1546      char *s;
1547 {
1548     static char retbuf[MSG_SIZ];
1549     char *p = retbuf;
1550
1551     while (*s != NULLCHAR) {
1552         while (*s == '\033') {
1553             while (*s != NULLCHAR && !isalpha(*s)) s++;
1554             if (*s != NULLCHAR) s++;
1555         }
1556         while (*s != NULLCHAR && *s != '\033') {
1557             if (*s == '(' || *s == '[') {
1558                 *p = NULLCHAR;
1559                 return retbuf;
1560             }
1561             *p++ = *s++;
1562         }
1563     }
1564     *p = NULLCHAR;
1565     return retbuf;
1566 }
1567
1568 /* Remove all highlighting escape sequences in s */
1569 char *
1570 StripHighlight(s)
1571      char *s;
1572 {
1573     static char retbuf[MSG_SIZ];
1574     char *p = retbuf;
1575
1576     while (*s != NULLCHAR) {
1577         while (*s == '\033') {
1578             while (*s != NULLCHAR && !isalpha(*s)) s++;
1579             if (*s != NULLCHAR) s++;
1580         }
1581         while (*s != NULLCHAR && *s != '\033') {
1582             *p++ = *s++;
1583         }
1584     }
1585     *p = NULLCHAR;
1586     return retbuf;
1587 }
1588
1589 char *variantNames[] = VARIANT_NAMES;
1590 char *
1591 VariantName(v)
1592      VariantClass v;
1593 {
1594     return variantNames[v];
1595 }
1596
1597
1598 /* Identify a variant from the strings the chess servers use or the
1599    PGN Variant tag names we use. */
1600 VariantClass
1601 StringToVariant(e)
1602      char *e;
1603 {
1604     char *p;
1605     int wnum = -1;
1606     VariantClass v = VariantNormal;
1607     int i, found = FALSE;
1608     char buf[MSG_SIZ];
1609
1610     if (!e) return v;
1611
1612     /* [HGM] skip over optional board-size prefixes */
1613     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1614         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1615         while( *e++ != '_');
1616     }
1617
1618     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1619         v = VariantNormal;
1620         found = TRUE;
1621     } else
1622     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1623       if (StrCaseStr(e, variantNames[i])) {
1624         v = (VariantClass) i;
1625         found = TRUE;
1626         break;
1627       }
1628     }
1629
1630     if (!found) {
1631       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1632           || StrCaseStr(e, "wild/fr") 
1633           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1634         v = VariantFischeRandom;
1635       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1636                  (i = 1, p = StrCaseStr(e, "w"))) {
1637         p += i;
1638         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1639         if (isdigit(*p)) {
1640           wnum = atoi(p);
1641         } else {
1642           wnum = -1;
1643         }
1644         switch (wnum) {
1645         case 0: /* FICS only, actually */
1646         case 1:
1647           /* Castling legal even if K starts on d-file */
1648           v = VariantWildCastle;
1649           break;
1650         case 2:
1651         case 3:
1652         case 4:
1653           /* Castling illegal even if K & R happen to start in
1654              normal positions. */
1655           v = VariantNoCastle;
1656           break;
1657         case 5:
1658         case 7:
1659         case 8:
1660         case 10:
1661         case 11:
1662         case 12:
1663         case 13:
1664         case 14:
1665         case 15:
1666         case 18:
1667         case 19:
1668           /* Castling legal iff K & R start in normal positions */
1669           v = VariantNormal;
1670           break;
1671         case 6:
1672         case 20:
1673         case 21:
1674           /* Special wilds for position setup; unclear what to do here */
1675           v = VariantLoadable;
1676           break;
1677         case 9:
1678           /* Bizarre ICC game */
1679           v = VariantTwoKings;
1680           break;
1681         case 16:
1682           v = VariantKriegspiel;
1683           break;
1684         case 17:
1685           v = VariantLosers;
1686           break;
1687         case 22:
1688           v = VariantFischeRandom;
1689           break;
1690         case 23:
1691           v = VariantCrazyhouse;
1692           break;
1693         case 24:
1694           v = VariantBughouse;
1695           break;
1696         case 25:
1697           v = Variant3Check;
1698           break;
1699         case 26:
1700           /* Not quite the same as FICS suicide! */
1701           v = VariantGiveaway;
1702           break;
1703         case 27:
1704           v = VariantAtomic;
1705           break;
1706         case 28:
1707           v = VariantShatranj;
1708           break;
1709
1710         /* Temporary names for future ICC types.  The name *will* change in 
1711            the next xboard/WinBoard release after ICC defines it. */
1712         case 29:
1713           v = Variant29;
1714           break;
1715         case 30:
1716           v = Variant30;
1717           break;
1718         case 31:
1719           v = Variant31;
1720           break;
1721         case 32:
1722           v = Variant32;
1723           break;
1724         case 33:
1725           v = Variant33;
1726           break;
1727         case 34:
1728           v = Variant34;
1729           break;
1730         case 35:
1731           v = Variant35;
1732           break;
1733         case 36:
1734           v = Variant36;
1735           break;
1736         case 37:
1737           v = VariantShogi;
1738           break;
1739         case 38:
1740           v = VariantXiangqi;
1741           break;
1742         case 39:
1743           v = VariantCourier;
1744           break;
1745         case 40:
1746           v = VariantGothic;
1747           break;
1748         case 41:
1749           v = VariantCapablanca;
1750           break;
1751         case 42:
1752           v = VariantKnightmate;
1753           break;
1754         case 43:
1755           v = VariantFairy;
1756           break;
1757         case 44:
1758           v = VariantCylinder;
1759           break;
1760         case 45:
1761           v = VariantFalcon;
1762           break;
1763         case 46:
1764           v = VariantCapaRandom;
1765           break;
1766         case 47:
1767           v = VariantBerolina;
1768           break;
1769         case 48:
1770           v = VariantJanus;
1771           break;
1772         case 49:
1773           v = VariantSuper;
1774           break;
1775         case 50:
1776           v = VariantGreat;
1777           break;
1778         case -1:
1779           /* Found "wild" or "w" in the string but no number;
1780              must assume it's normal chess. */
1781           v = VariantNormal;
1782           break;
1783         default:
1784           sprintf(buf, _("Unknown wild type %d"), wnum);
1785           DisplayError(buf, 0);
1786           v = VariantUnknown;
1787           break;
1788         }
1789       }
1790     }
1791     if (appData.debugMode) {
1792       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1793               e, wnum, VariantName(v));
1794     }
1795     return v;
1796 }
1797
1798 static int leftover_start = 0, leftover_len = 0;
1799 char star_match[STAR_MATCH_N][MSG_SIZ];
1800
1801 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1802    advance *index beyond it, and set leftover_start to the new value of
1803    *index; else return FALSE.  If pattern contains the character '*', it
1804    matches any sequence of characters not containing '\r', '\n', or the
1805    character following the '*' (if any), and the matched sequence(s) are
1806    copied into star_match.
1807    */
1808 int
1809 looking_at(buf, index, pattern)
1810      char *buf;
1811      int *index;
1812      char *pattern;
1813 {
1814     char *bufp = &buf[*index], *patternp = pattern;
1815     int star_count = 0;
1816     char *matchp = star_match[0];
1817     
1818     for (;;) {
1819         if (*patternp == NULLCHAR) {
1820             *index = leftover_start = bufp - buf;
1821             *matchp = NULLCHAR;
1822             return TRUE;
1823         }
1824         if (*bufp == NULLCHAR) return FALSE;
1825         if (*patternp == '*') {
1826             if (*bufp == *(patternp + 1)) {
1827                 *matchp = NULLCHAR;
1828                 matchp = star_match[++star_count];
1829                 patternp += 2;
1830                 bufp++;
1831                 continue;
1832             } else if (*bufp == '\n' || *bufp == '\r') {
1833                 patternp++;
1834                 if (*patternp == NULLCHAR)
1835                   continue;
1836                 else
1837                   return FALSE;
1838             } else {
1839                 *matchp++ = *bufp++;
1840                 continue;
1841             }
1842         }
1843         if (*patternp != *bufp) return FALSE;
1844         patternp++;
1845         bufp++;
1846     }
1847 }
1848
1849 void
1850 SendToPlayer(data, length)
1851      char *data;
1852      int length;
1853 {
1854     int error, outCount;
1855     outCount = OutputToProcess(NoProc, data, length, &error);
1856     if (outCount < length) {
1857         DisplayFatalError(_("Error writing to display"), error, 1);
1858     }
1859 }
1860
1861 void
1862 PackHolding(packed, holding)
1863      char packed[];
1864      char *holding;
1865 {
1866     char *p = holding;
1867     char *q = packed;
1868     int runlength = 0;
1869     int curr = 9999;
1870     do {
1871         if (*p == curr) {
1872             runlength++;
1873         } else {
1874             switch (runlength) {
1875               case 0:
1876                 break;
1877               case 1:
1878                 *q++ = curr;
1879                 break;
1880               case 2:
1881                 *q++ = curr;
1882                 *q++ = curr;
1883                 break;
1884               default:
1885                 sprintf(q, "%d", runlength);
1886                 while (*q) q++;
1887                 *q++ = curr;
1888                 break;
1889             }
1890             runlength = 1;
1891             curr = *p;
1892         }
1893     } while (*p++);
1894     *q = NULLCHAR;
1895 }
1896
1897 /* Telnet protocol requests from the front end */
1898 void
1899 TelnetRequest(ddww, option)
1900      unsigned char ddww, option;
1901 {
1902     unsigned char msg[3];
1903     int outCount, outError;
1904
1905     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1906
1907     if (appData.debugMode) {
1908         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1909         switch (ddww) {
1910           case TN_DO:
1911             ddwwStr = "DO";
1912             break;
1913           case TN_DONT:
1914             ddwwStr = "DONT";
1915             break;
1916           case TN_WILL:
1917             ddwwStr = "WILL";
1918             break;
1919           case TN_WONT:
1920             ddwwStr = "WONT";
1921             break;
1922           default:
1923             ddwwStr = buf1;
1924             sprintf(buf1, "%d", ddww);
1925             break;
1926         }
1927         switch (option) {
1928           case TN_ECHO:
1929             optionStr = "ECHO";
1930             break;
1931           default:
1932             optionStr = buf2;
1933             sprintf(buf2, "%d", option);
1934             break;
1935         }
1936         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1937     }
1938     msg[0] = TN_IAC;
1939     msg[1] = ddww;
1940     msg[2] = option;
1941     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1942     if (outCount < 3) {
1943         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1944     }
1945 }
1946
1947 void
1948 DoEcho()
1949 {
1950     if (!appData.icsActive) return;
1951     TelnetRequest(TN_DO, TN_ECHO);
1952 }
1953
1954 void
1955 DontEcho()
1956 {
1957     if (!appData.icsActive) return;
1958     TelnetRequest(TN_DONT, TN_ECHO);
1959 }
1960
1961 void
1962 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1963 {
1964     /* put the holdings sent to us by the server on the board holdings area */
1965     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1966     char p;
1967     ChessSquare piece;
1968
1969     if(gameInfo.holdingsWidth < 2)  return;
1970     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1971         return; // prevent overwriting by pre-board holdings
1972
1973     if( (int)lowestPiece >= BlackPawn ) {
1974         holdingsColumn = 0;
1975         countsColumn = 1;
1976         holdingsStartRow = BOARD_HEIGHT-1;
1977         direction = -1;
1978     } else {
1979         holdingsColumn = BOARD_WIDTH-1;
1980         countsColumn = BOARD_WIDTH-2;
1981         holdingsStartRow = 0;
1982         direction = 1;
1983     }
1984
1985     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1986         board[i][holdingsColumn] = EmptySquare;
1987         board[i][countsColumn]   = (ChessSquare) 0;
1988     }
1989     while( (p=*holdings++) != NULLCHAR ) {
1990         piece = CharToPiece( ToUpper(p) );
1991         if(piece == EmptySquare) continue;
1992         /*j = (int) piece - (int) WhitePawn;*/
1993         j = PieceToNumber(piece);
1994         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1995         if(j < 0) continue;               /* should not happen */
1996         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1997         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1998         board[holdingsStartRow+j*direction][countsColumn]++;
1999     }
2000 }
2001
2002
2003 void
2004 VariantSwitch(Board board, VariantClass newVariant)
2005 {
2006    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2007    static Board oldBoard;
2008
2009    startedFromPositionFile = FALSE;
2010    if(gameInfo.variant == newVariant) return;
2011
2012    /* [HGM] This routine is called each time an assignment is made to
2013     * gameInfo.variant during a game, to make sure the board sizes
2014     * are set to match the new variant. If that means adding or deleting
2015     * holdings, we shift the playing board accordingly
2016     * This kludge is needed because in ICS observe mode, we get boards
2017     * of an ongoing game without knowing the variant, and learn about the
2018     * latter only later. This can be because of the move list we requested,
2019     * in which case the game history is refilled from the beginning anyway,
2020     * but also when receiving holdings of a crazyhouse game. In the latter
2021     * case we want to add those holdings to the already received position.
2022     */
2023
2024    
2025    if (appData.debugMode) {
2026      fprintf(debugFP, "Switch board from %s to %s\n",
2027              VariantName(gameInfo.variant), VariantName(newVariant));
2028      setbuf(debugFP, NULL);
2029    }
2030    shuffleOpenings = 0;       /* [HGM] shuffle */
2031    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2032    switch(newVariant) 
2033      {
2034      case VariantShogi:
2035        newWidth = 9;  newHeight = 9;
2036        gameInfo.holdingsSize = 7;
2037      case VariantBughouse:
2038      case VariantCrazyhouse:
2039        newHoldingsWidth = 2; break;
2040      case VariantGreat:
2041        newWidth = 10;
2042      case VariantSuper:
2043        newHoldingsWidth = 2;
2044        gameInfo.holdingsSize = 8;
2045        break;
2046      case VariantGothic:
2047      case VariantCapablanca:
2048      case VariantCapaRandom:
2049        newWidth = 10;
2050      default:
2051        newHoldingsWidth = gameInfo.holdingsSize = 0;
2052      };
2053    
2054    if(newWidth  != gameInfo.boardWidth  ||
2055       newHeight != gameInfo.boardHeight ||
2056       newHoldingsWidth != gameInfo.holdingsWidth ) {
2057      
2058      /* shift position to new playing area, if needed */
2059      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2060        for(i=0; i<BOARD_HEIGHT; i++) 
2061          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2062            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2063              board[i][j];
2064        for(i=0; i<newHeight; i++) {
2065          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2066          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2067        }
2068      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2069        for(i=0; i<BOARD_HEIGHT; i++)
2070          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2071            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2072              board[i][j];
2073      }
2074      gameInfo.boardWidth  = newWidth;
2075      gameInfo.boardHeight = newHeight;
2076      gameInfo.holdingsWidth = newHoldingsWidth;
2077      gameInfo.variant = newVariant;
2078      InitDrawingSizes(-2, 0);
2079    } else gameInfo.variant = newVariant;
2080    CopyBoard(oldBoard, board);   // remember correctly formatted board
2081      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2082    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2083 }
2084
2085 static int loggedOn = FALSE;
2086
2087 /*-- Game start info cache: --*/
2088 int gs_gamenum;
2089 char gs_kind[MSG_SIZ];
2090 static char player1Name[128] = "";
2091 static char player2Name[128] = "";
2092 static char cont_seq[] = "\n\\   ";
2093 static int player1Rating = -1;
2094 static int player2Rating = -1;
2095 /*----------------------------*/
2096
2097 ColorClass curColor = ColorNormal;
2098 int suppressKibitz = 0;
2099
2100 // [HGM] seekgraph
2101 Boolean soughtPending = FALSE;
2102 Boolean seekGraphUp;
2103 #define MAX_SEEK_ADS 200
2104 #define SQUARE 0x80
2105 char *seekAdList[MAX_SEEK_ADS];
2106 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2107 float tcList[MAX_SEEK_ADS];
2108 char colorList[MAX_SEEK_ADS];
2109 int nrOfSeekAds = 0;
2110 int minRating = 1010, maxRating = 2800;
2111 int hMargin = 10, vMargin = 20, h, w;
2112 extern int squareSize, lineGap;
2113
2114 void
2115 PlotSeekAd(int i)
2116 {
2117         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2118         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2119         if(r < minRating+100 && r >=0 ) r = minRating+100;
2120         if(r > maxRating) r = maxRating;
2121         if(tc < 1.) tc = 1.;
2122         if(tc > 95.) tc = 95.;
2123         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2124         y = ((double)r - minRating)/(maxRating - minRating)
2125             * (h-vMargin-squareSize/8-1) + vMargin;
2126         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2127         if(strstr(seekAdList[i], " u ")) color = 1;
2128         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2129            !strstr(seekAdList[i], "bullet") &&
2130            !strstr(seekAdList[i], "blitz") &&
2131            !strstr(seekAdList[i], "standard") ) color = 2;
2132         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2133         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2134 }
2135
2136 void
2137 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2138 {
2139         char buf[MSG_SIZ], *ext = "";
2140         VariantClass v = StringToVariant(type);
2141         if(strstr(type, "wild")) {
2142             ext = type + 4; // append wild number
2143             if(v == VariantFischeRandom) type = "chess960"; else
2144             if(v == VariantLoadable) type = "setup"; else
2145             type = VariantName(v);
2146         }
2147         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2148         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2149             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2150             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2151             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2152             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2153             seekNrList[nrOfSeekAds] = nr;
2154             zList[nrOfSeekAds] = 0;
2155             seekAdList[nrOfSeekAds++] = StrSave(buf);
2156             if(plot) PlotSeekAd(nrOfSeekAds-1);
2157         }
2158 }
2159
2160 void
2161 EraseSeekDot(int i)
2162 {
2163     int x = xList[i], y = yList[i], d=squareSize/4, k;
2164     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2165     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2166     // now replot every dot that overlapped
2167     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2168         int xx = xList[k], yy = yList[k];
2169         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2170             DrawSeekDot(xx, yy, colorList[k]);
2171     }
2172 }
2173
2174 void
2175 RemoveSeekAd(int nr)
2176 {
2177         int i;
2178         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2179             EraseSeekDot(i);
2180             if(seekAdList[i]) free(seekAdList[i]);
2181             seekAdList[i] = seekAdList[--nrOfSeekAds];
2182             seekNrList[i] = seekNrList[nrOfSeekAds];
2183             ratingList[i] = ratingList[nrOfSeekAds];
2184             colorList[i]  = colorList[nrOfSeekAds];
2185             tcList[i] = tcList[nrOfSeekAds];
2186             xList[i]  = xList[nrOfSeekAds];
2187             yList[i]  = yList[nrOfSeekAds];
2188             zList[i]  = zList[nrOfSeekAds];
2189             seekAdList[nrOfSeekAds] = NULL;
2190             break;
2191         }
2192 }
2193
2194 Boolean
2195 MatchSoughtLine(char *line)
2196 {
2197     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2198     int nr, base, inc, u=0; char dummy;
2199
2200     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2201        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2202        (u=1) &&
2203        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2204         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2205         // match: compact and save the line
2206         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2207         return TRUE;
2208     }
2209     return FALSE;
2210 }
2211
2212 int
2213 DrawSeekGraph()
2214 {
2215     int i;
2216     if(!seekGraphUp) return FALSE;
2217     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2218     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2219
2220     DrawSeekBackground(0, 0, w, h);
2221     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2222     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2223     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2224         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2225         yy = h-1-yy;
2226         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2227         if(i%500 == 0) {
2228             char buf[MSG_SIZ];
2229             sprintf(buf, "%d", i);
2230             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2231         }
2232     }
2233     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2234     for(i=1; i<100; i+=(i<10?1:5)) {
2235         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2236         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2237         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2238             char buf[MSG_SIZ];
2239             sprintf(buf, "%d", i);
2240             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2241         }
2242     }
2243     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2244     return TRUE;
2245 }
2246
2247 int SeekGraphClick(ClickType click, int x, int y, int moving)
2248 {
2249     static int lastDown = 0, displayed = 0, lastSecond;
2250     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2251         if(click == Release || moving) return FALSE;
2252         nrOfSeekAds = 0;
2253         soughtPending = TRUE;
2254         SendToICS(ics_prefix);
2255         SendToICS("sought\n"); // should this be "sought all"?
2256     } else { // issue challenge based on clicked ad
2257         int dist = 10000; int i, closest = 0, second = 0;
2258         for(i=0; i<nrOfSeekAds; i++) {
2259             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2260             if(d < dist) { dist = d; closest = i; }
2261             second += (d - zList[i] < 120); // count in-range ads
2262             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2263         }
2264         if(dist < 120) {
2265             char buf[MSG_SIZ];
2266             second = (second > 1);
2267             if(displayed != closest || second != lastSecond) {
2268                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2269                 lastSecond = second; displayed = closest;
2270             }
2271             if(click == Press) {
2272                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2273                 lastDown = closest;
2274                 return TRUE;
2275             } // on press 'hit', only show info
2276             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2277             sprintf(buf, "play %d\n", seekNrList[closest]);
2278             SendToICS(ics_prefix);
2279             SendToICS(buf);
2280             return TRUE; // let incoming board of started game pop down the graph
2281         } else if(click == Release) { // release 'miss' is ignored
2282             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2283             if(moving == 2) { // right up-click
2284                 nrOfSeekAds = 0; // refresh graph
2285                 soughtPending = TRUE;
2286                 SendToICS(ics_prefix);
2287                 SendToICS("sought\n"); // should this be "sought all"?
2288             }
2289             return TRUE;
2290         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2291         // press miss or release hit 'pop down' seek graph
2292         seekGraphUp = FALSE;
2293         DrawPosition(TRUE, NULL);
2294     }
2295     return TRUE;
2296 }
2297
2298 void
2299 read_from_ics(isr, closure, data, count, error)
2300      InputSourceRef isr;
2301      VOIDSTAR closure;
2302      char *data;
2303      int count;
2304      int error;
2305 {
2306 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2307 #define STARTED_NONE 0
2308 #define STARTED_MOVES 1
2309 #define STARTED_BOARD 2
2310 #define STARTED_OBSERVE 3
2311 #define STARTED_HOLDINGS 4
2312 #define STARTED_CHATTER 5
2313 #define STARTED_COMMENT 6
2314 #define STARTED_MOVES_NOHIDE 7
2315     
2316     static int started = STARTED_NONE;
2317     static char parse[20000];
2318     static int parse_pos = 0;
2319     static char buf[BUF_SIZE + 1];
2320     static int firstTime = TRUE, intfSet = FALSE;
2321     static ColorClass prevColor = ColorNormal;
2322     static int savingComment = FALSE;
2323     static int cmatch = 0; // continuation sequence match
2324     char *bp;
2325     char str[500];
2326     int i, oldi;
2327     int buf_len;
2328     int next_out;
2329     int tkind;
2330     int backup;    /* [DM] For zippy color lines */
2331     char *p;
2332     char talker[MSG_SIZ]; // [HGM] chat
2333     int channel;
2334
2335     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2336
2337     if (appData.debugMode) {
2338       if (!error) {
2339         fprintf(debugFP, "<ICS: ");
2340         show_bytes(debugFP, data, count);
2341         fprintf(debugFP, "\n");
2342       }
2343     }
2344
2345     if (appData.debugMode) { int f = forwardMostMove;
2346         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2347                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2348                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2349     }
2350     if (count > 0) {
2351         /* If last read ended with a partial line that we couldn't parse,
2352            prepend it to the new read and try again. */
2353         if (leftover_len > 0) {
2354             for (i=0; i<leftover_len; i++)
2355               buf[i] = buf[leftover_start + i];
2356         }
2357
2358     /* copy new characters into the buffer */
2359     bp = buf + leftover_len;
2360     buf_len=leftover_len;
2361     for (i=0; i<count; i++)
2362     {
2363         // ignore these
2364         if (data[i] == '\r')
2365             continue;
2366
2367         // join lines split by ICS?
2368         if (!appData.noJoin)
2369         {
2370             /*
2371                 Joining just consists of finding matches against the
2372                 continuation sequence, and discarding that sequence
2373                 if found instead of copying it.  So, until a match
2374                 fails, there's nothing to do since it might be the
2375                 complete sequence, and thus, something we don't want
2376                 copied.
2377             */
2378             if (data[i] == cont_seq[cmatch])
2379             {
2380                 cmatch++;
2381                 if (cmatch == strlen(cont_seq))
2382                 {
2383                     cmatch = 0; // complete match.  just reset the counter
2384
2385                     /*
2386                         it's possible for the ICS to not include the space
2387                         at the end of the last word, making our [correct]
2388                         join operation fuse two separate words.  the server
2389                         does this when the space occurs at the width setting.
2390                     */
2391                     if (!buf_len || buf[buf_len-1] != ' ')
2392                     {
2393                         *bp++ = ' ';
2394                         buf_len++;
2395                     }
2396                 }
2397                 continue;
2398             }
2399             else if (cmatch)
2400             {
2401                 /*
2402                     match failed, so we have to copy what matched before
2403                     falling through and copying this character.  In reality,
2404                     this will only ever be just the newline character, but
2405                     it doesn't hurt to be precise.
2406                 */
2407                 strncpy(bp, cont_seq, cmatch);
2408                 bp += cmatch;
2409                 buf_len += cmatch;
2410                 cmatch = 0;
2411             }
2412         }
2413
2414         // copy this char
2415         *bp++ = data[i];
2416         buf_len++;
2417     }
2418
2419         buf[buf_len] = NULLCHAR;
2420 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2421         next_out = 0;
2422         leftover_start = 0;
2423         
2424         i = 0;
2425         while (i < buf_len) {
2426             /* Deal with part of the TELNET option negotiation
2427                protocol.  We refuse to do anything beyond the
2428                defaults, except that we allow the WILL ECHO option,
2429                which ICS uses to turn off password echoing when we are
2430                directly connected to it.  We reject this option
2431                if localLineEditing mode is on (always on in xboard)
2432                and we are talking to port 23, which might be a real
2433                telnet server that will try to keep WILL ECHO on permanently.
2434              */
2435             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2436                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2437                 unsigned char option;
2438                 oldi = i;
2439                 switch ((unsigned char) buf[++i]) {
2440                   case TN_WILL:
2441                     if (appData.debugMode)
2442                       fprintf(debugFP, "\n<WILL ");
2443                     switch (option = (unsigned char) buf[++i]) {
2444                       case TN_ECHO:
2445                         if (appData.debugMode)
2446                           fprintf(debugFP, "ECHO ");
2447                         /* Reply only if this is a change, according
2448                            to the protocol rules. */
2449                         if (remoteEchoOption) break;
2450                         if (appData.localLineEditing &&
2451                             atoi(appData.icsPort) == TN_PORT) {
2452                             TelnetRequest(TN_DONT, TN_ECHO);
2453                         } else {
2454                             EchoOff();
2455                             TelnetRequest(TN_DO, TN_ECHO);
2456                             remoteEchoOption = TRUE;
2457                         }
2458                         break;
2459                       default:
2460                         if (appData.debugMode)
2461                           fprintf(debugFP, "%d ", option);
2462                         /* Whatever this is, we don't want it. */
2463                         TelnetRequest(TN_DONT, option);
2464                         break;
2465                     }
2466                     break;
2467                   case TN_WONT:
2468                     if (appData.debugMode)
2469                       fprintf(debugFP, "\n<WONT ");
2470                     switch (option = (unsigned char) buf[++i]) {
2471                       case TN_ECHO:
2472                         if (appData.debugMode)
2473                           fprintf(debugFP, "ECHO ");
2474                         /* Reply only if this is a change, according
2475                            to the protocol rules. */
2476                         if (!remoteEchoOption) break;
2477                         EchoOn();
2478                         TelnetRequest(TN_DONT, TN_ECHO);
2479                         remoteEchoOption = FALSE;
2480                         break;
2481                       default:
2482                         if (appData.debugMode)
2483                           fprintf(debugFP, "%d ", (unsigned char) option);
2484                         /* Whatever this is, it must already be turned
2485                            off, because we never agree to turn on
2486                            anything non-default, so according to the
2487                            protocol rules, we don't reply. */
2488                         break;
2489                     }
2490                     break;
2491                   case TN_DO:
2492                     if (appData.debugMode)
2493                       fprintf(debugFP, "\n<DO ");
2494                     switch (option = (unsigned char) buf[++i]) {
2495                       default:
2496                         /* Whatever this is, we refuse to do it. */
2497                         if (appData.debugMode)
2498                           fprintf(debugFP, "%d ", option);
2499                         TelnetRequest(TN_WONT, option);
2500                         break;
2501                     }
2502                     break;
2503                   case TN_DONT:
2504                     if (appData.debugMode)
2505                       fprintf(debugFP, "\n<DONT ");
2506                     switch (option = (unsigned char) buf[++i]) {
2507                       default:
2508                         if (appData.debugMode)
2509                           fprintf(debugFP, "%d ", option);
2510                         /* Whatever this is, we are already not doing
2511                            it, because we never agree to do anything
2512                            non-default, so according to the protocol
2513                            rules, we don't reply. */
2514                         break;
2515                     }
2516                     break;
2517                   case TN_IAC:
2518                     if (appData.debugMode)
2519                       fprintf(debugFP, "\n<IAC ");
2520                     /* Doubled IAC; pass it through */
2521                     i--;
2522                     break;
2523                   default:
2524                     if (appData.debugMode)
2525                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2526                     /* Drop all other telnet commands on the floor */
2527                     break;
2528                 }
2529                 if (oldi > next_out)
2530                   SendToPlayer(&buf[next_out], oldi - next_out);
2531                 if (++i > next_out)
2532                   next_out = i;
2533                 continue;
2534             }
2535                 
2536             /* OK, this at least will *usually* work */
2537             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2538                 loggedOn = TRUE;
2539             }
2540             
2541             if (loggedOn && !intfSet) {
2542                 if (ics_type == ICS_ICC) {
2543                   sprintf(str,
2544                           "/set-quietly interface %s\n/set-quietly style 12\n",
2545                           programVersion);
2546                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2547                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2548                 } else if (ics_type == ICS_CHESSNET) {
2549                   sprintf(str, "/style 12\n");
2550                 } else {
2551                   strcpy(str, "alias $ @\n$set interface ");
2552                   strcat(str, programVersion);
2553                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2554                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2555                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2556 #ifdef WIN32
2557                   strcat(str, "$iset nohighlight 1\n");
2558 #endif
2559                   strcat(str, "$iset lock 1\n$style 12\n");
2560                 }
2561                 SendToICS(str);
2562                 NotifyFrontendLogin();
2563                 intfSet = TRUE;
2564             }
2565
2566             if (started == STARTED_COMMENT) {
2567                 /* Accumulate characters in comment */
2568                 parse[parse_pos++] = buf[i];
2569                 if (buf[i] == '\n') {
2570                     parse[parse_pos] = NULLCHAR;
2571                     if(chattingPartner>=0) {
2572                         char mess[MSG_SIZ];
2573                         sprintf(mess, "%s%s", talker, parse);
2574                         OutputChatMessage(chattingPartner, mess);
2575                         chattingPartner = -1;
2576                         next_out = i+1; // [HGM] suppress printing in ICS window
2577                     } else
2578                     if(!suppressKibitz) // [HGM] kibitz
2579                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2580                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2581                         int nrDigit = 0, nrAlph = 0, j;
2582                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2583                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2584                         parse[parse_pos] = NULLCHAR;
2585                         // try to be smart: if it does not look like search info, it should go to
2586                         // ICS interaction window after all, not to engine-output window.
2587                         for(j=0; j<parse_pos; j++) { // count letters and digits
2588                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2589                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2590                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2591                         }
2592                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2593                             int depth=0; float score;
2594                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2595                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2596                                 pvInfoList[forwardMostMove-1].depth = depth;
2597                                 pvInfoList[forwardMostMove-1].score = 100*score;
2598                             }
2599                             OutputKibitz(suppressKibitz, parse);
2600                         } else {
2601                             char tmp[MSG_SIZ];
2602                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2603                             SendToPlayer(tmp, strlen(tmp));
2604                         }
2605                         next_out = i+1; // [HGM] suppress printing in ICS window
2606                     }
2607                     started = STARTED_NONE;
2608                 } else {
2609                     /* Don't match patterns against characters in comment */
2610                     i++;
2611                     continue;
2612                 }
2613             }
2614             if (started == STARTED_CHATTER) {
2615                 if (buf[i] != '\n') {
2616                     /* Don't match patterns against characters in chatter */
2617                     i++;
2618                     continue;
2619                 }
2620                 started = STARTED_NONE;
2621                 if(suppressKibitz) next_out = i+1;
2622             }
2623
2624             /* Kludge to deal with rcmd protocol */
2625             if (firstTime && looking_at(buf, &i, "\001*")) {
2626                 DisplayFatalError(&buf[1], 0, 1);
2627                 continue;
2628             } else {
2629                 firstTime = FALSE;
2630             }
2631
2632             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2633                 ics_type = ICS_ICC;
2634                 ics_prefix = "/";
2635                 if (appData.debugMode)
2636                   fprintf(debugFP, "ics_type %d\n", ics_type);
2637                 continue;
2638             }
2639             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2640                 ics_type = ICS_FICS;
2641                 ics_prefix = "$";
2642                 if (appData.debugMode)
2643                   fprintf(debugFP, "ics_type %d\n", ics_type);
2644                 continue;
2645             }
2646             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2647                 ics_type = ICS_CHESSNET;
2648                 ics_prefix = "/";
2649                 if (appData.debugMode)
2650                   fprintf(debugFP, "ics_type %d\n", ics_type);
2651                 continue;
2652             }
2653
2654             if (!loggedOn &&
2655                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2656                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2657                  looking_at(buf, &i, "will be \"*\""))) {
2658               strcpy(ics_handle, star_match[0]);
2659               continue;
2660             }
2661
2662             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2663               char buf[MSG_SIZ];
2664               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2665               DisplayIcsInteractionTitle(buf);
2666               have_set_title = TRUE;
2667             }
2668
2669             /* skip finger notes */
2670             if (started == STARTED_NONE &&
2671                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2672                  (buf[i] == '1' && buf[i+1] == '0')) &&
2673                 buf[i+2] == ':' && buf[i+3] == ' ') {
2674               started = STARTED_CHATTER;
2675               i += 3;
2676               continue;
2677             }
2678
2679             oldi = i;
2680             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2681             if(appData.seekGraph) {
2682                 if(soughtPending && MatchSoughtLine(buf+i)) {
2683                     i = strstr(buf+i, "rated") - buf;
2684                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2685                     next_out = leftover_start = i;
2686                     started = STARTED_CHATTER;
2687                     suppressKibitz = TRUE;
2688                     continue;
2689                 }
2690                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2691                         && looking_at(buf, &i, "* ads displayed")) {
2692                     soughtPending = FALSE;
2693                     seekGraphUp = TRUE;
2694                     DrawSeekGraph();
2695                     continue;
2696                 }
2697                 if(appData.autoRefresh) {
2698                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2699                         int s = (ics_type == ICS_ICC); // ICC format differs
2700                         if(seekGraphUp)
2701                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2702                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2703                         looking_at(buf, &i, "*% "); // eat prompt
2704                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2705                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2706                         next_out = i; // suppress
2707                         continue;
2708                     }
2709                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2710                         char *p = star_match[0];
2711                         while(*p) {
2712                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2713                             while(*p && *p++ != ' '); // next
2714                         }
2715                         looking_at(buf, &i, "*% "); // eat prompt
2716                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2717                         next_out = i;
2718                         continue;
2719                     }
2720                 }
2721             }
2722
2723             /* skip formula vars */
2724             if (started == STARTED_NONE &&
2725                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2726               started = STARTED_CHATTER;
2727               i += 3;
2728               continue;
2729             }
2730
2731             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2732             if (appData.autoKibitz && started == STARTED_NONE && 
2733                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2734                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2735                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2736                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2737                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2738                         suppressKibitz = TRUE;
2739                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2740                         next_out = i;
2741                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2742                                 && (gameMode == IcsPlayingWhite)) ||
2743                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2744                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2745                             started = STARTED_CHATTER; // own kibitz we simply discard
2746                         else {
2747                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2748                             parse_pos = 0; parse[0] = NULLCHAR;
2749                             savingComment = TRUE;
2750                             suppressKibitz = gameMode != IcsObserving ? 2 :
2751                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2752                         } 
2753                         continue;
2754                 } else
2755                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2756                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2757                          && atoi(star_match[0])) {
2758                     // suppress the acknowledgements of our own autoKibitz
2759                     char *p;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2762                     SendToPlayer(star_match[0], strlen(star_match[0]));
2763                     if(looking_at(buf, &i, "*% ")) // eat prompt
2764                         suppressKibitz = FALSE;
2765                     next_out = i;
2766                     continue;
2767                 }
2768             } // [HGM] kibitz: end of patch
2769
2770             // [HGM] chat: intercept tells by users for which we have an open chat window
2771             channel = -1;
2772             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2773                                            looking_at(buf, &i, "* whispers:") ||
2774                                            looking_at(buf, &i, "* kibitzes:") ||
2775                                            looking_at(buf, &i, "* shouts:") ||
2776                                            looking_at(buf, &i, "* c-shouts:") ||
2777                                            looking_at(buf, &i, "--> * ") ||
2778                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2779                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2781                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2782                 int p;
2783                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2784                 chattingPartner = -1;
2785
2786                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2787                 for(p=0; p<MAX_CHAT; p++) {
2788                     if(channel == atoi(chatPartner[p])) {
2789                     talker[0] = '['; strcat(talker, "] ");
2790                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2791                     chattingPartner = p; break;
2792                     }
2793                 } else
2794                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2795                 for(p=0; p<MAX_CHAT; p++) {
2796                     if(!strcmp("kibitzes", chatPartner[p])) {
2797                         talker[0] = '['; strcat(talker, "] ");
2798                         chattingPartner = p; break;
2799                     }
2800                 } else
2801                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2802                 for(p=0; p<MAX_CHAT; p++) {
2803                     if(!strcmp("whispers", chatPartner[p])) {
2804                         talker[0] = '['; strcat(talker, "] ");
2805                         chattingPartner = p; break;
2806                     }
2807                 } else
2808                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2809                   if(buf[i-8] == '-' && buf[i-3] == 't')
2810                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2811                     if(!strcmp("c-shouts", chatPartner[p])) {
2812                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2813                         chattingPartner = p; break;
2814                     }
2815                   }
2816                   if(chattingPartner < 0)
2817                   for(p=0; p<MAX_CHAT; p++) {
2818                     if(!strcmp("shouts", chatPartner[p])) {
2819                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2820                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2821                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2822                         chattingPartner = p; break;
2823                     }
2824                   }
2825                 }
2826                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2827                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2828                     talker[0] = 0; Colorize(ColorTell, FALSE);
2829                     chattingPartner = p; break;
2830                 }
2831                 if(chattingPartner<0) i = oldi; else {
2832                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2833                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2834                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835                     started = STARTED_COMMENT;
2836                     parse_pos = 0; parse[0] = NULLCHAR;
2837                     savingComment = 3 + chattingPartner; // counts as TRUE
2838                     suppressKibitz = TRUE;
2839                     continue;
2840                 }
2841             } // [HGM] chat: end of patch
2842
2843             if (appData.zippyTalk || appData.zippyPlay) {
2844                 /* [DM] Backup address for color zippy lines */
2845                 backup = i;
2846 #if ZIPPY
2847                if (loggedOn == TRUE)
2848                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2849                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2850 #endif
2851             } // [DM] 'else { ' deleted
2852                 if (
2853                     /* Regular tells and says */
2854                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2855                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2856                     looking_at(buf, &i, "* says: ") ||
2857                     /* Don't color "message" or "messages" output */
2858                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2859                     looking_at(buf, &i, "*. * at *:*: ") ||
2860                     looking_at(buf, &i, "--* (*:*): ") ||
2861                     /* Message notifications (same color as tells) */
2862                     looking_at(buf, &i, "* has left a message ") ||
2863                     looking_at(buf, &i, "* just sent you a message:\n") ||
2864                     /* Whispers and kibitzes */
2865                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2866                     looking_at(buf, &i, "* kibitzes: ") ||
2867                     /* Channel tells */
2868                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2869
2870                   if (tkind == 1 && strchr(star_match[0], ':')) {
2871                       /* Avoid "tells you:" spoofs in channels */
2872                      tkind = 3;
2873                   }
2874                   if (star_match[0][0] == NULLCHAR ||
2875                       strchr(star_match[0], ' ') ||
2876                       (tkind == 3 && strchr(star_match[1], ' '))) {
2877                     /* Reject bogus matches */
2878                     i = oldi;
2879                   } else {
2880                     if (appData.colorize) {
2881                       if (oldi > next_out) {
2882                         SendToPlayer(&buf[next_out], oldi - next_out);
2883                         next_out = oldi;
2884                       }
2885                       switch (tkind) {
2886                       case 1:
2887                         Colorize(ColorTell, FALSE);
2888                         curColor = ColorTell;
2889                         break;
2890                       case 2:
2891                         Colorize(ColorKibitz, FALSE);
2892                         curColor = ColorKibitz;
2893                         break;
2894                       case 3:
2895                         p = strrchr(star_match[1], '(');
2896                         if (p == NULL) {
2897                           p = star_match[1];
2898                         } else {
2899                           p++;
2900                         }
2901                         if (atoi(p) == 1) {
2902                           Colorize(ColorChannel1, FALSE);
2903                           curColor = ColorChannel1;
2904                         } else {
2905                           Colorize(ColorChannel, FALSE);
2906                           curColor = ColorChannel;
2907                         }
2908                         break;
2909                       case 5:
2910                         curColor = ColorNormal;
2911                         break;
2912                       }
2913                     }
2914                     if (started == STARTED_NONE && appData.autoComment &&
2915                         (gameMode == IcsObserving ||
2916                          gameMode == IcsPlayingWhite ||
2917                          gameMode == IcsPlayingBlack)) {
2918                       parse_pos = i - oldi;
2919                       memcpy(parse, &buf[oldi], parse_pos);
2920                       parse[parse_pos] = NULLCHAR;
2921                       started = STARTED_COMMENT;
2922                       savingComment = TRUE;
2923                     } else {
2924                       started = STARTED_CHATTER;
2925                       savingComment = FALSE;
2926                     }
2927                     loggedOn = TRUE;
2928                     continue;
2929                   }
2930                 }
2931
2932                 if (looking_at(buf, &i, "* s-shouts: ") ||
2933                     looking_at(buf, &i, "* c-shouts: ")) {
2934                     if (appData.colorize) {
2935                         if (oldi > next_out) {
2936                             SendToPlayer(&buf[next_out], oldi - next_out);
2937                             next_out = oldi;
2938                         }
2939                         Colorize(ColorSShout, FALSE);
2940                         curColor = ColorSShout;
2941                     }
2942                     loggedOn = TRUE;
2943                     started = STARTED_CHATTER;
2944                     continue;
2945                 }
2946
2947                 if (looking_at(buf, &i, "--->")) {
2948                     loggedOn = TRUE;
2949                     continue;
2950                 }
2951
2952                 if (looking_at(buf, &i, "* shouts: ") ||
2953                     looking_at(buf, &i, "--> ")) {
2954                     if (appData.colorize) {
2955                         if (oldi > next_out) {
2956                             SendToPlayer(&buf[next_out], oldi - next_out);
2957                             next_out = oldi;
2958                         }
2959                         Colorize(ColorShout, FALSE);
2960                         curColor = ColorShout;
2961                     }
2962                     loggedOn = TRUE;
2963                     started = STARTED_CHATTER;
2964                     continue;
2965                 }
2966
2967                 if (looking_at( buf, &i, "Challenge:")) {
2968                     if (appData.colorize) {
2969                         if (oldi > next_out) {
2970                             SendToPlayer(&buf[next_out], oldi - next_out);
2971                             next_out = oldi;
2972                         }
2973                         Colorize(ColorChallenge, FALSE);
2974                         curColor = ColorChallenge;
2975                     }
2976                     loggedOn = TRUE;
2977                     continue;
2978                 }
2979
2980                 if (looking_at(buf, &i, "* offers you") ||
2981                     looking_at(buf, &i, "* offers to be") ||
2982                     looking_at(buf, &i, "* would like to") ||
2983                     looking_at(buf, &i, "* requests to") ||
2984                     looking_at(buf, &i, "Your opponent offers") ||
2985                     looking_at(buf, &i, "Your opponent requests")) {
2986
2987                     if (appData.colorize) {
2988                         if (oldi > next_out) {
2989                             SendToPlayer(&buf[next_out], oldi - next_out);
2990                             next_out = oldi;
2991                         }
2992                         Colorize(ColorRequest, FALSE);
2993                         curColor = ColorRequest;
2994                     }
2995                     continue;
2996                 }
2997
2998                 if (looking_at(buf, &i, "* (*) seeking")) {
2999                     if (appData.colorize) {
3000                         if (oldi > next_out) {
3001                             SendToPlayer(&buf[next_out], oldi - next_out);
3002                             next_out = oldi;
3003                         }
3004                         Colorize(ColorSeek, FALSE);
3005                         curColor = ColorSeek;
3006                     }
3007                     continue;
3008             }
3009
3010             if (looking_at(buf, &i, "\\   ")) {
3011                 if (prevColor != ColorNormal) {
3012                     if (oldi > next_out) {
3013                         SendToPlayer(&buf[next_out], oldi - next_out);
3014                         next_out = oldi;
3015                     }
3016                     Colorize(prevColor, TRUE);
3017                     curColor = prevColor;
3018                 }
3019                 if (savingComment) {
3020                     parse_pos = i - oldi;
3021                     memcpy(parse, &buf[oldi], parse_pos);
3022                     parse[parse_pos] = NULLCHAR;
3023                     started = STARTED_COMMENT;
3024                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3025                         chattingPartner = savingComment - 3; // kludge to remember the box
3026                 } else {
3027                     started = STARTED_CHATTER;
3028                 }
3029                 continue;
3030             }
3031
3032             if (looking_at(buf, &i, "Black Strength :") ||
3033                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3034                 looking_at(buf, &i, "<10>") ||
3035                 looking_at(buf, &i, "#@#")) {
3036                 /* Wrong board style */
3037                 loggedOn = TRUE;
3038                 SendToICS(ics_prefix);
3039                 SendToICS("set style 12\n");
3040                 SendToICS(ics_prefix);
3041                 SendToICS("refresh\n");
3042                 continue;
3043             }
3044             
3045             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3046                 ICSInitScript();
3047                 have_sent_ICS_logon = 1;
3048                 continue;
3049             }
3050               
3051             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3052                 (looking_at(buf, &i, "\n<12> ") ||
3053                  looking_at(buf, &i, "<12> "))) {
3054                 loggedOn = TRUE;
3055                 if (oldi > next_out) {
3056                     SendToPlayer(&buf[next_out], oldi - next_out);
3057                 }
3058                 next_out = i;
3059                 started = STARTED_BOARD;
3060                 parse_pos = 0;
3061                 continue;
3062             }
3063
3064             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3065                 looking_at(buf, &i, "<b1> ")) {
3066                 if (oldi > next_out) {
3067                     SendToPlayer(&buf[next_out], oldi - next_out);
3068                 }
3069                 next_out = i;
3070                 started = STARTED_HOLDINGS;
3071                 parse_pos = 0;
3072                 continue;
3073             }
3074
3075             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3076                 loggedOn = TRUE;
3077                 /* Header for a move list -- first line */
3078
3079                 switch (ics_getting_history) {
3080                   case H_FALSE:
3081                     switch (gameMode) {
3082                       case IcsIdle:
3083                       case BeginningOfGame:
3084                         /* User typed "moves" or "oldmoves" while we
3085                            were idle.  Pretend we asked for these
3086                            moves and soak them up so user can step
3087                            through them and/or save them.
3088                            */
3089                         Reset(FALSE, TRUE);
3090                         gameMode = IcsObserving;
3091                         ModeHighlight();
3092                         ics_gamenum = -1;
3093                         ics_getting_history = H_GOT_UNREQ_HEADER;
3094                         break;
3095                       case EditGame: /*?*/
3096                       case EditPosition: /*?*/
3097                         /* Should above feature work in these modes too? */
3098                         /* For now it doesn't */
3099                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3100                         break;
3101                       default:
3102                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3103                         break;
3104                     }
3105                     break;
3106                   case H_REQUESTED:
3107                     /* Is this the right one? */
3108                     if (gameInfo.white && gameInfo.black &&
3109                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3110                         strcmp(gameInfo.black, star_match[2]) == 0) {
3111                         /* All is well */
3112                         ics_getting_history = H_GOT_REQ_HEADER;
3113                     }
3114                     break;
3115                   case H_GOT_REQ_HEADER:
3116                   case H_GOT_UNREQ_HEADER:
3117                   case H_GOT_UNWANTED_HEADER:
3118                   case H_GETTING_MOVES:
3119                     /* Should not happen */
3120                     DisplayError(_("Error gathering move list: two headers"), 0);
3121                     ics_getting_history = H_FALSE;
3122                     break;
3123                 }
3124
3125                 /* Save player ratings into gameInfo if needed */
3126                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3127                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3128                     (gameInfo.whiteRating == -1 ||
3129                      gameInfo.blackRating == -1)) {
3130
3131                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3132                     gameInfo.blackRating = string_to_rating(star_match[3]);
3133                     if (appData.debugMode)
3134                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3135                               gameInfo.whiteRating, gameInfo.blackRating);
3136                 }
3137                 continue;
3138             }
3139
3140             if (looking_at(buf, &i,
3141               "* * match, initial time: * minute*, increment: * second")) {
3142                 /* Header for a move list -- second line */
3143                 /* Initial board will follow if this is a wild game */
3144                 if (gameInfo.event != NULL) free(gameInfo.event);
3145                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3146                 gameInfo.event = StrSave(str);
3147                 /* [HGM] we switched variant. Translate boards if needed. */
3148                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3149                 continue;
3150             }
3151
3152             if (looking_at(buf, &i, "Move  ")) {
3153                 /* Beginning of a move list */
3154                 switch (ics_getting_history) {
3155                   case H_FALSE:
3156                     /* Normally should not happen */
3157                     /* Maybe user hit reset while we were parsing */
3158                     break;
3159                   case H_REQUESTED:
3160                     /* Happens if we are ignoring a move list that is not
3161                      * the one we just requested.  Common if the user
3162                      * tries to observe two games without turning off
3163                      * getMoveList */
3164                     break;
3165                   case H_GETTING_MOVES:
3166                     /* Should not happen */
3167                     DisplayError(_("Error gathering move list: nested"), 0);
3168                     ics_getting_history = H_FALSE;
3169                     break;
3170                   case H_GOT_REQ_HEADER:
3171                     ics_getting_history = H_GETTING_MOVES;
3172                     started = STARTED_MOVES;
3173                     parse_pos = 0;
3174                     if (oldi > next_out) {
3175                         SendToPlayer(&buf[next_out], oldi - next_out);
3176                     }
3177                     break;
3178                   case H_GOT_UNREQ_HEADER:
3179                     ics_getting_history = H_GETTING_MOVES;
3180                     started = STARTED_MOVES_NOHIDE;
3181                     parse_pos = 0;
3182                     break;
3183                   case H_GOT_UNWANTED_HEADER:
3184                     ics_getting_history = H_FALSE;
3185                     break;
3186                 }
3187                 continue;
3188             }                           
3189             
3190             if (looking_at(buf, &i, "% ") ||
3191                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3192                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3193                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3194                     soughtPending = FALSE;
3195                     seekGraphUp = TRUE;
3196                     DrawSeekGraph();
3197                 }
3198                 if(suppressKibitz) next_out = i;
3199                 savingComment = FALSE;
3200                 suppressKibitz = 0;
3201                 switch (started) {
3202                   case STARTED_MOVES:
3203                   case STARTED_MOVES_NOHIDE:
3204                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3205                     parse[parse_pos + i - oldi] = NULLCHAR;
3206                     ParseGameHistory(parse);
3207 #if ZIPPY
3208                     if (appData.zippyPlay && first.initDone) {
3209                         FeedMovesToProgram(&first, forwardMostMove);
3210                         if (gameMode == IcsPlayingWhite) {
3211                             if (WhiteOnMove(forwardMostMove)) {
3212                                 if (first.sendTime) {
3213                                   if (first.useColors) {
3214                                     SendToProgram("black\n", &first); 
3215                                   }
3216                                   SendTimeRemaining(&first, TRUE);
3217                                 }
3218                                 if (first.useColors) {
3219                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3220                                 }
3221                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3222                                 first.maybeThinking = TRUE;
3223                             } else {
3224                                 if (first.usePlayother) {
3225                                   if (first.sendTime) {
3226                                     SendTimeRemaining(&first, TRUE);
3227                                   }
3228                                   SendToProgram("playother\n", &first);
3229                                   firstMove = FALSE;
3230                                 } else {
3231                                   firstMove = TRUE;
3232                                 }
3233                             }
3234                         } else if (gameMode == IcsPlayingBlack) {
3235                             if (!WhiteOnMove(forwardMostMove)) {
3236                                 if (first.sendTime) {
3237                                   if (first.useColors) {
3238                                     SendToProgram("white\n", &first);
3239                                   }
3240                                   SendTimeRemaining(&first, FALSE);
3241                                 }
3242                                 if (first.useColors) {
3243                                   SendToProgram("black\n", &first);
3244                                 }
3245                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3246                                 first.maybeThinking = TRUE;
3247                             } else {
3248                                 if (first.usePlayother) {
3249                                   if (first.sendTime) {
3250                                     SendTimeRemaining(&first, FALSE);
3251                                   }
3252                                   SendToProgram("playother\n", &first);
3253                                   firstMove = FALSE;
3254                                 } else {
3255                                   firstMove = TRUE;
3256                                 }
3257                             }
3258                         }                       
3259                     }
3260 #endif
3261                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3262                         /* Moves came from oldmoves or moves command
3263                            while we weren't doing anything else.
3264                            */
3265                         currentMove = forwardMostMove;
3266                         ClearHighlights();/*!!could figure this out*/
3267                         flipView = appData.flipView;
3268                         DrawPosition(TRUE, boards[currentMove]);
3269                         DisplayBothClocks();
3270                         sprintf(str, "%s vs. %s",
3271                                 gameInfo.white, gameInfo.black);
3272                         DisplayTitle(str);
3273                         gameMode = IcsIdle;
3274                     } else {
3275                         /* Moves were history of an active game */
3276                         if (gameInfo.resultDetails != NULL) {
3277                             free(gameInfo.resultDetails);
3278                             gameInfo.resultDetails = NULL;
3279                         }
3280                     }
3281                     HistorySet(parseList, backwardMostMove,
3282                                forwardMostMove, currentMove-1);
3283                     DisplayMove(currentMove - 1);
3284                     if (started == STARTED_MOVES) next_out = i;
3285                     started = STARTED_NONE;
3286                     ics_getting_history = H_FALSE;
3287                     break;
3288
3289                   case STARTED_OBSERVE:
3290                     started = STARTED_NONE;
3291                     SendToICS(ics_prefix);
3292                     SendToICS("refresh\n");
3293                     break;
3294
3295                   default:
3296                     break;
3297                 }
3298                 if(bookHit) { // [HGM] book: simulate book reply
3299                     static char bookMove[MSG_SIZ]; // a bit generous?
3300
3301                     programStats.nodes = programStats.depth = programStats.time = 
3302                     programStats.score = programStats.got_only_move = 0;
3303                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3304
3305                     strcpy(bookMove, "move ");
3306                     strcat(bookMove, bookHit);
3307                     HandleMachineMove(bookMove, &first);
3308                 }
3309                 continue;
3310             }
3311             
3312             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3313                  started == STARTED_HOLDINGS ||
3314                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3315                 /* Accumulate characters in move list or board */
3316                 parse[parse_pos++] = buf[i];
3317             }
3318             
3319             /* Start of game messages.  Mostly we detect start of game
3320                when the first board image arrives.  On some versions
3321                of the ICS, though, we need to do a "refresh" after starting
3322                to observe in order to get the current board right away. */
3323             if (looking_at(buf, &i, "Adding game * to observation list")) {
3324                 started = STARTED_OBSERVE;
3325                 continue;
3326             }
3327
3328             /* Handle auto-observe */
3329             if (appData.autoObserve &&
3330                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3331                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3332                 char *player;
3333                 /* Choose the player that was highlighted, if any. */
3334                 if (star_match[0][0] == '\033' ||
3335                     star_match[1][0] != '\033') {
3336                     player = star_match[0];
3337                 } else {
3338                     player = star_match[2];
3339                 }
3340                 sprintf(str, "%sobserve %s\n",
3341                         ics_prefix, StripHighlightAndTitle(player));
3342                 SendToICS(str);
3343
3344                 /* Save ratings from notify string */
3345                 strcpy(player1Name, star_match[0]);
3346                 player1Rating = string_to_rating(star_match[1]);
3347                 strcpy(player2Name, star_match[2]);
3348                 player2Rating = string_to_rating(star_match[3]);
3349
3350                 if (appData.debugMode)
3351                   fprintf(debugFP, 
3352                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3353                           player1Name, player1Rating,
3354                           player2Name, player2Rating);
3355
3356                 continue;
3357             }
3358
3359             /* Deal with automatic examine mode after a game,
3360                and with IcsObserving -> IcsExamining transition */
3361             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3362                 looking_at(buf, &i, "has made you an examiner of game *")) {
3363
3364                 int gamenum = atoi(star_match[0]);
3365                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3366                     gamenum == ics_gamenum) {
3367                     /* We were already playing or observing this game;
3368                        no need to refetch history */
3369                     gameMode = IcsExamining;
3370                     if (pausing) {
3371                         pauseExamForwardMostMove = forwardMostMove;
3372                     } else if (currentMove < forwardMostMove) {
3373                         ForwardInner(forwardMostMove);
3374                     }
3375                 } else {
3376                     /* I don't think this case really can happen */
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                 }
3380                 continue;
3381             }    
3382             
3383             /* Error messages */
3384 //          if (ics_user_moved) {
3385             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3386                 if (looking_at(buf, &i, "Illegal move") ||
3387                     looking_at(buf, &i, "Not a legal move") ||
3388                     looking_at(buf, &i, "Your king is in check") ||
3389                     looking_at(buf, &i, "It isn't your turn") ||
3390                     looking_at(buf, &i, "It is not your move")) {
3391                     /* Illegal move */
3392                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3393                         currentMove = forwardMostMove-1;
3394                         DisplayMove(currentMove - 1); /* before DMError */
3395                         DrawPosition(FALSE, boards[currentMove]);
3396                         SwitchClocks(forwardMostMove-1); // [HGM] race
3397                         DisplayBothClocks();
3398                     }
3399                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3400                     ics_user_moved = 0;
3401                     continue;
3402                 }
3403             }
3404
3405             if (looking_at(buf, &i, "still have time") ||
3406                 looking_at(buf, &i, "not out of time") ||
3407                 looking_at(buf, &i, "either player is out of time") ||
3408                 looking_at(buf, &i, "has timeseal; checking")) {
3409                 /* We must have called his flag a little too soon */
3410                 whiteFlag = blackFlag = FALSE;
3411                 continue;
3412             }
3413
3414             if (looking_at(buf, &i, "added * seconds to") ||
3415                 looking_at(buf, &i, "seconds were added to")) {
3416                 /* Update the clocks */
3417                 SendToICS(ics_prefix);
3418                 SendToICS("refresh\n");
3419                 continue;
3420             }
3421
3422             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3423                 ics_clock_paused = TRUE;
3424                 StopClocks();
3425                 continue;
3426             }
3427
3428             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3429                 ics_clock_paused = FALSE;
3430                 StartClocks();
3431                 continue;
3432             }
3433
3434             /* Grab player ratings from the Creating: message.
3435                Note we have to check for the special case when
3436                the ICS inserts things like [white] or [black]. */
3437             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3438                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3439                 /* star_matches:
3440                    0    player 1 name (not necessarily white)
3441                    1    player 1 rating
3442                    2    empty, white, or black (IGNORED)
3443                    3    player 2 name (not necessarily black)
3444                    4    player 2 rating
3445                    
3446                    The names/ratings are sorted out when the game
3447                    actually starts (below).
3448                 */
3449                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3450                 player1Rating = string_to_rating(star_match[1]);
3451                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3452                 player2Rating = string_to_rating(star_match[4]);
3453
3454                 if (appData.debugMode)
3455                   fprintf(debugFP, 
3456                           "Ratings from 'Creating:' %s %d, %s %d\n",
3457                           player1Name, player1Rating,
3458                           player2Name, player2Rating);
3459
3460                 continue;
3461             }
3462             
3463             /* Improved generic start/end-of-game messages */
3464             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3465                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3466                 /* If tkind == 0: */
3467                 /* star_match[0] is the game number */
3468                 /*           [1] is the white player's name */
3469                 /*           [2] is the black player's name */
3470                 /* For end-of-game: */
3471                 /*           [3] is the reason for the game end */
3472                 /*           [4] is a PGN end game-token, preceded by " " */
3473                 /* For start-of-game: */
3474                 /*           [3] begins with "Creating" or "Continuing" */
3475                 /*           [4] is " *" or empty (don't care). */
3476                 int gamenum = atoi(star_match[0]);
3477                 char *whitename, *blackname, *why, *endtoken;
3478                 ChessMove endtype = (ChessMove) 0;
3479
3480                 if (tkind == 0) {
3481                   whitename = star_match[1];
3482                   blackname = star_match[2];
3483                   why = star_match[3];
3484                   endtoken = star_match[4];
3485                 } else {
3486                   whitename = star_match[1];
3487                   blackname = star_match[3];
3488                   why = star_match[5];
3489                   endtoken = star_match[6];
3490                 }
3491
3492                 /* Game start messages */
3493                 if (strncmp(why, "Creating ", 9) == 0 ||
3494                     strncmp(why, "Continuing ", 11) == 0) {
3495                     gs_gamenum = gamenum;
3496                     strcpy(gs_kind, strchr(why, ' ') + 1);
3497                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3498 #if ZIPPY
3499                     if (appData.zippyPlay) {
3500                         ZippyGameStart(whitename, blackname);
3501                     }
3502 #endif /*ZIPPY*/
3503                     partnerBoardValid = FALSE; // [HGM] bughouse
3504                     continue;
3505                 }
3506
3507                 /* Game end messages */
3508                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3509                     ics_gamenum != gamenum) {
3510                     continue;
3511                 }
3512                 while (endtoken[0] == ' ') endtoken++;
3513                 switch (endtoken[0]) {
3514                   case '*':
3515                   default:
3516                     endtype = GameUnfinished;
3517                     break;
3518                   case '0':
3519                     endtype = BlackWins;
3520                     break;
3521                   case '1':
3522                     if (endtoken[1] == '/')
3523                       endtype = GameIsDrawn;
3524                     else
3525                       endtype = WhiteWins;
3526                     break;
3527                 }
3528                 GameEnds(endtype, why, GE_ICS);
3529 #if ZIPPY
3530                 if (appData.zippyPlay && first.initDone) {
3531                     ZippyGameEnd(endtype, why);
3532                     if (first.pr == NULL) {
3533                       /* Start the next process early so that we'll
3534                          be ready for the next challenge */
3535                       StartChessProgram(&first);
3536                     }
3537                     /* Send "new" early, in case this command takes
3538                        a long time to finish, so that we'll be ready
3539                        for the next challenge. */
3540                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3541                     Reset(TRUE, TRUE);
3542                 }
3543 #endif /*ZIPPY*/
3544                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3545                 continue;
3546             }
3547
3548             if (looking_at(buf, &i, "Removing game * from observation") ||
3549                 looking_at(buf, &i, "no longer observing game *") ||
3550                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3551                 if (gameMode == IcsObserving &&
3552                     atoi(star_match[0]) == ics_gamenum)
3553                   {
3554                       /* icsEngineAnalyze */
3555                       if (appData.icsEngineAnalyze) {
3556                             ExitAnalyzeMode();
3557                             ModeHighlight();
3558                       }
3559                       StopClocks();
3560                       gameMode = IcsIdle;
3561                       ics_gamenum = -1;
3562                       ics_user_moved = FALSE;
3563                   }
3564                 continue;
3565             }
3566
3567             if (looking_at(buf, &i, "no longer examining game *")) {
3568                 if (gameMode == IcsExamining &&
3569                     atoi(star_match[0]) == ics_gamenum)
3570                   {
3571                       gameMode = IcsIdle;
3572                       ics_gamenum = -1;
3573                       ics_user_moved = FALSE;
3574                   }
3575                 continue;
3576             }
3577
3578             /* Advance leftover_start past any newlines we find,
3579                so only partial lines can get reparsed */
3580             if (looking_at(buf, &i, "\n")) {
3581                 prevColor = curColor;
3582                 if (curColor != ColorNormal) {
3583                     if (oldi > next_out) {
3584                         SendToPlayer(&buf[next_out], oldi - next_out);
3585                         next_out = oldi;
3586                     }
3587                     Colorize(ColorNormal, FALSE);
3588                     curColor = ColorNormal;
3589                 }
3590                 if (started == STARTED_BOARD) {
3591                     started = STARTED_NONE;
3592                     parse[parse_pos] = NULLCHAR;
3593                     ParseBoard12(parse);
3594                     ics_user_moved = 0;
3595
3596                     /* Send premove here */
3597                     if (appData.premove) {
3598                       char str[MSG_SIZ];
3599                       if (currentMove == 0 &&
3600                           gameMode == IcsPlayingWhite &&
3601                           appData.premoveWhite) {
3602                         sprintf(str, "%s\n", appData.premoveWhiteText);
3603                         if (appData.debugMode)
3604                           fprintf(debugFP, "Sending premove:\n");
3605                         SendToICS(str);
3606                       } else if (currentMove == 1 &&
3607                                  gameMode == IcsPlayingBlack &&
3608                                  appData.premoveBlack) {
3609                         sprintf(str, "%s\n", appData.premoveBlackText);
3610                         if (appData.debugMode)
3611                           fprintf(debugFP, "Sending premove:\n");
3612                         SendToICS(str);
3613                       } else if (gotPremove) {
3614                         gotPremove = 0;
3615                         ClearPremoveHighlights();
3616                         if (appData.debugMode)
3617                           fprintf(debugFP, "Sending premove:\n");
3618                           UserMoveEvent(premoveFromX, premoveFromY, 
3619                                         premoveToX, premoveToY, 
3620                                         premovePromoChar);
3621                       }
3622                     }
3623
3624                     /* Usually suppress following prompt */
3625                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3626                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3627                         if (looking_at(buf, &i, "*% ")) {
3628                             savingComment = FALSE;
3629                             suppressKibitz = 0;
3630                         }
3631                     }
3632                     next_out = i;
3633                 } else if (started == STARTED_HOLDINGS) {
3634                     int gamenum;
3635                     char new_piece[MSG_SIZ];
3636                     started = STARTED_NONE;
3637                     parse[parse_pos] = NULLCHAR;
3638                     if (appData.debugMode)
3639                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3640                                                         parse, currentMove);
3641                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3642                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3643                         if (gameInfo.variant == VariantNormal) {
3644                           /* [HGM] We seem to switch variant during a game!
3645                            * Presumably no holdings were displayed, so we have
3646                            * to move the position two files to the right to
3647                            * create room for them!
3648                            */
3649                           VariantClass newVariant;
3650                           switch(gameInfo.boardWidth) { // base guess on board width
3651                                 case 9:  newVariant = VariantShogi; break;
3652                                 case 10: newVariant = VariantGreat; break;
3653                                 default: newVariant = VariantCrazyhouse; break;
3654                           }
3655                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3656                           /* Get a move list just to see the header, which
3657                              will tell us whether this is really bug or zh */
3658                           if (ics_getting_history == H_FALSE) {
3659                             ics_getting_history = H_REQUESTED;
3660                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3661                             SendToICS(str);
3662                           }
3663                         }
3664                         new_piece[0] = NULLCHAR;
3665                         sscanf(parse, "game %d white [%s black [%s <- %s",
3666                                &gamenum, white_holding, black_holding,
3667                                new_piece);
3668                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3669                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3670                         /* [HGM] copy holdings to board holdings area */
3671                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3672                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3673                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3674 #if ZIPPY
3675                         if (appData.zippyPlay && first.initDone) {
3676                             ZippyHoldings(white_holding, black_holding,
3677                                           new_piece);
3678                         }
3679 #endif /*ZIPPY*/
3680                         if (tinyLayout || smallLayout) {
3681                             char wh[16], bh[16];
3682                             PackHolding(wh, white_holding);
3683                             PackHolding(bh, black_holding);
3684                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3685                                     gameInfo.white, gameInfo.black);
3686                         } else {
3687                             sprintf(str, "%s [%s] vs. %s [%s]",
3688                                     gameInfo.white, white_holding,
3689                                     gameInfo.black, black_holding);
3690                         }
3691                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3692                         DrawPosition(FALSE, boards[currentMove]);
3693                         DisplayTitle(str);
3694                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3695                         sscanf(parse, "game %d white [%s black [%s <- %s",
3696                                &gamenum, white_holding, black_holding,
3697                                new_piece);
3698                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3699                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3700                         /* [HGM] copy holdings to partner-board holdings area */
3701                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3702                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3703                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3704                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3705                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3706                       }
3707                     }
3708                     /* Suppress following prompt */
3709                     if (looking_at(buf, &i, "*% ")) {
3710                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3711                         savingComment = FALSE;
3712                         suppressKibitz = 0;
3713                     }
3714                     next_out = i;
3715                 }
3716                 continue;
3717             }
3718
3719             i++;                /* skip unparsed character and loop back */
3720         }
3721         
3722         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3723 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3724 //          SendToPlayer(&buf[next_out], i - next_out);
3725             started != STARTED_HOLDINGS && leftover_start > next_out) {
3726             SendToPlayer(&buf[next_out], leftover_start - next_out);
3727             next_out = i;
3728         }
3729         
3730         leftover_len = buf_len - leftover_start;
3731         /* if buffer ends with something we couldn't parse,
3732            reparse it after appending the next read */
3733         
3734     } else if (count == 0) {
3735         RemoveInputSource(isr);
3736         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3737     } else {
3738         DisplayFatalError(_("Error reading from ICS"), error, 1);
3739     }
3740 }
3741
3742
3743 /* Board style 12 looks like this:
3744    
3745    <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
3746    
3747  * The "<12> " is stripped before it gets to this routine.  The two
3748  * trailing 0's (flip state and clock ticking) are later addition, and
3749  * some chess servers may not have them, or may have only the first.
3750  * Additional trailing fields may be added in the future.  
3751  */
3752
3753 #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"
3754
3755 #define RELATION_OBSERVING_PLAYED    0
3756 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3757 #define RELATION_PLAYING_MYMOVE      1
3758 #define RELATION_PLAYING_NOTMYMOVE  -1
3759 #define RELATION_EXAMINING           2
3760 #define RELATION_ISOLATED_BOARD     -3
3761 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3762
3763 void
3764 ParseBoard12(string)
3765      char *string;
3766
3767     GameMode newGameMode;
3768     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3769     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3770     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3771     char to_play, board_chars[200];
3772     char move_str[500], str[500], elapsed_time[500];
3773     char black[32], white[32];
3774     Board board;
3775     int prevMove = currentMove;
3776     int ticking = 2;
3777     ChessMove moveType;
3778     int fromX, fromY, toX, toY;
3779     char promoChar;
3780     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3781     char *bookHit = NULL; // [HGM] book
3782     Boolean weird = FALSE, reqFlag = FALSE;
3783
3784     fromX = fromY = toX = toY = -1;
3785     
3786     newGame = FALSE;
3787
3788     if (appData.debugMode)
3789       fprintf(debugFP, _("Parsing board: %s\n"), string);
3790
3791     move_str[0] = NULLCHAR;
3792     elapsed_time[0] = NULLCHAR;
3793     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3794         int  i = 0, j;
3795         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3796             if(string[i] == ' ') { ranks++; files = 0; }
3797             else files++;
3798             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3799             i++;
3800         }
3801         for(j = 0; j <i; j++) board_chars[j] = string[j];
3802         board_chars[i] = '\0';
3803         string += i + 1;
3804     }
3805     n = sscanf(string, PATTERN, &to_play, &double_push,
3806                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3807                &gamenum, white, black, &relation, &basetime, &increment,
3808                &white_stren, &black_stren, &white_time, &black_time,
3809                &moveNum, str, elapsed_time, move_str, &ics_flip,
3810                &ticking);
3811
3812     if (n < 21) {
3813         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3814         DisplayError(str, 0);
3815         return;
3816     }
3817
3818     /* Convert the move number to internal form */
3819     moveNum = (moveNum - 1) * 2;
3820     if (to_play == 'B') moveNum++;
3821     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3822       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3823                         0, 1);
3824       return;
3825     }
3826     
3827     switch (relation) {
3828       case RELATION_OBSERVING_PLAYED:
3829       case RELATION_OBSERVING_STATIC:
3830         if (gamenum == -1) {
3831             /* Old ICC buglet */
3832             relation = RELATION_OBSERVING_STATIC;
3833         }
3834         newGameMode = IcsObserving;
3835         break;
3836       case RELATION_PLAYING_MYMOVE:
3837       case RELATION_PLAYING_NOTMYMOVE:
3838         newGameMode =
3839           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3840             IcsPlayingWhite : IcsPlayingBlack;
3841         break;
3842       case RELATION_EXAMINING:
3843         newGameMode = IcsExamining;
3844         break;
3845       case RELATION_ISOLATED_BOARD:
3846       default:
3847         /* Just display this board.  If user was doing something else,
3848            we will forget about it until the next board comes. */ 
3849         newGameMode = IcsIdle;
3850         break;
3851       case RELATION_STARTING_POSITION:
3852         newGameMode = gameMode;
3853         break;
3854     }
3855     
3856     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3857          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3858       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3859       char *toSqr;
3860       for (k = 0; k < ranks; k++) {
3861         for (j = 0; j < files; j++)
3862           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3863         if(gameInfo.holdingsWidth > 1) {
3864              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3865              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3866         }
3867       }
3868       CopyBoard(partnerBoard, board);
3869       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3870         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3871         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3872       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3873       if(toSqr = strchr(str, '-')) {
3874         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3875         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3876       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3877       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3878       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3879       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3880       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3881       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3882                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3883       DisplayMessage(partnerStatus, "");
3884         partnerBoardValid = TRUE;
3885       return;
3886     }
3887
3888     /* Modify behavior for initial board display on move listing
3889        of wild games.
3890        */
3891     switch (ics_getting_history) {
3892       case H_FALSE:
3893       case H_REQUESTED:
3894         break;
3895       case H_GOT_REQ_HEADER:
3896       case H_GOT_UNREQ_HEADER:
3897         /* This is the initial position of the current game */
3898         gamenum = ics_gamenum;
3899         moveNum = 0;            /* old ICS bug workaround */
3900         if (to_play == 'B') {
3901           startedFromSetupPosition = TRUE;
3902           blackPlaysFirst = TRUE;
3903           moveNum = 1;
3904           if (forwardMostMove == 0) forwardMostMove = 1;
3905           if (backwardMostMove == 0) backwardMostMove = 1;
3906           if (currentMove == 0) currentMove = 1;
3907         }
3908         newGameMode = gameMode;
3909         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3910         break;
3911       case H_GOT_UNWANTED_HEADER:
3912         /* This is an initial board that we don't want */
3913         return;
3914       case H_GETTING_MOVES:
3915         /* Should not happen */
3916         DisplayError(_("Error gathering move list: extra board"), 0);
3917         ics_getting_history = H_FALSE;
3918         return;
3919     }
3920
3921    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3922                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3923      /* [HGM] We seem to have switched variant unexpectedly
3924       * Try to guess new variant from board size
3925       */
3926           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3927           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3928           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3929           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3930           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3931           if(!weird) newVariant = VariantNormal;
3932           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3933           /* Get a move list just to see the header, which
3934              will tell us whether this is really bug or zh */
3935           if (ics_getting_history == H_FALSE) {
3936             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3937             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3938             SendToICS(str);
3939           }
3940     }
3941     
3942     /* Take action if this is the first board of a new game, or of a
3943        different game than is currently being displayed.  */
3944     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3945         relation == RELATION_ISOLATED_BOARD) {
3946         
3947         /* Forget the old game and get the history (if any) of the new one */
3948         if (gameMode != BeginningOfGame) {
3949           Reset(TRUE, TRUE);
3950         }
3951         newGame = TRUE;
3952         if (appData.autoRaiseBoard) BoardToTop();
3953         prevMove = -3;
3954         if (gamenum == -1) {
3955             newGameMode = IcsIdle;
3956         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3957                    appData.getMoveList && !reqFlag) {
3958             /* Need to get game history */
3959             ics_getting_history = H_REQUESTED;
3960             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3961             SendToICS(str);
3962         }
3963         
3964         /* Initially flip the board to have black on the bottom if playing
3965            black or if the ICS flip flag is set, but let the user change
3966            it with the Flip View button. */
3967         flipView = appData.autoFlipView ? 
3968           (newGameMode == IcsPlayingBlack) || ics_flip :
3969           appData.flipView;
3970         
3971         /* Done with values from previous mode; copy in new ones */
3972         gameMode = newGameMode;
3973         ModeHighlight();
3974         ics_gamenum = gamenum;
3975         if (gamenum == gs_gamenum) {
3976             int klen = strlen(gs_kind);
3977             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3978             sprintf(str, "ICS %s", gs_kind);
3979             gameInfo.event = StrSave(str);
3980         } else {
3981             gameInfo.event = StrSave("ICS game");
3982         }
3983         gameInfo.site = StrSave(appData.icsHost);
3984         gameInfo.date = PGNDate();
3985         gameInfo.round = StrSave("-");
3986         gameInfo.white = StrSave(white);
3987         gameInfo.black = StrSave(black);
3988         timeControl = basetime * 60 * 1000;
3989         timeControl_2 = 0;
3990         timeIncrement = increment * 1000;
3991         movesPerSession = 0;
3992         gameInfo.timeControl = TimeControlTagValue();
3993         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3994   if (appData.debugMode) {
3995     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3996     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3997     setbuf(debugFP, NULL);
3998   }
3999
4000         gameInfo.outOfBook = NULL;
4001         
4002         /* Do we have the ratings? */
4003         if (strcmp(player1Name, white) == 0 &&
4004             strcmp(player2Name, black) == 0) {
4005             if (appData.debugMode)
4006               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4007                       player1Rating, player2Rating);
4008             gameInfo.whiteRating = player1Rating;
4009             gameInfo.blackRating = player2Rating;
4010         } else if (strcmp(player2Name, white) == 0 &&
4011                    strcmp(player1Name, black) == 0) {
4012             if (appData.debugMode)
4013               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4014                       player2Rating, player1Rating);
4015             gameInfo.whiteRating = player2Rating;
4016             gameInfo.blackRating = player1Rating;
4017         }
4018         player1Name[0] = player2Name[0] = NULLCHAR;
4019
4020         /* Silence shouts if requested */
4021         if (appData.quietPlay &&
4022             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4023             SendToICS(ics_prefix);
4024             SendToICS("set shout 0\n");
4025         }
4026     }
4027     
4028     /* Deal with midgame name changes */
4029     if (!newGame) {
4030         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4031             if (gameInfo.white) free(gameInfo.white);
4032             gameInfo.white = StrSave(white);
4033         }
4034         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4035             if (gameInfo.black) free(gameInfo.black);
4036             gameInfo.black = StrSave(black);
4037         }
4038     }
4039     
4040     /* Throw away game result if anything actually changes in examine mode */
4041     if (gameMode == IcsExamining && !newGame) {
4042         gameInfo.result = GameUnfinished;
4043         if (gameInfo.resultDetails != NULL) {
4044             free(gameInfo.resultDetails);
4045             gameInfo.resultDetails = NULL;
4046         }
4047     }
4048     
4049     /* In pausing && IcsExamining mode, we ignore boards coming
4050        in if they are in a different variation than we are. */
4051     if (pauseExamInvalid) return;
4052     if (pausing && gameMode == IcsExamining) {
4053         if (moveNum <= pauseExamForwardMostMove) {
4054             pauseExamInvalid = TRUE;
4055             forwardMostMove = pauseExamForwardMostMove;
4056             return;
4057         }
4058     }
4059     
4060   if (appData.debugMode) {
4061     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4062   }
4063     /* Parse the board */
4064     for (k = 0; k < ranks; k++) {
4065       for (j = 0; j < files; j++)
4066         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4067       if(gameInfo.holdingsWidth > 1) {
4068            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4069            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4070       }
4071     }
4072     CopyBoard(boards[moveNum], board);
4073     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4074     if (moveNum == 0) {
4075         startedFromSetupPosition =
4076           !CompareBoards(board, initialPosition);
4077         if(startedFromSetupPosition)
4078             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4079     }
4080
4081     /* [HGM] Set castling rights. Take the outermost Rooks,
4082        to make it also work for FRC opening positions. Note that board12
4083        is really defective for later FRC positions, as it has no way to
4084        indicate which Rook can castle if they are on the same side of King.
4085        For the initial position we grant rights to the outermost Rooks,
4086        and remember thos rights, and we then copy them on positions
4087        later in an FRC game. This means WB might not recognize castlings with
4088        Rooks that have moved back to their original position as illegal,
4089        but in ICS mode that is not its job anyway.
4090     */
4091     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4092     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4093
4094         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4095             if(board[0][i] == WhiteRook) j = i;
4096         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4097         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4098             if(board[0][i] == WhiteRook) j = i;
4099         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4101             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4102         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4103         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4104             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4105         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4106
4107         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4108         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4109             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4110         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4111             if(board[BOARD_HEIGHT-1][k] == bKing)
4112                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4113         if(gameInfo.variant == VariantTwoKings) {
4114             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4115             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4116             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4117         }
4118     } else { int r;
4119         r = boards[moveNum][CASTLING][0] = initialRights[0];
4120         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4121         r = boards[moveNum][CASTLING][1] = initialRights[1];
4122         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4123         r = boards[moveNum][CASTLING][3] = initialRights[3];
4124         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4125         r = boards[moveNum][CASTLING][4] = initialRights[4];
4126         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4127         /* wildcastle kludge: always assume King has rights */
4128         r = boards[moveNum][CASTLING][2] = initialRights[2];
4129         r = boards[moveNum][CASTLING][5] = initialRights[5];
4130     }
4131     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4132     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4133
4134     
4135     if (ics_getting_history == H_GOT_REQ_HEADER ||
4136         ics_getting_history == H_GOT_UNREQ_HEADER) {
4137         /* This was an initial position from a move list, not
4138            the current position */
4139         return;
4140     }
4141     
4142     /* Update currentMove and known move number limits */
4143     newMove = newGame || moveNum > forwardMostMove;
4144
4145     if (newGame) {
4146         forwardMostMove = backwardMostMove = currentMove = moveNum;
4147         if (gameMode == IcsExamining && moveNum == 0) {
4148           /* Workaround for ICS limitation: we are not told the wild
4149              type when starting to examine a game.  But if we ask for
4150              the move list, the move list header will tell us */
4151             ics_getting_history = H_REQUESTED;
4152             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4153             SendToICS(str);
4154         }
4155     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4156                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4157 #if ZIPPY
4158         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4159         /* [HGM] applied this also to an engine that is silently watching        */
4160         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4161             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4162             gameInfo.variant == currentlyInitializedVariant) {
4163           takeback = forwardMostMove - moveNum;
4164           for (i = 0; i < takeback; i++) {
4165             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4166             SendToProgram("undo\n", &first);
4167           }
4168         }
4169 #endif
4170
4171         forwardMostMove = moveNum;
4172         if (!pausing || currentMove > forwardMostMove)
4173           currentMove = forwardMostMove;
4174     } else {
4175         /* New part of history that is not contiguous with old part */ 
4176         if (pausing && gameMode == IcsExamining) {
4177             pauseExamInvalid = TRUE;
4178             forwardMostMove = pauseExamForwardMostMove;
4179             return;
4180         }
4181         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4182 #if ZIPPY
4183             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4184                 // [HGM] when we will receive the move list we now request, it will be
4185                 // fed to the engine from the first move on. So if the engine is not
4186                 // in the initial position now, bring it there.
4187                 InitChessProgram(&first, 0);
4188             }
4189 #endif
4190             ics_getting_history = H_REQUESTED;
4191             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4192             SendToICS(str);
4193         }
4194         forwardMostMove = backwardMostMove = currentMove = moveNum;
4195     }
4196     
4197     /* Update the clocks */
4198     if (strchr(elapsed_time, '.')) {
4199       /* Time is in ms */
4200       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4201       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4202     } else {
4203       /* Time is in seconds */
4204       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4205       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4206     }
4207       
4208
4209 #if ZIPPY
4210     if (appData.zippyPlay && newGame &&
4211         gameMode != IcsObserving && gameMode != IcsIdle &&
4212         gameMode != IcsExamining)
4213       ZippyFirstBoard(moveNum, basetime, increment);
4214 #endif
4215     
4216     /* Put the move on the move list, first converting
4217        to canonical algebraic form. */
4218     if (moveNum > 0) {
4219   if (appData.debugMode) {
4220     if (appData.debugMode) { int f = forwardMostMove;
4221         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4222                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4223                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4224     }
4225     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4226     fprintf(debugFP, "moveNum = %d\n", moveNum);
4227     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4228     setbuf(debugFP, NULL);
4229   }
4230         if (moveNum <= backwardMostMove) {
4231             /* We don't know what the board looked like before
4232                this move.  Punt. */
4233             strcpy(parseList[moveNum - 1], move_str);
4234             strcat(parseList[moveNum - 1], " ");
4235             strcat(parseList[moveNum - 1], elapsed_time);
4236             moveList[moveNum - 1][0] = NULLCHAR;
4237         } else if (strcmp(move_str, "none") == 0) {
4238             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4239             /* Again, we don't know what the board looked like;
4240                this is really the start of the game. */
4241             parseList[moveNum - 1][0] = NULLCHAR;
4242             moveList[moveNum - 1][0] = NULLCHAR;
4243             backwardMostMove = moveNum;
4244             startedFromSetupPosition = TRUE;
4245             fromX = fromY = toX = toY = -1;
4246         } else {
4247           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4248           //                 So we parse the long-algebraic move string in stead of the SAN move
4249           int valid; char buf[MSG_SIZ], *prom;
4250
4251           // str looks something like "Q/a1-a2"; kill the slash
4252           if(str[1] == '/') 
4253                 sprintf(buf, "%c%s", str[0], str+2);
4254           else  strcpy(buf, str); // might be castling
4255           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4256                 strcat(buf, prom); // long move lacks promo specification!
4257           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4258                 if(appData.debugMode) 
4259                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4260                 strcpy(move_str, buf);
4261           }
4262           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4263                                 &fromX, &fromY, &toX, &toY, &promoChar)
4264                || ParseOneMove(buf, moveNum - 1, &moveType,
4265                                 &fromX, &fromY, &toX, &toY, &promoChar);
4266           // end of long SAN patch
4267           if (valid) {
4268             (void) CoordsToAlgebraic(boards[moveNum - 1],
4269                                      PosFlags(moveNum - 1),
4270                                      fromY, fromX, toY, toX, promoChar,
4271                                      parseList[moveNum-1]);
4272             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4273               case MT_NONE:
4274               case MT_STALEMATE:
4275               default:
4276                 break;
4277               case MT_CHECK:
4278                 if(gameInfo.variant != VariantShogi)
4279                     strcat(parseList[moveNum - 1], "+");
4280                 break;
4281               case MT_CHECKMATE:
4282               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4283                 strcat(parseList[moveNum - 1], "#");
4284                 break;
4285             }
4286             strcat(parseList[moveNum - 1], " ");
4287             strcat(parseList[moveNum - 1], elapsed_time);
4288             /* currentMoveString is set as a side-effect of ParseOneMove */
4289             strcpy(moveList[moveNum - 1], currentMoveString);
4290             strcat(moveList[moveNum - 1], "\n");
4291           } else {
4292             /* Move from ICS was illegal!?  Punt. */
4293   if (appData.debugMode) {
4294     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4295     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4296   }
4297             strcpy(parseList[moveNum - 1], move_str);
4298             strcat(parseList[moveNum - 1], " ");
4299             strcat(parseList[moveNum - 1], elapsed_time);
4300             moveList[moveNum - 1][0] = NULLCHAR;
4301             fromX = fromY = toX = toY = -1;
4302           }
4303         }
4304   if (appData.debugMode) {
4305     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4306     setbuf(debugFP, NULL);
4307   }
4308
4309 #if ZIPPY
4310         /* Send move to chess program (BEFORE animating it). */
4311         if (appData.zippyPlay && !newGame && newMove && 
4312            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4313
4314             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4315                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4316                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4317                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4318                             move_str);
4319                     DisplayError(str, 0);
4320                 } else {
4321                     if (first.sendTime) {
4322                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4323                     }
4324                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4325                     if (firstMove && !bookHit) {
4326                         firstMove = FALSE;
4327                         if (first.useColors) {
4328                           SendToProgram(gameMode == IcsPlayingWhite ?
4329                                         "white\ngo\n" :
4330                                         "black\ngo\n", &first);
4331                         } else {
4332                           SendToProgram("go\n", &first);
4333                         }
4334                         first.maybeThinking = TRUE;
4335                     }
4336                 }
4337             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4338               if (moveList[moveNum - 1][0] == NULLCHAR) {
4339                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4340                 DisplayError(str, 0);
4341               } else {
4342                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4343                 SendMoveToProgram(moveNum - 1, &first);
4344               }
4345             }
4346         }
4347 #endif
4348     }
4349
4350     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4351         /* If move comes from a remote source, animate it.  If it
4352            isn't remote, it will have already been animated. */
4353         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4354             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4355         }
4356         if (!pausing && appData.highlightLastMove) {
4357             SetHighlights(fromX, fromY, toX, toY);
4358         }
4359     }
4360     
4361     /* Start the clocks */
4362     whiteFlag = blackFlag = FALSE;
4363     appData.clockMode = !(basetime == 0 && increment == 0);
4364     if (ticking == 0) {
4365       ics_clock_paused = TRUE;
4366       StopClocks();
4367     } else if (ticking == 1) {
4368       ics_clock_paused = FALSE;
4369     }
4370     if (gameMode == IcsIdle ||
4371         relation == RELATION_OBSERVING_STATIC ||
4372         relation == RELATION_EXAMINING ||
4373         ics_clock_paused)
4374       DisplayBothClocks();
4375     else
4376       StartClocks();
4377     
4378     /* Display opponents and material strengths */
4379     if (gameInfo.variant != VariantBughouse &&
4380         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4381         if (tinyLayout || smallLayout) {
4382             if(gameInfo.variant == VariantNormal)
4383                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4384                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4385                     basetime, increment);
4386             else
4387                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4388                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4389                     basetime, increment, (int) gameInfo.variant);
4390         } else {
4391             if(gameInfo.variant == VariantNormal)
4392                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4393                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4394                     basetime, increment);
4395             else
4396                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4397                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4398                     basetime, increment, VariantName(gameInfo.variant));
4399         }
4400         DisplayTitle(str);
4401   if (appData.debugMode) {
4402     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4403   }
4404     }
4405
4406
4407     /* Display the board */
4408     if (!pausing && !appData.noGUI) {
4409       
4410       if (appData.premove)
4411           if (!gotPremove || 
4412              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4413              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4414               ClearPremoveHighlights();
4415
4416       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4417         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4418       DrawPosition(j, boards[currentMove]);
4419
4420       DisplayMove(moveNum - 1);
4421       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4422             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4423               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4424         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4425       }
4426     }
4427
4428     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4429 #if ZIPPY
4430     if(bookHit) { // [HGM] book: simulate book reply
4431         static char bookMove[MSG_SIZ]; // a bit generous?
4432
4433         programStats.nodes = programStats.depth = programStats.time = 
4434         programStats.score = programStats.got_only_move = 0;
4435         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4436
4437         strcpy(bookMove, "move ");
4438         strcat(bookMove, bookHit);
4439         HandleMachineMove(bookMove, &first);
4440     }
4441 #endif
4442 }
4443
4444 void
4445 GetMoveListEvent()
4446 {
4447     char buf[MSG_SIZ];
4448     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4449         ics_getting_history = H_REQUESTED;
4450         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4451         SendToICS(buf);
4452     }
4453 }
4454
4455 void
4456 AnalysisPeriodicEvent(force)
4457      int force;
4458 {
4459     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4460          && !force) || !appData.periodicUpdates)
4461       return;
4462
4463     /* Send . command to Crafty to collect stats */
4464     SendToProgram(".\n", &first);
4465
4466     /* Don't send another until we get a response (this makes
4467        us stop sending to old Crafty's which don't understand
4468        the "." command (sending illegal cmds resets node count & time,
4469        which looks bad)) */
4470     programStats.ok_to_send = 0;
4471 }
4472
4473 void ics_update_width(new_width)
4474         int new_width;
4475 {
4476         ics_printf("set width %d\n", new_width);
4477 }
4478
4479 void
4480 SendMoveToProgram(moveNum, cps)
4481      int moveNum;
4482      ChessProgramState *cps;
4483 {
4484     char buf[MSG_SIZ];
4485
4486     if (cps->useUsermove) {
4487       SendToProgram("usermove ", cps);
4488     }
4489     if (cps->useSAN) {
4490       char *space;
4491       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4492         int len = space - parseList[moveNum];
4493         memcpy(buf, parseList[moveNum], len);
4494         buf[len++] = '\n';
4495         buf[len] = NULLCHAR;
4496       } else {
4497         sprintf(buf, "%s\n", parseList[moveNum]);
4498       }
4499       SendToProgram(buf, cps);
4500     } else {
4501       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4502         AlphaRank(moveList[moveNum], 4);
4503         SendToProgram(moveList[moveNum], cps);
4504         AlphaRank(moveList[moveNum], 4); // and back
4505       } else
4506       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4507        * the engine. It would be nice to have a better way to identify castle 
4508        * moves here. */
4509       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4510                                                                          && cps->useOOCastle) {
4511         int fromX = moveList[moveNum][0] - AAA; 
4512         int fromY = moveList[moveNum][1] - ONE;
4513         int toX = moveList[moveNum][2] - AAA; 
4514         int toY = moveList[moveNum][3] - ONE;
4515         if((boards[moveNum][fromY][fromX] == WhiteKing 
4516             && boards[moveNum][toY][toX] == WhiteRook)
4517            || (boards[moveNum][fromY][fromX] == BlackKing 
4518                && boards[moveNum][toY][toX] == BlackRook)) {
4519           if(toX > fromX) SendToProgram("O-O\n", cps);
4520           else SendToProgram("O-O-O\n", cps);
4521         }
4522         else SendToProgram(moveList[moveNum], cps);
4523       }
4524       else SendToProgram(moveList[moveNum], cps);
4525       /* End of additions by Tord */
4526     }
4527
4528     /* [HGM] setting up the opening has brought engine in force mode! */
4529     /*       Send 'go' if we are in a mode where machine should play. */
4530     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4531         (gameMode == TwoMachinesPlay   ||
4532 #if ZIPPY
4533          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4534 #endif
4535          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4536         SendToProgram("go\n", cps);
4537   if (appData.debugMode) {
4538     fprintf(debugFP, "(extra)\n");
4539   }
4540     }
4541     setboardSpoiledMachineBlack = 0;
4542 }
4543
4544 void
4545 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4546      ChessMove moveType;
4547      int fromX, fromY, toX, toY;
4548      char promoChar;
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                 promoChar);
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 = BOARD_HEIGHT/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     }
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, promoChar);
6126       }
6127       // also send plain move, in case ICS does not understand atomic claims
6128       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
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
6670             if( appData.testLegality )
6671             {   /* [HGM] Some more adjudications for obstinate engines */
6672                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6673                 static int moveCount = 6;
6674                 ChessMove result;
6675                 char *reason = NULL;
6676
6677                 /* Count what is on board. */
6678                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6679
6680                 /* Some material-based adjudications that have to be made before stalemate test */
6681                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6682                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6683                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6684                      if(canAdjudicate && appData.checkMates) {
6685                          if(engineOpponent)
6686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6687                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6688                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6689                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6690                          return 1;
6691                      }
6692                 }
6693
6694                 /* Bare King in Shatranj (loses) or Losers (wins) */
6695                 if( nrW == 1 || nrB == 1) {
6696                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6697                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6698                      if(canAdjudicate && appData.checkMates) {
6699                          if(engineOpponent)
6700                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6701                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6703                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6704                          return 1;
6705                      }
6706                   } else
6707                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6708                   {    /* bare King */
6709                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6710                         if(canAdjudicate && appData.checkMates) {
6711                             /* but only adjudicate if adjudication enabled */
6712                             if(engineOpponent)
6713                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6714                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6715                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6716                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6717                             return 1;
6718                         }
6719                   }
6720                 } else bare = 1;
6721
6722
6723             // don't wait for engine to announce game end if we can judge ourselves
6724             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6725               case MT_CHECK:
6726                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6727                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6728                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6729                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6730                             checkCnt++;
6731                         if(checkCnt >= 2) {
6732                             reason = "Xboard adjudication: 3rd check";
6733                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6734                             break;
6735                         }
6736                     }
6737                 }
6738               case MT_NONE:
6739               default:
6740                 break;
6741               case MT_STALEMATE:
6742               case MT_STAINMATE:
6743                 reason = "Xboard adjudication: Stalemate";
6744                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6745                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6746                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6747                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6748                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6749                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6750                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6751                                                                         EP_CHECKMATE : EP_WINS);
6752                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6753                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6754                 }
6755                 break;
6756               case MT_CHECKMATE:
6757                 reason = "Xboard adjudication: Checkmate";
6758                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6759                 break;
6760             }
6761
6762                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6763                     case EP_STALEMATE:
6764                         result = GameIsDrawn; break;
6765                     case EP_CHECKMATE:
6766                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6767                     case EP_WINS:
6768                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6769                     default:
6770                         result = (ChessMove) 0;
6771                 }
6772                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6773                     if(engineOpponent)
6774                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6775                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6776                     GameEnds( result, reason, GE_XBOARD );
6777                     return 1;
6778                 }
6779
6780                 /* Next absolutely insufficient mating material. */
6781                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6782                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6783                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6784
6785                      /* always flag draws, for judging claims */
6786                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6787
6788                      if(canAdjudicate && appData.materialDraws) {
6789                          /* but only adjudicate them if adjudication enabled */
6790                          if(engineOpponent) {
6791                            SendToProgram("force\n", engineOpponent); // suppress reply
6792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6793                          }
6794                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6796                          return 1;
6797                      }
6798                 }
6799
6800                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6801                 if(gameInfo.variant == VariantXiangqi ?
6802                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6803                  : nrW + nrB == 4 && 
6804                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6805                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6806                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6807                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6808                    ) ) {
6809                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6810                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6811                           if(engineOpponent) {
6812                             SendToProgram("force\n", engineOpponent); // suppress reply
6813                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6814                           }
6815                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6816                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6817                           return 1;
6818                      }
6819                 } else moveCount = 6;
6820             }
6821           
6822         if (appData.debugMode) { int i;
6823             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6824                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6825                     appData.drawRepeats);
6826             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6827               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6828             
6829         }
6830
6831         // Repetition draws and 50-move rule can be applied independently of legality testing
6832
6833                 /* Check for rep-draws */
6834                 count = 0;
6835                 for(k = forwardMostMove-2;
6836                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6837                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6838                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6839                     k-=2)
6840                 {   int rights=0;
6841                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6842                         /* compare castling rights */
6843                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6844                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6845                                 rights++; /* King lost rights, while rook still had them */
6846                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6847                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6848                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6849                                    rights++; /* but at least one rook lost them */
6850                         }
6851                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6852                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6853                                 rights++; 
6854                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6855                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6856                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6857                                    rights++;
6858                         }
6859                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6860                             && appData.drawRepeats > 1) {
6861                              /* adjudicate after user-specified nr of repeats */
6862                              int result = GameIsDrawn;
6863                              char *details = "XBoard adjudication: repetition draw";
6864                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6865                                 // [HGM] xiangqi: check for forbidden perpetuals
6866                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6867                                 for(m=forwardMostMove; m>k; m-=2) {
6868                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6869                                         ourPerpetual = 0; // the current mover did not always check
6870                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6871                                         hisPerpetual = 0; // the opponent did not always check
6872                                 }
6873                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6874                                                                         ourPerpetual, hisPerpetual);
6875                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6876                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6877                                     details = "Xboard adjudication: perpetual checking";
6878                                 } else
6879                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6880                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6881                                 } else
6882                                 // Now check for perpetual chases
6883                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6884                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6885                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6886                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6887                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6888                                         details = "Xboard adjudication: perpetual chasing";
6889                                     } else
6890                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6891                                         break; // Abort repetition-checking loop.
6892                                 }
6893                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6894                              }
6895                              if(engineOpponent) {
6896                                SendToProgram("force\n", engineOpponent); // suppress reply
6897                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6898                              }
6899                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6900                              GameEnds( result, details, GE_XBOARD );
6901                              return 1;
6902                         }
6903                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6904                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6905                     }
6906                 }
6907
6908                 /* Now we test for 50-move draws. Determine ply count */
6909                 count = forwardMostMove;
6910                 /* look for last irreversble move */
6911                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6912                     count--;
6913                 /* if we hit starting position, add initial plies */
6914                 if( count == backwardMostMove )
6915                     count -= initialRulePlies;
6916                 count = forwardMostMove - count; 
6917                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6918                         // adjust reversible move counter for checks in Xiangqi
6919                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6920                         if(i < backwardMostMove) i = backwardMostMove;
6921                         while(i <= forwardMostMove) {
6922                                 lastCheck = inCheck; // check evasion does not count
6923                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6924                                 if(inCheck || lastCheck) count--; // check does not count
6925                                 i++;
6926                         }
6927                 }
6928                 if( count >= 100)
6929                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6930                          /* this is used to judge if draw claims are legal */
6931                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6932                          if(engineOpponent) {
6933                            SendToProgram("force\n", engineOpponent); // suppress reply
6934                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6935                          }
6936                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6937                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6938                          return 1;
6939                 }
6940
6941                 /* if draw offer is pending, treat it as a draw claim
6942                  * when draw condition present, to allow engines a way to
6943                  * claim draws before making their move to avoid a race
6944                  * condition occurring after their move
6945                  */
6946                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6947                          char *p = NULL;
6948                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6949                              p = "Draw claim: 50-move rule";
6950                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6951                              p = "Draw claim: 3-fold repetition";
6952                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6953                              p = "Draw claim: insufficient mating material";
6954                          if( p != NULL && canAdjudicate) {
6955                              if(engineOpponent) {
6956                                SendToProgram("force\n", engineOpponent); // suppress reply
6957                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6958                              }
6959                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6960                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6961                              return 1;
6962                          }
6963                 }
6964
6965                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6966                     if(engineOpponent) {
6967                       SendToProgram("force\n", engineOpponent); // suppress reply
6968                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6969                     }
6970                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6971                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6972                     return 1;
6973                 }
6974         return 0;
6975 }
6976
6977 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6978 {   // [HGM] book: this routine intercepts moves to simulate book replies
6979     char *bookHit = NULL;
6980
6981     //first determine if the incoming move brings opponent into his book
6982     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6983         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6984     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6985     if(bookHit != NULL && !cps->bookSuspend) {
6986         // make sure opponent is not going to reply after receiving move to book position
6987         SendToProgram("force\n", cps);
6988         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6989     }
6990     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6991     // now arrange restart after book miss
6992     if(bookHit) {
6993         // after a book hit we never send 'go', and the code after the call to this routine
6994         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6995         char buf[MSG_SIZ];
6996         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6997         SendToProgram(buf, cps);
6998         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6999     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7000         SendToProgram("go\n", cps);
7001         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7002     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7003         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7004             SendToProgram("go\n", cps); 
7005         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7006     }
7007     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7008 }
7009
7010 char *savedMessage;
7011 ChessProgramState *savedState;
7012 void DeferredBookMove(void)
7013 {
7014         if(savedState->lastPing != savedState->lastPong)
7015                     ScheduleDelayedEvent(DeferredBookMove, 10);
7016         else
7017         HandleMachineMove(savedMessage, savedState);
7018 }
7019
7020 void
7021 HandleMachineMove(message, cps)
7022      char *message;
7023      ChessProgramState *cps;
7024 {
7025     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7026     char realname[MSG_SIZ];
7027     int fromX, fromY, toX, toY;
7028     ChessMove moveType;
7029     char promoChar;
7030     char *p;
7031     int machineWhite;
7032     char *bookHit;
7033
7034     cps->userError = 0;
7035
7036 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7037     /*
7038      * Kludge to ignore BEL characters
7039      */
7040     while (*message == '\007') message++;
7041
7042     /*
7043      * [HGM] engine debug message: ignore lines starting with '#' character
7044      */
7045     if(cps->debug && *message == '#') return;
7046
7047     /*
7048      * Look for book output
7049      */
7050     if (cps == &first && bookRequested) {
7051         if (message[0] == '\t' || message[0] == ' ') {
7052             /* Part of the book output is here; append it */
7053             strcat(bookOutput, message);
7054             strcat(bookOutput, "  \n");
7055             return;
7056         } else if (bookOutput[0] != NULLCHAR) {
7057             /* All of book output has arrived; display it */
7058             char *p = bookOutput;
7059             while (*p != NULLCHAR) {
7060                 if (*p == '\t') *p = ' ';
7061                 p++;
7062             }
7063             DisplayInformation(bookOutput);
7064             bookRequested = FALSE;
7065             /* Fall through to parse the current output */
7066         }
7067     }
7068
7069     /*
7070      * Look for machine move.
7071      */
7072     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7073         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7074     {
7075         /* This method is only useful on engines that support ping */
7076         if (cps->lastPing != cps->lastPong) {
7077           if (gameMode == BeginningOfGame) {
7078             /* Extra move from before last new; ignore */
7079             if (appData.debugMode) {
7080                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7081             }
7082           } else {
7083             if (appData.debugMode) {
7084                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7085                         cps->which, gameMode);
7086             }
7087
7088             SendToProgram("undo\n", cps);
7089           }
7090           return;
7091         }
7092
7093         switch (gameMode) {
7094           case BeginningOfGame:
7095             /* Extra move from before last reset; ignore */
7096             if (appData.debugMode) {
7097                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7098             }
7099             return;
7100
7101           case EndOfGame:
7102           case IcsIdle:
7103           default:
7104             /* Extra move after we tried to stop.  The mode test is
7105                not a reliable way of detecting this problem, but it's
7106                the best we can do on engines that don't support ping.
7107             */
7108             if (appData.debugMode) {
7109                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7110                         cps->which, gameMode);
7111             }
7112             SendToProgram("undo\n", cps);
7113             return;
7114
7115           case MachinePlaysWhite:
7116           case IcsPlayingWhite:
7117             machineWhite = TRUE;
7118             break;
7119
7120           case MachinePlaysBlack:
7121           case IcsPlayingBlack:
7122             machineWhite = FALSE;
7123             break;
7124
7125           case TwoMachinesPlay:
7126             machineWhite = (cps->twoMachinesColor[0] == 'w');
7127             break;
7128         }
7129         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7130             if (appData.debugMode) {
7131                 fprintf(debugFP,
7132                         "Ignoring move out of turn by %s, gameMode %d"
7133                         ", forwardMost %d\n",
7134                         cps->which, gameMode, forwardMostMove);
7135             }
7136             return;
7137         }
7138
7139     if (appData.debugMode) { int f = forwardMostMove;
7140         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7141                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7142                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7143     }
7144         if(cps->alphaRank) AlphaRank(machineMove, 4);
7145         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7146                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7147             /* Machine move could not be parsed; ignore it. */
7148             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7149                     machineMove, cps->which);
7150             DisplayError(buf1, 0);
7151             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7152                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7153             if (gameMode == TwoMachinesPlay) {
7154               GameEnds(machineWhite ? BlackWins : WhiteWins,
7155                        buf1, GE_XBOARD);
7156             }
7157             return;
7158         }
7159
7160         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7161         /* So we have to redo legality test with true e.p. status here,  */
7162         /* to make sure an illegal e.p. capture does not slip through,   */
7163         /* to cause a forfeit on a justified illegal-move complaint      */
7164         /* of the opponent.                                              */
7165         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7166            ChessMove moveType;
7167            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7168                              fromY, fromX, toY, toX, promoChar);
7169             if (appData.debugMode) {
7170                 int i;
7171                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7172                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7173                 fprintf(debugFP, "castling rights\n");
7174             }
7175             if(moveType == IllegalMove) {
7176                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7177                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7178                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7179                            buf1, GE_XBOARD);
7180                 return;
7181            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7182            /* [HGM] Kludge to handle engines that send FRC-style castling
7183               when they shouldn't (like TSCP-Gothic) */
7184            switch(moveType) {
7185              case WhiteASideCastleFR:
7186              case BlackASideCastleFR:
7187                toX+=2;
7188                currentMoveString[2]++;
7189                break;
7190              case WhiteHSideCastleFR:
7191              case BlackHSideCastleFR:
7192                toX--;
7193                currentMoveString[2]--;
7194                break;
7195              default: ; // nothing to do, but suppresses warning of pedantic compilers
7196            }
7197         }
7198         hintRequested = FALSE;
7199         lastHint[0] = NULLCHAR;
7200         bookRequested = FALSE;
7201         /* Program may be pondering now */
7202         cps->maybeThinking = TRUE;
7203         if (cps->sendTime == 2) cps->sendTime = 1;
7204         if (cps->offeredDraw) cps->offeredDraw--;
7205
7206         /* currentMoveString is set as a side-effect of ParseOneMove */
7207         strcpy(machineMove, currentMoveString);
7208         strcat(machineMove, "\n");
7209         strcpy(moveList[forwardMostMove], machineMove);
7210
7211         /* [AS] Save move info*/
7212         pvInfoList[ forwardMostMove ].score = programStats.score;
7213         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7214         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7215
7216         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7217
7218         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7219         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7220             int count = 0;
7221
7222             while( count < adjudicateLossPlies ) {
7223                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7224
7225                 if( count & 1 ) {
7226                     score = -score; /* Flip score for winning side */
7227                 }
7228
7229                 if( score > adjudicateLossThreshold ) {
7230                     break;
7231                 }
7232
7233                 count++;
7234             }
7235
7236             if( count >= adjudicateLossPlies ) {
7237                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7238
7239                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7240                     "Xboard adjudication", 
7241                     GE_XBOARD );
7242
7243                 return;
7244             }
7245         }
7246
7247         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7248
7249 #if ZIPPY
7250         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7251             first.initDone) {
7252           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7253                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7254                 SendToICS("draw ");
7255                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7256           }
7257           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7258           ics_user_moved = 1;
7259           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7260                 char buf[3*MSG_SIZ];
7261
7262                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7263                         programStats.score / 100.,
7264                         programStats.depth,
7265                         programStats.time / 100.,
7266                         (unsigned int)programStats.nodes,
7267                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7268                         programStats.movelist);
7269                 SendToICS(buf);
7270 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7271           }
7272         }
7273 #endif
7274
7275         /* [AS] Clear stats for next move */
7276         ClearProgramStats();
7277         thinkOutput[0] = NULLCHAR;
7278         hiddenThinkOutputState = 0;
7279
7280         bookHit = NULL;
7281         if (gameMode == TwoMachinesPlay) {
7282             /* [HGM] relaying draw offers moved to after reception of move */
7283             /* and interpreting offer as claim if it brings draw condition */
7284             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7285                 SendToProgram("draw\n", cps->other);
7286             }
7287             if (cps->other->sendTime) {
7288                 SendTimeRemaining(cps->other,
7289                                   cps->other->twoMachinesColor[0] == 'w');
7290             }
7291             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7292             if (firstMove && !bookHit) {
7293                 firstMove = FALSE;
7294                 if (cps->other->useColors) {
7295                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7296                 }
7297                 SendToProgram("go\n", cps->other);
7298             }
7299             cps->other->maybeThinking = TRUE;
7300         }
7301
7302         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7303         
7304         if (!pausing && appData.ringBellAfterMoves) {
7305             RingBell();
7306         }
7307
7308         /* 
7309          * Reenable menu items that were disabled while
7310          * machine was thinking
7311          */
7312         if (gameMode != TwoMachinesPlay)
7313             SetUserThinkingEnables();
7314
7315         // [HGM] book: after book hit opponent has received move and is now in force mode
7316         // force the book reply into it, and then fake that it outputted this move by jumping
7317         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7318         if(bookHit) {
7319                 static char bookMove[MSG_SIZ]; // a bit generous?
7320
7321                 strcpy(bookMove, "move ");
7322                 strcat(bookMove, bookHit);
7323                 message = bookMove;
7324                 cps = cps->other;
7325                 programStats.nodes = programStats.depth = programStats.time = 
7326                 programStats.score = programStats.got_only_move = 0;
7327                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7328
7329                 if(cps->lastPing != cps->lastPong) {
7330                     savedMessage = message; // args for deferred call
7331                     savedState = cps;
7332                     ScheduleDelayedEvent(DeferredBookMove, 10);
7333                     return;
7334                 }
7335                 goto FakeBookMove;
7336         }
7337
7338         return;
7339     }
7340
7341     /* Set special modes for chess engines.  Later something general
7342      *  could be added here; for now there is just one kludge feature,
7343      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7344      *  when "xboard" is given as an interactive command.
7345      */
7346     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7347         cps->useSigint = FALSE;
7348         cps->useSigterm = FALSE;
7349     }
7350     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7351       ParseFeatures(message+8, cps);
7352       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7353     }
7354
7355     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7356      * want this, I was asked to put it in, and obliged.
7357      */
7358     if (!strncmp(message, "setboard ", 9)) {
7359         Board initial_position;
7360
7361         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7362
7363         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7364             DisplayError(_("Bad FEN received from engine"), 0);
7365             return ;
7366         } else {
7367            Reset(TRUE, FALSE);
7368            CopyBoard(boards[0], initial_position);
7369            initialRulePlies = FENrulePlies;
7370            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7371            else gameMode = MachinePlaysBlack;                 
7372            DrawPosition(FALSE, boards[currentMove]);
7373         }
7374         return;
7375     }
7376
7377     /*
7378      * Look for communication commands
7379      */
7380     if (!strncmp(message, "telluser ", 9)) {
7381         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7382         DisplayNote(message + 9);
7383         return;
7384     }
7385     if (!strncmp(message, "tellusererror ", 14)) {
7386         cps->userError = 1;
7387         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7388         DisplayError(message + 14, 0);
7389         return;
7390     }
7391     if (!strncmp(message, "tellopponent ", 13)) {
7392       if (appData.icsActive) {
7393         if (loggedOn) {
7394           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7395           SendToICS(buf1);
7396         }
7397       } else {
7398         DisplayNote(message + 13);
7399       }
7400       return;
7401     }
7402     if (!strncmp(message, "tellothers ", 11)) {
7403       if (appData.icsActive) {
7404         if (loggedOn) {
7405           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7406           SendToICS(buf1);
7407         }
7408       }
7409       return;
7410     }
7411     if (!strncmp(message, "tellall ", 8)) {
7412       if (appData.icsActive) {
7413         if (loggedOn) {
7414           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7415           SendToICS(buf1);
7416         }
7417       } else {
7418         DisplayNote(message + 8);
7419       }
7420       return;
7421     }
7422     if (strncmp(message, "warning", 7) == 0) {
7423         /* Undocumented feature, use tellusererror in new code */
7424         DisplayError(message, 0);
7425         return;
7426     }
7427     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7428         strcpy(realname, cps->tidy);
7429         strcat(realname, " query");
7430         AskQuestion(realname, buf2, buf1, cps->pr);
7431         return;
7432     }
7433     /* Commands from the engine directly to ICS.  We don't allow these to be 
7434      *  sent until we are logged on. Crafty kibitzes have been known to 
7435      *  interfere with the login process.
7436      */
7437     if (loggedOn) {
7438         if (!strncmp(message, "tellics ", 8)) {
7439             SendToICS(message + 8);
7440             SendToICS("\n");
7441             return;
7442         }
7443         if (!strncmp(message, "tellicsnoalias ", 15)) {
7444             SendToICS(ics_prefix);
7445             SendToICS(message + 15);
7446             SendToICS("\n");
7447             return;
7448         }
7449         /* The following are for backward compatibility only */
7450         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7451             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7452             SendToICS(ics_prefix);
7453             SendToICS(message);
7454             SendToICS("\n");
7455             return;
7456         }
7457     }
7458     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7459         return;
7460     }
7461     /*
7462      * If the move is illegal, cancel it and redraw the board.
7463      * Also deal with other error cases.  Matching is rather loose
7464      * here to accommodate engines written before the spec.
7465      */
7466     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7467         strncmp(message, "Error", 5) == 0) {
7468         if (StrStr(message, "name") || 
7469             StrStr(message, "rating") || StrStr(message, "?") ||
7470             StrStr(message, "result") || StrStr(message, "board") ||
7471             StrStr(message, "bk") || StrStr(message, "computer") ||
7472             StrStr(message, "variant") || StrStr(message, "hint") ||
7473             StrStr(message, "random") || StrStr(message, "depth") ||
7474             StrStr(message, "accepted")) {
7475             return;
7476         }
7477         if (StrStr(message, "protover")) {
7478           /* Program is responding to input, so it's apparently done
7479              initializing, and this error message indicates it is
7480              protocol version 1.  So we don't need to wait any longer
7481              for it to initialize and send feature commands. */
7482           FeatureDone(cps, 1);
7483           cps->protocolVersion = 1;
7484           return;
7485         }
7486         cps->maybeThinking = FALSE;
7487
7488         if (StrStr(message, "draw")) {
7489             /* Program doesn't have "draw" command */
7490             cps->sendDrawOffers = 0;
7491             return;
7492         }
7493         if (cps->sendTime != 1 &&
7494             (StrStr(message, "time") || StrStr(message, "otim"))) {
7495           /* Program apparently doesn't have "time" or "otim" command */
7496           cps->sendTime = 0;
7497           return;
7498         }
7499         if (StrStr(message, "analyze")) {
7500             cps->analysisSupport = FALSE;
7501             cps->analyzing = FALSE;
7502             Reset(FALSE, TRUE);
7503             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7504             DisplayError(buf2, 0);
7505             return;
7506         }
7507         if (StrStr(message, "(no matching move)st")) {
7508           /* Special kludge for GNU Chess 4 only */
7509           cps->stKludge = TRUE;
7510           SendTimeControl(cps, movesPerSession, timeControl,
7511                           timeIncrement, appData.searchDepth,
7512                           searchTime);
7513           return;
7514         }
7515         if (StrStr(message, "(no matching move)sd")) {
7516           /* Special kludge for GNU Chess 4 only */
7517           cps->sdKludge = TRUE;
7518           SendTimeControl(cps, movesPerSession, timeControl,
7519                           timeIncrement, appData.searchDepth,
7520                           searchTime);
7521           return;
7522         }
7523         if (!StrStr(message, "llegal")) {
7524             return;
7525         }
7526         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7527             gameMode == IcsIdle) return;
7528         if (forwardMostMove <= backwardMostMove) return;
7529         if (pausing) PauseEvent();
7530       if(appData.forceIllegal) {
7531             // [HGM] illegal: machine refused move; force position after move into it
7532           SendToProgram("force\n", cps);
7533           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7534                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7535                 // when black is to move, while there might be nothing on a2 or black
7536                 // might already have the move. So send the board as if white has the move.
7537                 // But first we must change the stm of the engine, as it refused the last move
7538                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7539                 if(WhiteOnMove(forwardMostMove)) {
7540                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7541                     SendBoard(cps, forwardMostMove); // kludgeless board
7542                 } else {
7543                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7544                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7545                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7546                 }
7547           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7548             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7549                  gameMode == TwoMachinesPlay)
7550               SendToProgram("go\n", cps);
7551             return;
7552       } else
7553         if (gameMode == PlayFromGameFile) {
7554             /* Stop reading this game file */
7555             gameMode = EditGame;
7556             ModeHighlight();
7557         }
7558         currentMove = forwardMostMove-1;
7559         DisplayMove(currentMove-1); /* before DisplayMoveError */
7560         SwitchClocks(forwardMostMove-1); // [HGM] race
7561         DisplayBothClocks();
7562         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7563                 parseList[currentMove], cps->which);
7564         DisplayMoveError(buf1);
7565         DrawPosition(FALSE, boards[currentMove]);
7566
7567         /* [HGM] illegal-move claim should forfeit game when Xboard */
7568         /* only passes fully legal moves                            */
7569         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7570             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7571                                 "False illegal-move claim", GE_XBOARD );
7572         }
7573         return;
7574     }
7575     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7576         /* Program has a broken "time" command that
7577            outputs a string not ending in newline.
7578            Don't use it. */
7579         cps->sendTime = 0;
7580     }
7581     
7582     /*
7583      * If chess program startup fails, exit with an error message.
7584      * Attempts to recover here are futile.
7585      */
7586     if ((StrStr(message, "unknown host") != NULL)
7587         || (StrStr(message, "No remote directory") != NULL)
7588         || (StrStr(message, "not found") != NULL)
7589         || (StrStr(message, "No such file") != NULL)
7590         || (StrStr(message, "can't alloc") != NULL)
7591         || (StrStr(message, "Permission denied") != NULL)) {
7592
7593         cps->maybeThinking = FALSE;
7594         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7595                 cps->which, cps->program, cps->host, message);
7596         RemoveInputSource(cps->isr);
7597         DisplayFatalError(buf1, 0, 1);
7598         return;
7599     }
7600     
7601     /* 
7602      * Look for hint output
7603      */
7604     if (sscanf(message, "Hint: %s", buf1) == 1) {
7605         if (cps == &first && hintRequested) {
7606             hintRequested = FALSE;
7607             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7608                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7609                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7610                                     PosFlags(forwardMostMove),
7611                                     fromY, fromX, toY, toX, promoChar, buf1);
7612                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7613                 DisplayInformation(buf2);
7614             } else {
7615                 /* Hint move could not be parsed!? */
7616               snprintf(buf2, sizeof(buf2),
7617                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7618                         buf1, cps->which);
7619                 DisplayError(buf2, 0);
7620             }
7621         } else {
7622             strcpy(lastHint, buf1);
7623         }
7624         return;
7625     }
7626
7627     /*
7628      * Ignore other messages if game is not in progress
7629      */
7630     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7631         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7632
7633     /*
7634      * look for win, lose, draw, or draw offer
7635      */
7636     if (strncmp(message, "1-0", 3) == 0) {
7637         char *p, *q, *r = "";
7638         p = strchr(message, '{');
7639         if (p) {
7640             q = strchr(p, '}');
7641             if (q) {
7642                 *q = NULLCHAR;
7643                 r = p + 1;
7644             }
7645         }
7646         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7647         return;
7648     } else if (strncmp(message, "0-1", 3) == 0) {
7649         char *p, *q, *r = "";
7650         p = strchr(message, '{');
7651         if (p) {
7652             q = strchr(p, '}');
7653             if (q) {
7654                 *q = NULLCHAR;
7655                 r = p + 1;
7656             }
7657         }
7658         /* Kludge for Arasan 4.1 bug */
7659         if (strcmp(r, "Black resigns") == 0) {
7660             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7661             return;
7662         }
7663         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7664         return;
7665     } else if (strncmp(message, "1/2", 3) == 0) {
7666         char *p, *q, *r = "";
7667         p = strchr(message, '{');
7668         if (p) {
7669             q = strchr(p, '}');
7670             if (q) {
7671                 *q = NULLCHAR;
7672                 r = p + 1;
7673             }
7674         }
7675             
7676         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7677         return;
7678
7679     } else if (strncmp(message, "White resign", 12) == 0) {
7680         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7681         return;
7682     } else if (strncmp(message, "Black resign", 12) == 0) {
7683         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7684         return;
7685     } else if (strncmp(message, "White matches", 13) == 0 ||
7686                strncmp(message, "Black matches", 13) == 0   ) {
7687         /* [HGM] ignore GNUShogi noises */
7688         return;
7689     } else if (strncmp(message, "White", 5) == 0 &&
7690                message[5] != '(' &&
7691                StrStr(message, "Black") == NULL) {
7692         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7693         return;
7694     } else if (strncmp(message, "Black", 5) == 0 &&
7695                message[5] != '(') {
7696         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7697         return;
7698     } else if (strcmp(message, "resign") == 0 ||
7699                strcmp(message, "computer resigns") == 0) {
7700         switch (gameMode) {
7701           case MachinePlaysBlack:
7702           case IcsPlayingBlack:
7703             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7704             break;
7705           case MachinePlaysWhite:
7706           case IcsPlayingWhite:
7707             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7708             break;
7709           case TwoMachinesPlay:
7710             if (cps->twoMachinesColor[0] == 'w')
7711               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7712             else
7713               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7714             break;
7715           default:
7716             /* can't happen */
7717             break;
7718         }
7719         return;
7720     } else if (strncmp(message, "opponent mates", 14) == 0) {
7721         switch (gameMode) {
7722           case MachinePlaysBlack:
7723           case IcsPlayingBlack:
7724             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7725             break;
7726           case MachinePlaysWhite:
7727           case IcsPlayingWhite:
7728             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7729             break;
7730           case TwoMachinesPlay:
7731             if (cps->twoMachinesColor[0] == 'w')
7732               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7733             else
7734               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7735             break;
7736           default:
7737             /* can't happen */
7738             break;
7739         }
7740         return;
7741     } else if (strncmp(message, "computer mates", 14) == 0) {
7742         switch (gameMode) {
7743           case MachinePlaysBlack:
7744           case IcsPlayingBlack:
7745             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7746             break;
7747           case MachinePlaysWhite:
7748           case IcsPlayingWhite:
7749             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7750             break;
7751           case TwoMachinesPlay:
7752             if (cps->twoMachinesColor[0] == 'w')
7753               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7754             else
7755               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7756             break;
7757           default:
7758             /* can't happen */
7759             break;
7760         }
7761         return;
7762     } else if (strncmp(message, "checkmate", 9) == 0) {
7763         if (WhiteOnMove(forwardMostMove)) {
7764             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7765         } else {
7766             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7767         }
7768         return;
7769     } else if (strstr(message, "Draw") != NULL ||
7770                strstr(message, "game is a draw") != NULL) {
7771         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7772         return;
7773     } else if (strstr(message, "offer") != NULL &&
7774                strstr(message, "draw") != NULL) {
7775 #if ZIPPY
7776         if (appData.zippyPlay && first.initDone) {
7777             /* Relay offer to ICS */
7778             SendToICS(ics_prefix);
7779             SendToICS("draw\n");
7780         }
7781 #endif
7782         cps->offeredDraw = 2; /* valid until this engine moves twice */
7783         if (gameMode == TwoMachinesPlay) {
7784             if (cps->other->offeredDraw) {
7785                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7786             /* [HGM] in two-machine mode we delay relaying draw offer      */
7787             /* until after we also have move, to see if it is really claim */
7788             }
7789         } else if (gameMode == MachinePlaysWhite ||
7790                    gameMode == MachinePlaysBlack) {
7791           if (userOfferedDraw) {
7792             DisplayInformation(_("Machine accepts your draw offer"));
7793             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7794           } else {
7795             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7796           }
7797         }
7798     }
7799
7800     
7801     /*
7802      * Look for thinking output
7803      */
7804     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7805           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7806                                 ) {
7807         int plylev, mvleft, mvtot, curscore, time;
7808         char mvname[MOVE_LEN];
7809         u64 nodes; // [DM]
7810         char plyext;
7811         int ignore = FALSE;
7812         int prefixHint = FALSE;
7813         mvname[0] = NULLCHAR;
7814
7815         switch (gameMode) {
7816           case MachinePlaysBlack:
7817           case IcsPlayingBlack:
7818             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7819             break;
7820           case MachinePlaysWhite:
7821           case IcsPlayingWhite:
7822             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7823             break;
7824           case AnalyzeMode:
7825           case AnalyzeFile:
7826             break;
7827           case IcsObserving: /* [DM] icsEngineAnalyze */
7828             if (!appData.icsEngineAnalyze) ignore = TRUE;
7829             break;
7830           case TwoMachinesPlay:
7831             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7832                 ignore = TRUE;
7833             }
7834             break;
7835           default:
7836             ignore = TRUE;
7837             break;
7838         }
7839
7840         if (!ignore) {
7841             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7842             buf1[0] = NULLCHAR;
7843             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7844                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7845
7846                 if (plyext != ' ' && plyext != '\t') {
7847                     time *= 100;
7848                 }
7849
7850                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7851                 if( cps->scoreIsAbsolute && 
7852                     ( gameMode == MachinePlaysBlack ||
7853                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7854                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7855                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7856                      !WhiteOnMove(currentMove)
7857                     ) )
7858                 {
7859                     curscore = -curscore;
7860                 }
7861
7862
7863                 tempStats.depth = plylev;
7864                 tempStats.nodes = nodes;
7865                 tempStats.time = time;
7866                 tempStats.score = curscore;
7867                 tempStats.got_only_move = 0;
7868
7869                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7870                         int ticklen;
7871
7872                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7873                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7874                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7876                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7877                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7879                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7880                 }
7881
7882                 /* Buffer overflow protection */
7883                 if (buf1[0] != NULLCHAR) {
7884                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7885                         && appData.debugMode) {
7886                         fprintf(debugFP,
7887                                 "PV is too long; using the first %u bytes.\n",
7888                                 (unsigned) sizeof(tempStats.movelist) - 1);
7889                     }
7890
7891                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7892                 } else {
7893                     sprintf(tempStats.movelist, " no PV\n");
7894                 }
7895
7896                 if (tempStats.seen_stat) {
7897                     tempStats.ok_to_send = 1;
7898                 }
7899
7900                 if (strchr(tempStats.movelist, '(') != NULL) {
7901                     tempStats.line_is_book = 1;
7902                     tempStats.nr_moves = 0;
7903                     tempStats.moves_left = 0;
7904                 } else {
7905                     tempStats.line_is_book = 0;
7906                 }
7907
7908                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7909                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7910
7911                 SendProgramStatsToFrontend( cps, &tempStats );
7912
7913                 /* 
7914                     [AS] Protect the thinkOutput buffer from overflow... this
7915                     is only useful if buf1 hasn't overflowed first!
7916                 */
7917                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7918                         plylev, 
7919                         (gameMode == TwoMachinesPlay ?
7920                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7921                         ((double) curscore) / 100.0,
7922                         prefixHint ? lastHint : "",
7923                         prefixHint ? " " : "" );
7924
7925                 if( buf1[0] != NULLCHAR ) {
7926                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7927
7928                     if( strlen(buf1) > max_len ) {
7929                         if( appData.debugMode) {
7930                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7931                         }
7932                         buf1[max_len+1] = '\0';
7933                     }
7934
7935                     strcat( thinkOutput, buf1 );
7936                 }
7937
7938                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7939                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7940                     DisplayMove(currentMove - 1);
7941                 }
7942                 return;
7943
7944             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7945                 /* crafty (9.25+) says "(only move) <move>"
7946                  * if there is only 1 legal move
7947                  */
7948                 sscanf(p, "(only move) %s", buf1);
7949                 sprintf(thinkOutput, "%s (only move)", buf1);
7950                 sprintf(programStats.movelist, "%s (only move)", buf1);
7951                 programStats.depth = 1;
7952                 programStats.nr_moves = 1;
7953                 programStats.moves_left = 1;
7954                 programStats.nodes = 1;
7955                 programStats.time = 1;
7956                 programStats.got_only_move = 1;
7957
7958                 /* Not really, but we also use this member to
7959                    mean "line isn't going to change" (Crafty
7960                    isn't searching, so stats won't change) */
7961                 programStats.line_is_book = 1;
7962
7963                 SendProgramStatsToFrontend( cps, &programStats );
7964                 
7965                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7966                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7967                     DisplayMove(currentMove - 1);
7968                 }
7969                 return;
7970             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7971                               &time, &nodes, &plylev, &mvleft,
7972                               &mvtot, mvname) >= 5) {
7973                 /* The stat01: line is from Crafty (9.29+) in response
7974                    to the "." command */
7975                 programStats.seen_stat = 1;
7976                 cps->maybeThinking = TRUE;
7977
7978                 if (programStats.got_only_move || !appData.periodicUpdates)
7979                   return;
7980
7981                 programStats.depth = plylev;
7982                 programStats.time = time;
7983                 programStats.nodes = nodes;
7984                 programStats.moves_left = mvleft;
7985                 programStats.nr_moves = mvtot;
7986                 strcpy(programStats.move_name, mvname);
7987                 programStats.ok_to_send = 1;
7988                 programStats.movelist[0] = '\0';
7989
7990                 SendProgramStatsToFrontend( cps, &programStats );
7991
7992                 return;
7993
7994             } else if (strncmp(message,"++",2) == 0) {
7995                 /* Crafty 9.29+ outputs this */
7996                 programStats.got_fail = 2;
7997                 return;
7998
7999             } else if (strncmp(message,"--",2) == 0) {
8000                 /* Crafty 9.29+ outputs this */
8001                 programStats.got_fail = 1;
8002                 return;
8003
8004             } else if (thinkOutput[0] != NULLCHAR &&
8005                        strncmp(message, "    ", 4) == 0) {
8006                 unsigned message_len;
8007
8008                 p = message;
8009                 while (*p && *p == ' ') p++;
8010
8011                 message_len = strlen( p );
8012
8013                 /* [AS] Avoid buffer overflow */
8014                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8015                     strcat(thinkOutput, " ");
8016                     strcat(thinkOutput, p);
8017                 }
8018
8019                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8020                     strcat(programStats.movelist, " ");
8021                     strcat(programStats.movelist, p);
8022                 }
8023
8024                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8025                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8026                     DisplayMove(currentMove - 1);
8027                 }
8028                 return;
8029             }
8030         }
8031         else {
8032             buf1[0] = NULLCHAR;
8033
8034             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8035                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8036             {
8037                 ChessProgramStats cpstats;
8038
8039                 if (plyext != ' ' && plyext != '\t') {
8040                     time *= 100;
8041                 }
8042
8043                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8044                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8045                     curscore = -curscore;
8046                 }
8047
8048                 cpstats.depth = plylev;
8049                 cpstats.nodes = nodes;
8050                 cpstats.time = time;
8051                 cpstats.score = curscore;
8052                 cpstats.got_only_move = 0;
8053                 cpstats.movelist[0] = '\0';
8054
8055                 if (buf1[0] != NULLCHAR) {
8056                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8057                 }
8058
8059                 cpstats.ok_to_send = 0;
8060                 cpstats.line_is_book = 0;
8061                 cpstats.nr_moves = 0;
8062                 cpstats.moves_left = 0;
8063
8064                 SendProgramStatsToFrontend( cps, &cpstats );
8065             }
8066         }
8067     }
8068 }
8069
8070
8071 /* Parse a game score from the character string "game", and
8072    record it as the history of the current game.  The game
8073    score is NOT assumed to start from the standard position. 
8074    The display is not updated in any way.
8075    */
8076 void
8077 ParseGameHistory(game)
8078      char *game;
8079 {
8080     ChessMove moveType;
8081     int fromX, fromY, toX, toY, boardIndex;
8082     char promoChar;
8083     char *p, *q;
8084     char buf[MSG_SIZ];
8085
8086     if (appData.debugMode)
8087       fprintf(debugFP, "Parsing game history: %s\n", game);
8088
8089     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8090     gameInfo.site = StrSave(appData.icsHost);
8091     gameInfo.date = PGNDate();
8092     gameInfo.round = StrSave("-");
8093
8094     /* Parse out names of players */
8095     while (*game == ' ') game++;
8096     p = buf;
8097     while (*game != ' ') *p++ = *game++;
8098     *p = NULLCHAR;
8099     gameInfo.white = StrSave(buf);
8100     while (*game == ' ') game++;
8101     p = buf;
8102     while (*game != ' ' && *game != '\n') *p++ = *game++;
8103     *p = NULLCHAR;
8104     gameInfo.black = StrSave(buf);
8105
8106     /* Parse moves */
8107     boardIndex = blackPlaysFirst ? 1 : 0;
8108     yynewstr(game);
8109     for (;;) {
8110         yyboardindex = boardIndex;
8111         moveType = (ChessMove) yylex();
8112         switch (moveType) {
8113           case IllegalMove:             /* maybe suicide chess, etc. */
8114   if (appData.debugMode) {
8115     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8117     setbuf(debugFP, NULL);
8118   }
8119           case WhitePromotionChancellor:
8120           case BlackPromotionChancellor:
8121           case WhitePromotionArchbishop:
8122           case BlackPromotionArchbishop:
8123           case WhitePromotionQueen:
8124           case BlackPromotionQueen:
8125           case WhitePromotionRook:
8126           case BlackPromotionRook:
8127           case WhitePromotionBishop:
8128           case BlackPromotionBishop:
8129           case WhitePromotionKnight:
8130           case BlackPromotionKnight:
8131           case WhitePromotionKing:
8132           case BlackPromotionKing:
8133           case WhiteNonPromotion:
8134           case BlackNonPromotion:
8135           case NormalMove:
8136           case WhiteCapturesEnPassant:
8137           case BlackCapturesEnPassant:
8138           case WhiteKingSideCastle:
8139           case WhiteQueenSideCastle:
8140           case BlackKingSideCastle:
8141           case BlackQueenSideCastle:
8142           case WhiteKingSideCastleWild:
8143           case WhiteQueenSideCastleWild:
8144           case BlackKingSideCastleWild:
8145           case BlackQueenSideCastleWild:
8146           /* PUSH Fabien */
8147           case WhiteHSideCastleFR:
8148           case WhiteASideCastleFR:
8149           case BlackHSideCastleFR:
8150           case BlackASideCastleFR:
8151           /* POP Fabien */
8152             fromX = currentMoveString[0] - AAA;
8153             fromY = currentMoveString[1] - ONE;
8154             toX = currentMoveString[2] - AAA;
8155             toY = currentMoveString[3] - ONE;
8156             promoChar = currentMoveString[4];
8157             break;
8158           case WhiteDrop:
8159           case BlackDrop:
8160             fromX = moveType == WhiteDrop ?
8161               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8162             (int) CharToPiece(ToLower(currentMoveString[0]));
8163             fromY = DROP_RANK;
8164             toX = currentMoveString[2] - AAA;
8165             toY = currentMoveString[3] - ONE;
8166             promoChar = NULLCHAR;
8167             break;
8168           case AmbiguousMove:
8169             /* bug? */
8170             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8171   if (appData.debugMode) {
8172     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8173     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8174     setbuf(debugFP, NULL);
8175   }
8176             DisplayError(buf, 0);
8177             return;
8178           case ImpossibleMove:
8179             /* bug? */
8180             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8181   if (appData.debugMode) {
8182     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8183     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8184     setbuf(debugFP, NULL);
8185   }
8186             DisplayError(buf, 0);
8187             return;
8188           case (ChessMove) 0:   /* end of file */
8189             if (boardIndex < backwardMostMove) {
8190                 /* Oops, gap.  How did that happen? */
8191                 DisplayError(_("Gap in move list"), 0);
8192                 return;
8193             }
8194             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8195             if (boardIndex > forwardMostMove) {
8196                 forwardMostMove = boardIndex;
8197             }
8198             return;
8199           case ElapsedTime:
8200             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8201                 strcat(parseList[boardIndex-1], " ");
8202                 strcat(parseList[boardIndex-1], yy_text);
8203             }
8204             continue;
8205           case Comment:
8206           case PGNTag:
8207           case NAG:
8208           default:
8209             /* ignore */
8210             continue;
8211           case WhiteWins:
8212           case BlackWins:
8213           case GameIsDrawn:
8214           case GameUnfinished:
8215             if (gameMode == IcsExamining) {
8216                 if (boardIndex < backwardMostMove) {
8217                     /* Oops, gap.  How did that happen? */
8218                     return;
8219                 }
8220                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8221                 return;
8222             }
8223             gameInfo.result = moveType;
8224             p = strchr(yy_text, '{');
8225             if (p == NULL) p = strchr(yy_text, '(');
8226             if (p == NULL) {
8227                 p = yy_text;
8228                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8229             } else {
8230                 q = strchr(p, *p == '{' ? '}' : ')');
8231                 if (q != NULL) *q = NULLCHAR;
8232                 p++;
8233             }
8234             gameInfo.resultDetails = StrSave(p);
8235             continue;
8236         }
8237         if (boardIndex >= forwardMostMove &&
8238             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8239             backwardMostMove = blackPlaysFirst ? 1 : 0;
8240             return;
8241         }
8242         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8243                                  fromY, fromX, toY, toX, promoChar,
8244                                  parseList[boardIndex]);
8245         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8246         /* currentMoveString is set as a side-effect of yylex */
8247         strcpy(moveList[boardIndex], currentMoveString);
8248         strcat(moveList[boardIndex], "\n");
8249         boardIndex++;
8250         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8251         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8252           case MT_NONE:
8253           case MT_STALEMATE:
8254           default:
8255             break;
8256           case MT_CHECK:
8257             if(gameInfo.variant != VariantShogi)
8258                 strcat(parseList[boardIndex - 1], "+");
8259             break;
8260           case MT_CHECKMATE:
8261           case MT_STAINMATE:
8262             strcat(parseList[boardIndex - 1], "#");
8263             break;
8264         }
8265     }
8266 }
8267
8268
8269 /* Apply a move to the given board  */
8270 void
8271 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8272      int fromX, fromY, toX, toY;
8273      int promoChar;
8274      Board board;
8275 {
8276   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8277   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8278
8279     /* [HGM] compute & store e.p. status and castling rights for new position */
8280     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8281
8282       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8283       oldEP = (signed char)board[EP_STATUS];
8284       board[EP_STATUS] = EP_NONE;
8285
8286       if( board[toY][toX] != EmptySquare ) 
8287            board[EP_STATUS] = EP_CAPTURE;  
8288
8289   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8290   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8291        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8292          
8293   if (fromY == DROP_RANK) {
8294         /* must be first */
8295         piece = board[toY][toX] = (ChessSquare) fromX;
8296   } else {
8297       int i;
8298
8299       if( board[fromY][fromX] == WhitePawn ) {
8300            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8301                board[EP_STATUS] = EP_PAWN_MOVE;
8302            if( toY-fromY==2) {
8303                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8304                         gameInfo.variant != VariantBerolina || toX < fromX)
8305                       board[EP_STATUS] = toX | berolina;
8306                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8307                         gameInfo.variant != VariantBerolina || toX > fromX) 
8308                       board[EP_STATUS] = toX;
8309            }
8310       } else 
8311       if( board[fromY][fromX] == BlackPawn ) {
8312            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8313                board[EP_STATUS] = EP_PAWN_MOVE; 
8314            if( toY-fromY== -2) {
8315                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8316                         gameInfo.variant != VariantBerolina || toX < fromX)
8317                       board[EP_STATUS] = toX | berolina;
8318                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8319                         gameInfo.variant != VariantBerolina || toX > fromX) 
8320                       board[EP_STATUS] = toX;
8321            }
8322        }
8323
8324        for(i=0; i<nrCastlingRights; i++) {
8325            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8326               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8327              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8328        }
8329
8330      if (fromX == toX && fromY == toY) return;
8331
8332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8334      if(gameInfo.variant == VariantKnightmate)
8335          king += (int) WhiteUnicorn - (int) WhiteKing;
8336
8337     /* Code added by Tord: */
8338     /* FRC castling assumed when king captures friendly rook. */
8339     if (board[fromY][fromX] == WhiteKing &&
8340              board[toY][toX] == WhiteRook) {
8341       board[fromY][fromX] = EmptySquare;
8342       board[toY][toX] = EmptySquare;
8343       if(toX > fromX) {
8344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8345       } else {
8346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8347       }
8348     } else if (board[fromY][fromX] == BlackKing &&
8349                board[toY][toX] == BlackRook) {
8350       board[fromY][fromX] = EmptySquare;
8351       board[toY][toX] = EmptySquare;
8352       if(toX > fromX) {
8353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8354       } else {
8355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8356       }
8357     /* End of code added by Tord */
8358
8359     } else if (board[fromY][fromX] == king
8360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8361         && toY == fromY && toX > fromX+1) {
8362         board[fromY][fromX] = EmptySquare;
8363         board[toY][toX] = king;
8364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8365         board[fromY][BOARD_RGHT-1] = EmptySquare;
8366     } else if (board[fromY][fromX] == king
8367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8368                && toY == fromY && toX < fromX-1) {
8369         board[fromY][fromX] = EmptySquare;
8370         board[toY][toX] = king;
8371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8372         board[fromY][BOARD_LEFT] = EmptySquare;
8373     } else if (board[fromY][fromX] == WhitePawn
8374                && toY >= BOARD_HEIGHT-promoRank
8375                && gameInfo.variant != VariantXiangqi
8376                ) {
8377         /* white pawn promotion */
8378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8379         if (board[toY][toX] == EmptySquare) {
8380             board[toY][toX] = WhiteQueen;
8381         }
8382         if(gameInfo.variant==VariantBughouse ||
8383            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8384             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8385         board[fromY][fromX] = EmptySquare;
8386     } else if ((fromY == BOARD_HEIGHT-4)
8387                && (toX != fromX)
8388                && gameInfo.variant != VariantXiangqi
8389                && gameInfo.variant != VariantBerolina
8390                && (board[fromY][fromX] == WhitePawn)
8391                && (board[toY][toX] == EmptySquare)) {
8392         board[fromY][fromX] = EmptySquare;
8393         board[toY][toX] = WhitePawn;
8394         captured = board[toY - 1][toX];
8395         board[toY - 1][toX] = EmptySquare;
8396     } else if ((fromY == BOARD_HEIGHT-4)
8397                && (toX == fromX)
8398                && gameInfo.variant == VariantBerolina
8399                && (board[fromY][fromX] == WhitePawn)
8400                && (board[toY][toX] == EmptySquare)) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = WhitePawn;
8403         if(oldEP & EP_BEROLIN_A) {
8404                 captured = board[fromY][fromX-1];
8405                 board[fromY][fromX-1] = EmptySquare;
8406         }else{  captured = board[fromY][fromX+1];
8407                 board[fromY][fromX+1] = EmptySquare;
8408         }
8409     } else if (board[fromY][fromX] == king
8410         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8411                && toY == fromY && toX > fromX+1) {
8412         board[fromY][fromX] = EmptySquare;
8413         board[toY][toX] = king;
8414         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8415         board[fromY][BOARD_RGHT-1] = EmptySquare;
8416     } else if (board[fromY][fromX] == king
8417         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8418                && toY == fromY && toX < fromX-1) {
8419         board[fromY][fromX] = EmptySquare;
8420         board[toY][toX] = king;
8421         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8422         board[fromY][BOARD_LEFT] = EmptySquare;
8423     } else if (fromY == 7 && fromX == 3
8424                && board[fromY][fromX] == BlackKing
8425                && toY == 7 && toX == 5) {
8426         board[fromY][fromX] = EmptySquare;
8427         board[toY][toX] = BlackKing;
8428         board[fromY][7] = EmptySquare;
8429         board[toY][4] = BlackRook;
8430     } else if (fromY == 7 && fromX == 3
8431                && board[fromY][fromX] == BlackKing
8432                && toY == 7 && toX == 1) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = BlackKing;
8435         board[fromY][0] = EmptySquare;
8436         board[toY][2] = BlackRook;
8437     } else if (board[fromY][fromX] == BlackPawn
8438                && toY < promoRank
8439                && gameInfo.variant != VariantXiangqi
8440                ) {
8441         /* black pawn promotion */
8442         board[toY][toX] = CharToPiece(ToLower(promoChar));
8443         if (board[toY][toX] == EmptySquare) {
8444             board[toY][toX] = BlackQueen;
8445         }
8446         if(gameInfo.variant==VariantBughouse ||
8447            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8448             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8449         board[fromY][fromX] = EmptySquare;
8450     } else if ((fromY == 3)
8451                && (toX != fromX)
8452                && gameInfo.variant != VariantXiangqi
8453                && gameInfo.variant != VariantBerolina
8454                && (board[fromY][fromX] == BlackPawn)
8455                && (board[toY][toX] == EmptySquare)) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = BlackPawn;
8458         captured = board[toY + 1][toX];
8459         board[toY + 1][toX] = EmptySquare;
8460     } else if ((fromY == 3)
8461                && (toX == fromX)
8462                && gameInfo.variant == VariantBerolina
8463                && (board[fromY][fromX] == BlackPawn)
8464                && (board[toY][toX] == EmptySquare)) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackPawn;
8467         if(oldEP & EP_BEROLIN_A) {
8468                 captured = board[fromY][fromX-1];
8469                 board[fromY][fromX-1] = EmptySquare;
8470         }else{  captured = board[fromY][fromX+1];
8471                 board[fromY][fromX+1] = EmptySquare;
8472         }
8473     } else {
8474         board[toY][toX] = board[fromY][fromX];
8475         board[fromY][fromX] = EmptySquare;
8476     }
8477
8478     /* [HGM] now we promote for Shogi, if needed */
8479     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8480         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8481   }
8482
8483     if (gameInfo.holdingsWidth != 0) {
8484
8485       /* !!A lot more code needs to be written to support holdings  */
8486       /* [HGM] OK, so I have written it. Holdings are stored in the */
8487       /* penultimate board files, so they are automaticlly stored   */
8488       /* in the game history.                                       */
8489       if (fromY == DROP_RANK) {
8490         /* Delete from holdings, by decreasing count */
8491         /* and erasing image if necessary            */
8492         p = (int) fromX;
8493         if(p < (int) BlackPawn) { /* white drop */
8494              p -= (int)WhitePawn;
8495                  p = PieceToNumber((ChessSquare)p);
8496              if(p >= gameInfo.holdingsSize) p = 0;
8497              if(--board[p][BOARD_WIDTH-2] <= 0)
8498                   board[p][BOARD_WIDTH-1] = EmptySquare;
8499              if((int)board[p][BOARD_WIDTH-2] < 0)
8500                         board[p][BOARD_WIDTH-2] = 0;
8501         } else {                  /* black drop */
8502              p -= (int)BlackPawn;
8503                  p = PieceToNumber((ChessSquare)p);
8504              if(p >= gameInfo.holdingsSize) p = 0;
8505              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8506                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8507              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8508                         board[BOARD_HEIGHT-1-p][1] = 0;
8509         }
8510       }
8511       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8512           && gameInfo.variant != VariantBughouse        ) {
8513         /* [HGM] holdings: Add to holdings, if holdings exist */
8514         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8515                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8516                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8517         }
8518         p = (int) captured;
8519         if (p >= (int) BlackPawn) {
8520           p -= (int)BlackPawn;
8521           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8522                   /* in Shogi restore piece to its original  first */
8523                   captured = (ChessSquare) (DEMOTED captured);
8524                   p = DEMOTED p;
8525           }
8526           p = PieceToNumber((ChessSquare)p);
8527           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8528           board[p][BOARD_WIDTH-2]++;
8529           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8530         } else {
8531           p -= (int)WhitePawn;
8532           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8533                   captured = (ChessSquare) (DEMOTED captured);
8534                   p = DEMOTED p;
8535           }
8536           p = PieceToNumber((ChessSquare)p);
8537           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8538           board[BOARD_HEIGHT-1-p][1]++;
8539           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8540         }
8541       }
8542     } else if (gameInfo.variant == VariantAtomic) {
8543       if (captured != EmptySquare) {
8544         int y, x;
8545         for (y = toY-1; y <= toY+1; y++) {
8546           for (x = toX-1; x <= toX+1; x++) {
8547             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8548                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8549               board[y][x] = EmptySquare;
8550             }
8551           }
8552         }
8553         board[toY][toX] = EmptySquare;
8554       }
8555     }
8556     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8557         /* [HGM] Shogi promotions */
8558         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8559     }
8560
8561     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8562                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8563         // [HGM] superchess: take promotion piece out of holdings
8564         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8565         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8566             if(!--board[k][BOARD_WIDTH-2])
8567                 board[k][BOARD_WIDTH-1] = EmptySquare;
8568         } else {
8569             if(!--board[BOARD_HEIGHT-1-k][1])
8570                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8571         }
8572     }
8573
8574 }
8575
8576 /* Updates forwardMostMove */
8577 void
8578 MakeMove(fromX, fromY, toX, toY, promoChar)
8579      int fromX, fromY, toX, toY;
8580      int promoChar;
8581 {
8582 //    forwardMostMove++; // [HGM] bare: moved downstream
8583
8584     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8585         int timeLeft; static int lastLoadFlag=0; int king, piece;
8586         piece = boards[forwardMostMove][fromY][fromX];
8587         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8588         if(gameInfo.variant == VariantKnightmate)
8589             king += (int) WhiteUnicorn - (int) WhiteKing;
8590         if(forwardMostMove == 0) {
8591             if(blackPlaysFirst) 
8592                 fprintf(serverMoves, "%s;", second.tidy);
8593             fprintf(serverMoves, "%s;", first.tidy);
8594             if(!blackPlaysFirst) 
8595                 fprintf(serverMoves, "%s;", second.tidy);
8596         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8597         lastLoadFlag = loadFlag;
8598         // print base move
8599         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8600         // print castling suffix
8601         if( toY == fromY && piece == king ) {
8602             if(toX-fromX > 1)
8603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8604             if(fromX-toX >1)
8605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8606         }
8607         // e.p. suffix
8608         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8609              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8610              boards[forwardMostMove][toY][toX] == EmptySquare
8611              && fromX != toX && fromY != toY)
8612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8613         // promotion suffix
8614         if(promoChar != NULLCHAR)
8615                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8616         if(!loadFlag) {
8617             fprintf(serverMoves, "/%d/%d",
8618                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8619             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8620             else                      timeLeft = blackTimeRemaining/1000;
8621             fprintf(serverMoves, "/%d", timeLeft);
8622         }
8623         fflush(serverMoves);
8624     }
8625
8626     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8627       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8628                         0, 1);
8629       return;
8630     }
8631     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8632     if (commentList[forwardMostMove+1] != NULL) {
8633         free(commentList[forwardMostMove+1]);
8634         commentList[forwardMostMove+1] = NULL;
8635     }
8636     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8637     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8638     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8639     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8640     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8641     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8642     gameInfo.result = GameUnfinished;
8643     if (gameInfo.resultDetails != NULL) {
8644         free(gameInfo.resultDetails);
8645         gameInfo.resultDetails = NULL;
8646     }
8647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8648                               moveList[forwardMostMove - 1]);
8649     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8650                              PosFlags(forwardMostMove - 1),
8651                              fromY, fromX, toY, toX, promoChar,
8652                              parseList[forwardMostMove - 1]);
8653     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8654       case MT_NONE:
8655       case MT_STALEMATE:
8656       default:
8657         break;
8658       case MT_CHECK:
8659         if(gameInfo.variant != VariantShogi)
8660             strcat(parseList[forwardMostMove - 1], "+");
8661         break;
8662       case MT_CHECKMATE:
8663       case MT_STAINMATE:
8664         strcat(parseList[forwardMostMove - 1], "#");
8665         break;
8666     }
8667     if (appData.debugMode) {
8668         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8669     }
8670
8671 }
8672
8673 /* Updates currentMove if not pausing */
8674 void
8675 ShowMove(fromX, fromY, toX, toY)
8676 {
8677     int instant = (gameMode == PlayFromGameFile) ?
8678         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8679     if(appData.noGUI) return;
8680     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8681         if (!instant) {
8682             if (forwardMostMove == currentMove + 1) {
8683                 AnimateMove(boards[forwardMostMove - 1],
8684                             fromX, fromY, toX, toY);
8685             }
8686             if (appData.highlightLastMove) {
8687                 SetHighlights(fromX, fromY, toX, toY);
8688             }
8689         }
8690         currentMove = forwardMostMove;
8691     }
8692
8693     if (instant) return;
8694
8695     DisplayMove(currentMove - 1);
8696     DrawPosition(FALSE, boards[currentMove]);
8697     DisplayBothClocks();
8698     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8699 }
8700
8701 void SendEgtPath(ChessProgramState *cps)
8702 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8703         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8704
8705         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8706
8707         while(*p) {
8708             char c, *q = name+1, *r, *s;
8709
8710             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8711             while(*p && *p != ',') *q++ = *p++;
8712             *q++ = ':'; *q = 0;
8713             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8714                 strcmp(name, ",nalimov:") == 0 ) {
8715                 // take nalimov path from the menu-changeable option first, if it is defined
8716                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8717                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8718             } else
8719             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8720                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8721                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8722                 s = r = StrStr(s, ":") + 1; // beginning of path info
8723                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8724                 c = *r; *r = 0;             // temporarily null-terminate path info
8725                     *--q = 0;               // strip of trailig ':' from name
8726                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8727                 *r = c;
8728                 SendToProgram(buf,cps);     // send egtbpath command for this format
8729             }
8730             if(*p == ',') p++; // read away comma to position for next format name
8731         }
8732 }
8733
8734 void
8735 InitChessProgram(cps, setup)
8736      ChessProgramState *cps;
8737      int setup; /* [HGM] needed to setup FRC opening position */
8738 {
8739     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8740     if (appData.noChessProgram) return;
8741     hintRequested = FALSE;
8742     bookRequested = FALSE;
8743
8744     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8745     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8746     if(cps->memSize) { /* [HGM] memory */
8747         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8748         SendToProgram(buf, cps);
8749     }
8750     SendEgtPath(cps); /* [HGM] EGT */
8751     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8752         sprintf(buf, "cores %d\n", appData.smpCores);
8753         SendToProgram(buf, cps);
8754     }
8755
8756     SendToProgram(cps->initString, cps);
8757     if (gameInfo.variant != VariantNormal &&
8758         gameInfo.variant != VariantLoadable
8759         /* [HGM] also send variant if board size non-standard */
8760         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8761                                             ) {
8762       char *v = VariantName(gameInfo.variant);
8763       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8764         /* [HGM] in protocol 1 we have to assume all variants valid */
8765         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8766         DisplayFatalError(buf, 0, 1);
8767         return;
8768       }
8769
8770       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8771       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8772       if( gameInfo.variant == VariantXiangqi )
8773            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8774       if( gameInfo.variant == VariantShogi )
8775            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8776       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8777            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8778       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8779                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8780            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8781       if( gameInfo.variant == VariantCourier )
8782            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8783       if( gameInfo.variant == VariantSuper )
8784            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8785       if( gameInfo.variant == VariantGreat )
8786            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8787
8788       if(overruled) {
8789            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8790                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8791            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8792            if(StrStr(cps->variants, b) == NULL) { 
8793                // specific sized variant not known, check if general sizing allowed
8794                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8795                    if(StrStr(cps->variants, "boardsize") == NULL) {
8796                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8797                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8798                        DisplayFatalError(buf, 0, 1);
8799                        return;
8800                    }
8801                    /* [HGM] here we really should compare with the maximum supported board size */
8802                }
8803            }
8804       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8805       sprintf(buf, "variant %s\n", b);
8806       SendToProgram(buf, cps);
8807     }
8808     currentlyInitializedVariant = gameInfo.variant;
8809
8810     /* [HGM] send opening position in FRC to first engine */
8811     if(setup) {
8812           SendToProgram("force\n", cps);
8813           SendBoard(cps, 0);
8814           /* engine is now in force mode! Set flag to wake it up after first move. */
8815           setboardSpoiledMachineBlack = 1;
8816     }
8817
8818     if (cps->sendICS) {
8819       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8820       SendToProgram(buf, cps);
8821     }
8822     cps->maybeThinking = FALSE;
8823     cps->offeredDraw = 0;
8824     if (!appData.icsActive) {
8825         SendTimeControl(cps, movesPerSession, timeControl,
8826                         timeIncrement, appData.searchDepth,
8827                         searchTime);
8828     }
8829     if (appData.showThinking 
8830         // [HGM] thinking: four options require thinking output to be sent
8831         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8832                                 ) {
8833         SendToProgram("post\n", cps);
8834     }
8835     SendToProgram("hard\n", cps);
8836     if (!appData.ponderNextMove) {
8837         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8838            it without being sure what state we are in first.  "hard"
8839            is not a toggle, so that one is OK.
8840          */
8841         SendToProgram("easy\n", cps);
8842     }
8843     if (cps->usePing) {
8844       sprintf(buf, "ping %d\n", ++cps->lastPing);
8845       SendToProgram(buf, cps);
8846     }
8847     cps->initDone = TRUE;
8848 }   
8849
8850
8851 void
8852 StartChessProgram(cps)
8853      ChessProgramState *cps;
8854 {
8855     char buf[MSG_SIZ];
8856     int err;
8857
8858     if (appData.noChessProgram) return;
8859     cps->initDone = FALSE;
8860
8861     if (strcmp(cps->host, "localhost") == 0) {
8862         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8863     } else if (*appData.remoteShell == NULLCHAR) {
8864         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8865     } else {
8866         if (*appData.remoteUser == NULLCHAR) {
8867           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8868                     cps->program);
8869         } else {
8870           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8871                     cps->host, appData.remoteUser, cps->program);
8872         }
8873         err = StartChildProcess(buf, "", &cps->pr);
8874     }
8875     
8876     if (err != 0) {
8877         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8878         DisplayFatalError(buf, err, 1);
8879         cps->pr = NoProc;
8880         cps->isr = NULL;
8881         return;
8882     }
8883     
8884     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8885     if (cps->protocolVersion > 1) {
8886       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8887       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8888       cps->comboCnt = 0;  //                and values of combo boxes
8889       SendToProgram(buf, cps);
8890     } else {
8891       SendToProgram("xboard\n", cps);
8892     }
8893 }
8894
8895
8896 void
8897 TwoMachinesEventIfReady P((void))
8898 {
8899   if (first.lastPing != first.lastPong) {
8900     DisplayMessage("", _("Waiting for first chess program"));
8901     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8902     return;
8903   }
8904   if (second.lastPing != second.lastPong) {
8905     DisplayMessage("", _("Waiting for second chess program"));
8906     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8907     return;
8908   }
8909   ThawUI();
8910   TwoMachinesEvent();
8911 }
8912
8913 void
8914 NextMatchGame P((void))
8915 {
8916     int index; /* [HGM] autoinc: step load index during match */
8917     Reset(FALSE, TRUE);
8918     if (*appData.loadGameFile != NULLCHAR) {
8919         index = appData.loadGameIndex;
8920         if(index < 0) { // [HGM] autoinc
8921             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8922             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8923         } 
8924         LoadGameFromFile(appData.loadGameFile,
8925                          index,
8926                          appData.loadGameFile, FALSE);
8927     } else if (*appData.loadPositionFile != NULLCHAR) {
8928         index = appData.loadPositionIndex;
8929         if(index < 0) { // [HGM] autoinc
8930             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8931             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8932         } 
8933         LoadPositionFromFile(appData.loadPositionFile,
8934                              index,
8935                              appData.loadPositionFile);
8936     }
8937     TwoMachinesEventIfReady();
8938 }
8939
8940 void UserAdjudicationEvent( int result )
8941 {
8942     ChessMove gameResult = GameIsDrawn;
8943
8944     if( result > 0 ) {
8945         gameResult = WhiteWins;
8946     }
8947     else if( result < 0 ) {
8948         gameResult = BlackWins;
8949     }
8950
8951     if( gameMode == TwoMachinesPlay ) {
8952         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8953     }
8954 }
8955
8956
8957 // [HGM] save: calculate checksum of game to make games easily identifiable
8958 int StringCheckSum(char *s)
8959 {
8960         int i = 0;
8961         if(s==NULL) return 0;
8962         while(*s) i = i*259 + *s++;
8963         return i;
8964 }
8965
8966 int GameCheckSum()
8967 {
8968         int i, sum=0;
8969         for(i=backwardMostMove; i<forwardMostMove; i++) {
8970                 sum += pvInfoList[i].depth;
8971                 sum += StringCheckSum(parseList[i]);
8972                 sum += StringCheckSum(commentList[i]);
8973                 sum *= 261;
8974         }
8975         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8976         return sum + StringCheckSum(commentList[i]);
8977 } // end of save patch
8978
8979 void
8980 GameEnds(result, resultDetails, whosays)
8981      ChessMove result;
8982      char *resultDetails;
8983      int whosays;
8984 {
8985     GameMode nextGameMode;
8986     int isIcsGame;
8987     char buf[MSG_SIZ], popupRequested = 0;
8988
8989     if(endingGame) return; /* [HGM] crash: forbid recursion */
8990     endingGame = 1;
8991     if(twoBoards) { // [HGM] dual: switch back to one board
8992         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8993         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8994     }
8995     if (appData.debugMode) {
8996       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8997               result, resultDetails ? resultDetails : "(null)", whosays);
8998     }
8999
9000     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9001
9002     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9003         /* If we are playing on ICS, the server decides when the
9004            game is over, but the engine can offer to draw, claim 
9005            a draw, or resign. 
9006          */
9007 #if ZIPPY
9008         if (appData.zippyPlay && first.initDone) {
9009             if (result == GameIsDrawn) {
9010                 /* In case draw still needs to be claimed */
9011                 SendToICS(ics_prefix);
9012                 SendToICS("draw\n");
9013             } else if (StrCaseStr(resultDetails, "resign")) {
9014                 SendToICS(ics_prefix);
9015                 SendToICS("resign\n");
9016             }
9017         }
9018 #endif
9019         endingGame = 0; /* [HGM] crash */
9020         return;
9021     }
9022
9023     /* If we're loading the game from a file, stop */
9024     if (whosays == GE_FILE) {
9025       (void) StopLoadGameTimer();
9026       gameFileFP = NULL;
9027     }
9028
9029     /* Cancel draw offers */
9030     first.offeredDraw = second.offeredDraw = 0;
9031
9032     /* If this is an ICS game, only ICS can really say it's done;
9033        if not, anyone can. */
9034     isIcsGame = (gameMode == IcsPlayingWhite || 
9035                  gameMode == IcsPlayingBlack || 
9036                  gameMode == IcsObserving    || 
9037                  gameMode == IcsExamining);
9038
9039     if (!isIcsGame || whosays == GE_ICS) {
9040         /* OK -- not an ICS game, or ICS said it was done */
9041         StopClocks();
9042         if (!isIcsGame && !appData.noChessProgram) 
9043           SetUserThinkingEnables();
9044     
9045         /* [HGM] if a machine claims the game end we verify this claim */
9046         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9047             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9048                 char claimer;
9049                 ChessMove trueResult = (ChessMove) -1;
9050
9051                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9052                                             first.twoMachinesColor[0] :
9053                                             second.twoMachinesColor[0] ;
9054
9055                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9056                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9057                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9058                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9059                 } else
9060                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9061                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9062                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9063                 } else
9064                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9065                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9066                 }
9067
9068                 // now verify win claims, but not in drop games, as we don't understand those yet
9069                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9070                                                  || gameInfo.variant == VariantGreat) &&
9071                     (result == WhiteWins && claimer == 'w' ||
9072                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9073                       if (appData.debugMode) {
9074                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9075                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9076                       }
9077                       if(result != trueResult) {
9078                               sprintf(buf, "False win claim: '%s'", resultDetails);
9079                               result = claimer == 'w' ? BlackWins : WhiteWins;
9080                               resultDetails = buf;
9081                       }
9082                 } else
9083                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9084                     && (forwardMostMove <= backwardMostMove ||
9085                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9086                         (claimer=='b')==(forwardMostMove&1))
9087                                                                                   ) {
9088                       /* [HGM] verify: draws that were not flagged are false claims */
9089                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9090                       result = claimer == 'w' ? BlackWins : WhiteWins;
9091                       resultDetails = buf;
9092                 }
9093                 /* (Claiming a loss is accepted no questions asked!) */
9094             }
9095             /* [HGM] bare: don't allow bare King to win */
9096             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9097                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9098                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9099                && result != GameIsDrawn)
9100             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9101                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9102                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9103                         if(p >= 0 && p <= (int)WhiteKing) k++;
9104                 }
9105                 if (appData.debugMode) {
9106                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9107                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9108                 }
9109                 if(k <= 1) {
9110                         result = GameIsDrawn;
9111                         sprintf(buf, "%s but bare king", resultDetails);
9112                         resultDetails = buf;
9113                 }
9114             }
9115         }
9116
9117
9118         if(serverMoves != NULL && !loadFlag) { char c = '=';
9119             if(result==WhiteWins) c = '+';
9120             if(result==BlackWins) c = '-';
9121             if(resultDetails != NULL)
9122                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9123         }
9124         if (resultDetails != NULL) {
9125             gameInfo.result = result;
9126             gameInfo.resultDetails = StrSave(resultDetails);
9127
9128             /* display last move only if game was not loaded from file */
9129             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9130                 DisplayMove(currentMove - 1);
9131     
9132             if (forwardMostMove != 0) {
9133                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9134                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9135                                                                 ) {
9136                     if (*appData.saveGameFile != NULLCHAR) {
9137                         SaveGameToFile(appData.saveGameFile, TRUE);
9138                     } else if (appData.autoSaveGames) {
9139                         AutoSaveGame();
9140                     }
9141                     if (*appData.savePositionFile != NULLCHAR) {
9142                         SavePositionToFile(appData.savePositionFile);
9143                     }
9144                 }
9145             }
9146
9147             /* Tell program how game ended in case it is learning */
9148             /* [HGM] Moved this to after saving the PGN, just in case */
9149             /* engine died and we got here through time loss. In that */
9150             /* case we will get a fatal error writing the pipe, which */
9151             /* would otherwise lose us the PGN.                       */
9152             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9153             /* output during GameEnds should never be fatal anymore   */
9154             if (gameMode == MachinePlaysWhite ||
9155                 gameMode == MachinePlaysBlack ||
9156                 gameMode == TwoMachinesPlay ||
9157                 gameMode == IcsPlayingWhite ||
9158                 gameMode == IcsPlayingBlack ||
9159                 gameMode == BeginningOfGame) {
9160                 char buf[MSG_SIZ];
9161                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9162                         resultDetails);
9163                 if (first.pr != NoProc) {
9164                     SendToProgram(buf, &first);
9165                 }
9166                 if (second.pr != NoProc &&
9167                     gameMode == TwoMachinesPlay) {
9168                     SendToProgram(buf, &second);
9169                 }
9170             }
9171         }
9172
9173         if (appData.icsActive) {
9174             if (appData.quietPlay &&
9175                 (gameMode == IcsPlayingWhite ||
9176                  gameMode == IcsPlayingBlack)) {
9177                 SendToICS(ics_prefix);
9178                 SendToICS("set shout 1\n");
9179             }
9180             nextGameMode = IcsIdle;
9181             ics_user_moved = FALSE;
9182             /* clean up premove.  It's ugly when the game has ended and the
9183              * premove highlights are still on the board.
9184              */
9185             if (gotPremove) {
9186               gotPremove = FALSE;
9187               ClearPremoveHighlights();
9188               DrawPosition(FALSE, boards[currentMove]);
9189             }
9190             if (whosays == GE_ICS) {
9191                 switch (result) {
9192                 case WhiteWins:
9193                     if (gameMode == IcsPlayingWhite)
9194                         PlayIcsWinSound();
9195                     else if(gameMode == IcsPlayingBlack)
9196                         PlayIcsLossSound();
9197                     break;
9198                 case BlackWins:
9199                     if (gameMode == IcsPlayingBlack)
9200                         PlayIcsWinSound();
9201                     else if(gameMode == IcsPlayingWhite)
9202                         PlayIcsLossSound();
9203                     break;
9204                 case GameIsDrawn:
9205                     PlayIcsDrawSound();
9206                     break;
9207                 default:
9208                     PlayIcsUnfinishedSound();
9209                 }
9210             }
9211         } else if (gameMode == EditGame ||
9212                    gameMode == PlayFromGameFile || 
9213                    gameMode == AnalyzeMode || 
9214                    gameMode == AnalyzeFile) {
9215             nextGameMode = gameMode;
9216         } else {
9217             nextGameMode = EndOfGame;
9218         }
9219         pausing = FALSE;
9220         ModeHighlight();
9221     } else {
9222         nextGameMode = gameMode;
9223     }
9224
9225     if (appData.noChessProgram) {
9226         gameMode = nextGameMode;
9227         ModeHighlight();
9228         endingGame = 0; /* [HGM] crash */
9229         return;
9230     }
9231
9232     if (first.reuse) {
9233         /* Put first chess program into idle state */
9234         if (first.pr != NoProc &&
9235             (gameMode == MachinePlaysWhite ||
9236              gameMode == MachinePlaysBlack ||
9237              gameMode == TwoMachinesPlay ||
9238              gameMode == IcsPlayingWhite ||
9239              gameMode == IcsPlayingBlack ||
9240              gameMode == BeginningOfGame)) {
9241             SendToProgram("force\n", &first);
9242             if (first.usePing) {
9243               char buf[MSG_SIZ];
9244               sprintf(buf, "ping %d\n", ++first.lastPing);
9245               SendToProgram(buf, &first);
9246             }
9247         }
9248     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9249         /* Kill off first chess program */
9250         if (first.isr != NULL)
9251           RemoveInputSource(first.isr);
9252         first.isr = NULL;
9253     
9254         if (first.pr != NoProc) {
9255             ExitAnalyzeMode();
9256             DoSleep( appData.delayBeforeQuit );
9257             SendToProgram("quit\n", &first);
9258             DoSleep( appData.delayAfterQuit );
9259             DestroyChildProcess(first.pr, first.useSigterm);
9260         }
9261         first.pr = NoProc;
9262     }
9263     if (second.reuse) {
9264         /* Put second chess program into idle state */
9265         if (second.pr != NoProc &&
9266             gameMode == TwoMachinesPlay) {
9267             SendToProgram("force\n", &second);
9268             if (second.usePing) {
9269               char buf[MSG_SIZ];
9270               sprintf(buf, "ping %d\n", ++second.lastPing);
9271               SendToProgram(buf, &second);
9272             }
9273         }
9274     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9275         /* Kill off second chess program */
9276         if (second.isr != NULL)
9277           RemoveInputSource(second.isr);
9278         second.isr = NULL;
9279     
9280         if (second.pr != NoProc) {
9281             DoSleep( appData.delayBeforeQuit );
9282             SendToProgram("quit\n", &second);
9283             DoSleep( appData.delayAfterQuit );
9284             DestroyChildProcess(second.pr, second.useSigterm);
9285         }
9286         second.pr = NoProc;
9287     }
9288
9289     if (matchMode && gameMode == TwoMachinesPlay) {
9290         switch (result) {
9291         case WhiteWins:
9292           if (first.twoMachinesColor[0] == 'w') {
9293             first.matchWins++;
9294           } else {
9295             second.matchWins++;
9296           }
9297           break;
9298         case BlackWins:
9299           if (first.twoMachinesColor[0] == 'b') {
9300             first.matchWins++;
9301           } else {
9302             second.matchWins++;
9303           }
9304           break;
9305         default:
9306           break;
9307         }
9308         if (matchGame < appData.matchGames) {
9309             char *tmp;
9310             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9311                 tmp = first.twoMachinesColor;
9312                 first.twoMachinesColor = second.twoMachinesColor;
9313                 second.twoMachinesColor = tmp;
9314             }
9315             gameMode = nextGameMode;
9316             matchGame++;
9317             if(appData.matchPause>10000 || appData.matchPause<10)
9318                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9319             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9320             endingGame = 0; /* [HGM] crash */
9321             return;
9322         } else {
9323             gameMode = nextGameMode;
9324             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9325                     first.tidy, second.tidy,
9326                     first.matchWins, second.matchWins,
9327                     appData.matchGames - (first.matchWins + second.matchWins));
9328             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9329         }
9330     }
9331     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9332         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9333       ExitAnalyzeMode();
9334     gameMode = nextGameMode;
9335     ModeHighlight();
9336     endingGame = 0;  /* [HGM] crash */
9337     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9338       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9339         matchMode = FALSE; appData.matchGames = matchGame = 0;
9340         DisplayNote(buf);
9341       }
9342     }
9343 }
9344
9345 /* Assumes program was just initialized (initString sent).
9346    Leaves program in force mode. */
9347 void
9348 FeedMovesToProgram(cps, upto) 
9349      ChessProgramState *cps;
9350      int upto;
9351 {
9352     int i;
9353     
9354     if (appData.debugMode)
9355       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9356               startedFromSetupPosition ? "position and " : "",
9357               backwardMostMove, upto, cps->which);
9358     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9359         // [HGM] variantswitch: make engine aware of new variant
9360         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9361                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9362         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9363         SendToProgram(buf, cps);
9364         currentlyInitializedVariant = gameInfo.variant;
9365     }
9366     SendToProgram("force\n", cps);
9367     if (startedFromSetupPosition) {
9368         SendBoard(cps, backwardMostMove);
9369     if (appData.debugMode) {
9370         fprintf(debugFP, "feedMoves\n");
9371     }
9372     }
9373     for (i = backwardMostMove; i < upto; i++) {
9374         SendMoveToProgram(i, cps);
9375     }
9376 }
9377
9378
9379 void
9380 ResurrectChessProgram()
9381 {
9382      /* The chess program may have exited.
9383         If so, restart it and feed it all the moves made so far. */
9384
9385     if (appData.noChessProgram || first.pr != NoProc) return;
9386     
9387     StartChessProgram(&first);
9388     InitChessProgram(&first, FALSE);
9389     FeedMovesToProgram(&first, currentMove);
9390
9391     if (!first.sendTime) {
9392         /* can't tell gnuchess what its clock should read,
9393            so we bow to its notion. */
9394         ResetClocks();
9395         timeRemaining[0][currentMove] = whiteTimeRemaining;
9396         timeRemaining[1][currentMove] = blackTimeRemaining;
9397     }
9398
9399     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9400                 appData.icsEngineAnalyze) && first.analysisSupport) {
9401       SendToProgram("analyze\n", &first);
9402       first.analyzing = TRUE;
9403     }
9404 }
9405
9406 /*
9407  * Button procedures
9408  */
9409 void
9410 Reset(redraw, init)
9411      int redraw, init;
9412 {
9413     int i;
9414
9415     if (appData.debugMode) {
9416         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9417                 redraw, init, gameMode);
9418     }
9419     CleanupTail(); // [HGM] vari: delete any stored variations
9420     pausing = pauseExamInvalid = FALSE;
9421     startedFromSetupPosition = blackPlaysFirst = FALSE;
9422     firstMove = TRUE;
9423     whiteFlag = blackFlag = FALSE;
9424     userOfferedDraw = FALSE;
9425     hintRequested = bookRequested = FALSE;
9426     first.maybeThinking = FALSE;
9427     second.maybeThinking = FALSE;
9428     first.bookSuspend = FALSE; // [HGM] book
9429     second.bookSuspend = FALSE;
9430     thinkOutput[0] = NULLCHAR;
9431     lastHint[0] = NULLCHAR;
9432     ClearGameInfo(&gameInfo);
9433     gameInfo.variant = StringToVariant(appData.variant);
9434     ics_user_moved = ics_clock_paused = FALSE;
9435     ics_getting_history = H_FALSE;
9436     ics_gamenum = -1;
9437     white_holding[0] = black_holding[0] = NULLCHAR;
9438     ClearProgramStats();
9439     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9440     
9441     ResetFrontEnd();
9442     ClearHighlights();
9443     flipView = appData.flipView;
9444     ClearPremoveHighlights();
9445     gotPremove = FALSE;
9446     alarmSounded = FALSE;
9447
9448     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9449     if(appData.serverMovesName != NULL) {
9450         /* [HGM] prepare to make moves file for broadcasting */
9451         clock_t t = clock();
9452         if(serverMoves != NULL) fclose(serverMoves);
9453         serverMoves = fopen(appData.serverMovesName, "r");
9454         if(serverMoves != NULL) {
9455             fclose(serverMoves);
9456             /* delay 15 sec before overwriting, so all clients can see end */
9457             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9458         }
9459         serverMoves = fopen(appData.serverMovesName, "w");
9460     }
9461
9462     ExitAnalyzeMode();
9463     gameMode = BeginningOfGame;
9464     ModeHighlight();
9465     if(appData.icsActive) gameInfo.variant = VariantNormal;
9466     currentMove = forwardMostMove = backwardMostMove = 0;
9467     InitPosition(redraw);
9468     for (i = 0; i < MAX_MOVES; i++) {
9469         if (commentList[i] != NULL) {
9470             free(commentList[i]);
9471             commentList[i] = NULL;
9472         }
9473     }
9474     ResetClocks();
9475     timeRemaining[0][0] = whiteTimeRemaining;
9476     timeRemaining[1][0] = blackTimeRemaining;
9477     if (first.pr == NULL) {
9478         StartChessProgram(&first);
9479     }
9480     if (init) {
9481             InitChessProgram(&first, startedFromSetupPosition);
9482     }
9483     DisplayTitle("");
9484     DisplayMessage("", "");
9485     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9486     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9487 }
9488
9489 void
9490 AutoPlayGameLoop()
9491 {
9492     for (;;) {
9493         if (!AutoPlayOneMove())
9494           return;
9495         if (matchMode || appData.timeDelay == 0)
9496           continue;
9497         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9498           return;
9499         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9500         break;
9501     }
9502 }
9503
9504
9505 int
9506 AutoPlayOneMove()
9507 {
9508     int fromX, fromY, toX, toY;
9509
9510     if (appData.debugMode) {
9511       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9512     }
9513
9514     if (gameMode != PlayFromGameFile)
9515       return FALSE;
9516
9517     if (currentMove >= forwardMostMove) {
9518       gameMode = EditGame;
9519       ModeHighlight();
9520
9521       /* [AS] Clear current move marker at the end of a game */
9522       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9523
9524       return FALSE;
9525     }
9526     
9527     toX = moveList[currentMove][2] - AAA;
9528     toY = moveList[currentMove][3] - ONE;
9529
9530     if (moveList[currentMove][1] == '@') {
9531         if (appData.highlightLastMove) {
9532             SetHighlights(-1, -1, toX, toY);
9533         }
9534     } else {
9535         fromX = moveList[currentMove][0] - AAA;
9536         fromY = moveList[currentMove][1] - ONE;
9537
9538         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9539
9540         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9541
9542         if (appData.highlightLastMove) {
9543             SetHighlights(fromX, fromY, toX, toY);
9544         }
9545     }
9546     DisplayMove(currentMove);
9547     SendMoveToProgram(currentMove++, &first);
9548     DisplayBothClocks();
9549     DrawPosition(FALSE, boards[currentMove]);
9550     // [HGM] PV info: always display, routine tests if empty
9551     DisplayComment(currentMove - 1, commentList[currentMove]);
9552     return TRUE;
9553 }
9554
9555
9556 int
9557 LoadGameOneMove(readAhead)
9558      ChessMove readAhead;
9559 {
9560     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9561     char promoChar = NULLCHAR;
9562     ChessMove moveType;
9563     char move[MSG_SIZ];
9564     char *p, *q;
9565     
9566     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9567         gameMode != AnalyzeMode && gameMode != Training) {
9568         gameFileFP = NULL;
9569         return FALSE;
9570     }
9571     
9572     yyboardindex = forwardMostMove;
9573     if (readAhead != (ChessMove)0) {
9574       moveType = readAhead;
9575     } else {
9576       if (gameFileFP == NULL)
9577           return FALSE;
9578       moveType = (ChessMove) yylex();
9579     }
9580     
9581     done = FALSE;
9582     switch (moveType) {
9583       case Comment:
9584         if (appData.debugMode) 
9585           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9586         p = yy_text;
9587
9588         /* append the comment but don't display it */
9589         AppendComment(currentMove, p, FALSE);
9590         return TRUE;
9591
9592       case WhiteCapturesEnPassant:
9593       case BlackCapturesEnPassant:
9594       case WhitePromotionChancellor:
9595       case BlackPromotionChancellor:
9596       case WhitePromotionArchbishop:
9597       case BlackPromotionArchbishop:
9598       case WhitePromotionCentaur:
9599       case BlackPromotionCentaur:
9600       case WhitePromotionQueen:
9601       case BlackPromotionQueen:
9602       case WhitePromotionRook:
9603       case BlackPromotionRook:
9604       case WhitePromotionBishop:
9605       case BlackPromotionBishop:
9606       case WhitePromotionKnight:
9607       case BlackPromotionKnight:
9608       case WhitePromotionKing:
9609       case BlackPromotionKing:
9610       case WhiteNonPromotion:
9611       case BlackNonPromotion:
9612       case NormalMove:
9613       case WhiteKingSideCastle:
9614       case WhiteQueenSideCastle:
9615       case BlackKingSideCastle:
9616       case BlackQueenSideCastle:
9617       case WhiteKingSideCastleWild:
9618       case WhiteQueenSideCastleWild:
9619       case BlackKingSideCastleWild:
9620       case BlackQueenSideCastleWild:
9621       /* PUSH Fabien */
9622       case WhiteHSideCastleFR:
9623       case WhiteASideCastleFR:
9624       case BlackHSideCastleFR:
9625       case BlackASideCastleFR:
9626       /* POP Fabien */
9627         if (appData.debugMode)
9628           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9629         fromX = currentMoveString[0] - AAA;
9630         fromY = currentMoveString[1] - ONE;
9631         toX = currentMoveString[2] - AAA;
9632         toY = currentMoveString[3] - ONE;
9633         promoChar = currentMoveString[4];
9634         break;
9635
9636       case WhiteDrop:
9637       case BlackDrop:
9638         if (appData.debugMode)
9639           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9640         fromX = moveType == WhiteDrop ?
9641           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9642         (int) CharToPiece(ToLower(currentMoveString[0]));
9643         fromY = DROP_RANK;
9644         toX = currentMoveString[2] - AAA;
9645         toY = currentMoveString[3] - ONE;
9646         break;
9647
9648       case WhiteWins:
9649       case BlackWins:
9650       case GameIsDrawn:
9651       case GameUnfinished:
9652         if (appData.debugMode)
9653           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9654         p = strchr(yy_text, '{');
9655         if (p == NULL) p = strchr(yy_text, '(');
9656         if (p == NULL) {
9657             p = yy_text;
9658             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9659         } else {
9660             q = strchr(p, *p == '{' ? '}' : ')');
9661             if (q != NULL) *q = NULLCHAR;
9662             p++;
9663         }
9664         GameEnds(moveType, p, GE_FILE);
9665         done = TRUE;
9666         if (cmailMsgLoaded) {
9667             ClearHighlights();
9668             flipView = WhiteOnMove(currentMove);
9669             if (moveType == GameUnfinished) flipView = !flipView;
9670             if (appData.debugMode)
9671               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9672         }
9673         break;
9674
9675       case (ChessMove) 0:       /* end of file */
9676         if (appData.debugMode)
9677           fprintf(debugFP, "Parser hit end of file\n");
9678         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9679           case MT_NONE:
9680           case MT_CHECK:
9681             break;
9682           case MT_CHECKMATE:
9683           case MT_STAINMATE:
9684             if (WhiteOnMove(currentMove)) {
9685                 GameEnds(BlackWins, "Black mates", GE_FILE);
9686             } else {
9687                 GameEnds(WhiteWins, "White mates", GE_FILE);
9688             }
9689             break;
9690           case MT_STALEMATE:
9691             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9692             break;
9693         }
9694         done = TRUE;
9695         break;
9696
9697       case MoveNumberOne:
9698         if (lastLoadGameStart == GNUChessGame) {
9699             /* GNUChessGames have numbers, but they aren't move numbers */
9700             if (appData.debugMode)
9701               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9702                       yy_text, (int) moveType);
9703             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9704         }
9705         /* else fall thru */
9706
9707       case XBoardGame:
9708       case GNUChessGame:
9709       case PGNTag:
9710         /* Reached start of next game in file */
9711         if (appData.debugMode)
9712           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9713         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9714           case MT_NONE:
9715           case MT_CHECK:
9716             break;
9717           case MT_CHECKMATE:
9718           case MT_STAINMATE:
9719             if (WhiteOnMove(currentMove)) {
9720                 GameEnds(BlackWins, "Black mates", GE_FILE);
9721             } else {
9722                 GameEnds(WhiteWins, "White mates", GE_FILE);
9723             }
9724             break;
9725           case MT_STALEMATE:
9726             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9727             break;
9728         }
9729         done = TRUE;
9730         break;
9731
9732       case PositionDiagram:     /* should not happen; ignore */
9733       case ElapsedTime:         /* ignore */
9734       case NAG:                 /* ignore */
9735         if (appData.debugMode)
9736           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9737                   yy_text, (int) moveType);
9738         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9739
9740       case IllegalMove:
9741         if (appData.testLegality) {
9742             if (appData.debugMode)
9743               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9744             sprintf(move, _("Illegal move: %d.%s%s"),
9745                     (forwardMostMove / 2) + 1,
9746                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9747             DisplayError(move, 0);
9748             done = TRUE;
9749         } else {
9750             if (appData.debugMode)
9751               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9752                       yy_text, currentMoveString);
9753             fromX = currentMoveString[0] - AAA;
9754             fromY = currentMoveString[1] - ONE;
9755             toX = currentMoveString[2] - AAA;
9756             toY = currentMoveString[3] - ONE;
9757             promoChar = currentMoveString[4];
9758         }
9759         break;
9760
9761       case AmbiguousMove:
9762         if (appData.debugMode)
9763           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9764         sprintf(move, _("Ambiguous move: %d.%s%s"),
9765                 (forwardMostMove / 2) + 1,
9766                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9767         DisplayError(move, 0);
9768         done = TRUE;
9769         break;
9770
9771       default:
9772       case ImpossibleMove:
9773         if (appData.debugMode)
9774           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9775         sprintf(move, _("Illegal move: %d.%s%s"),
9776                 (forwardMostMove / 2) + 1,
9777                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9778         DisplayError(move, 0);
9779         done = TRUE;
9780         break;
9781     }
9782
9783     if (done) {
9784         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9785             DrawPosition(FALSE, boards[currentMove]);
9786             DisplayBothClocks();
9787             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9788               DisplayComment(currentMove - 1, commentList[currentMove]);
9789         }
9790         (void) StopLoadGameTimer();
9791         gameFileFP = NULL;
9792         cmailOldMove = forwardMostMove;
9793         return FALSE;
9794     } else {
9795         /* currentMoveString is set as a side-effect of yylex */
9796         strcat(currentMoveString, "\n");
9797         strcpy(moveList[forwardMostMove], currentMoveString);
9798         
9799         thinkOutput[0] = NULLCHAR;
9800         MakeMove(fromX, fromY, toX, toY, promoChar);
9801         currentMove = forwardMostMove;
9802         return TRUE;
9803     }
9804 }
9805
9806 /* Load the nth game from the given file */
9807 int
9808 LoadGameFromFile(filename, n, title, useList)
9809      char *filename;
9810      int n;
9811      char *title;
9812      /*Boolean*/ int useList;
9813 {
9814     FILE *f;
9815     char buf[MSG_SIZ];
9816
9817     if (strcmp(filename, "-") == 0) {
9818         f = stdin;
9819         title = "stdin";
9820     } else {
9821         f = fopen(filename, "rb");
9822         if (f == NULL) {
9823           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9824             DisplayError(buf, errno);
9825             return FALSE;
9826         }
9827     }
9828     if (fseek(f, 0, 0) == -1) {
9829         /* f is not seekable; probably a pipe */
9830         useList = FALSE;
9831     }
9832     if (useList && n == 0) {
9833         int error = GameListBuild(f);
9834         if (error) {
9835             DisplayError(_("Cannot build game list"), error);
9836         } else if (!ListEmpty(&gameList) &&
9837                    ((ListGame *) gameList.tailPred)->number > 1) {
9838             GameListPopUp(f, title);
9839             return TRUE;
9840         }
9841         GameListDestroy();
9842         n = 1;
9843     }
9844     if (n == 0) n = 1;
9845     return LoadGame(f, n, title, FALSE);
9846 }
9847
9848
9849 void
9850 MakeRegisteredMove()
9851 {
9852     int fromX, fromY, toX, toY;
9853     char promoChar;
9854     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9855         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9856           case CMAIL_MOVE:
9857           case CMAIL_DRAW:
9858             if (appData.debugMode)
9859               fprintf(debugFP, "Restoring %s for game %d\n",
9860                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9861     
9862             thinkOutput[0] = NULLCHAR;
9863             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9864             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9865             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9866             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9867             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9868             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9869             MakeMove(fromX, fromY, toX, toY, promoChar);
9870             ShowMove(fromX, fromY, toX, toY);
9871               
9872             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9873               case MT_NONE:
9874               case MT_CHECK:
9875                 break;
9876                 
9877               case MT_CHECKMATE:
9878               case MT_STAINMATE:
9879                 if (WhiteOnMove(currentMove)) {
9880                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9881                 } else {
9882                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9883                 }
9884                 break;
9885                 
9886               case MT_STALEMATE:
9887                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9888                 break;
9889             }
9890
9891             break;
9892             
9893           case CMAIL_RESIGN:
9894             if (WhiteOnMove(currentMove)) {
9895                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9896             } else {
9897                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9898             }
9899             break;
9900             
9901           case CMAIL_ACCEPT:
9902             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9903             break;
9904               
9905           default:
9906             break;
9907         }
9908     }
9909
9910     return;
9911 }
9912
9913 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9914 int
9915 CmailLoadGame(f, gameNumber, title, useList)
9916      FILE *f;
9917      int gameNumber;
9918      char *title;
9919      int useList;
9920 {
9921     int retVal;
9922
9923     if (gameNumber > nCmailGames) {
9924         DisplayError(_("No more games in this message"), 0);
9925         return FALSE;
9926     }
9927     if (f == lastLoadGameFP) {
9928         int offset = gameNumber - lastLoadGameNumber;
9929         if (offset == 0) {
9930             cmailMsg[0] = NULLCHAR;
9931             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9932                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9933                 nCmailMovesRegistered--;
9934             }
9935             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9936             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9937                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9938             }
9939         } else {
9940             if (! RegisterMove()) return FALSE;
9941         }
9942     }
9943
9944     retVal = LoadGame(f, gameNumber, title, useList);
9945
9946     /* Make move registered during previous look at this game, if any */
9947     MakeRegisteredMove();
9948
9949     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9950         commentList[currentMove]
9951           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9952         DisplayComment(currentMove - 1, commentList[currentMove]);
9953     }
9954
9955     return retVal;
9956 }
9957
9958 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9959 int
9960 ReloadGame(offset)
9961      int offset;
9962 {
9963     int gameNumber = lastLoadGameNumber + offset;
9964     if (lastLoadGameFP == NULL) {
9965         DisplayError(_("No game has been loaded yet"), 0);
9966         return FALSE;
9967     }
9968     if (gameNumber <= 0) {
9969         DisplayError(_("Can't back up any further"), 0);
9970         return FALSE;
9971     }
9972     if (cmailMsgLoaded) {
9973         return CmailLoadGame(lastLoadGameFP, gameNumber,
9974                              lastLoadGameTitle, lastLoadGameUseList);
9975     } else {
9976         return LoadGame(lastLoadGameFP, gameNumber,
9977                         lastLoadGameTitle, lastLoadGameUseList);
9978     }
9979 }
9980
9981
9982
9983 /* Load the nth game from open file f */
9984 int
9985 LoadGame(f, gameNumber, title, useList)
9986      FILE *f;
9987      int gameNumber;
9988      char *title;
9989      int useList;
9990 {
9991     ChessMove cm;
9992     char buf[MSG_SIZ];
9993     int gn = gameNumber;
9994     ListGame *lg = NULL;
9995     int numPGNTags = 0;
9996     int err;
9997     GameMode oldGameMode;
9998     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9999
10000     if (appData.debugMode) 
10001         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10002
10003     if (gameMode == Training )
10004         SetTrainingModeOff();
10005
10006     oldGameMode = gameMode;
10007     if (gameMode != BeginningOfGame) {
10008       Reset(FALSE, TRUE);
10009     }
10010
10011     gameFileFP = f;
10012     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10013         fclose(lastLoadGameFP);
10014     }
10015
10016     if (useList) {
10017         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10018         
10019         if (lg) {
10020             fseek(f, lg->offset, 0);
10021             GameListHighlight(gameNumber);
10022             gn = 1;
10023         }
10024         else {
10025             DisplayError(_("Game number out of range"), 0);
10026             return FALSE;
10027         }
10028     } else {
10029         GameListDestroy();
10030         if (fseek(f, 0, 0) == -1) {
10031             if (f == lastLoadGameFP ?
10032                 gameNumber == lastLoadGameNumber + 1 :
10033                 gameNumber == 1) {
10034                 gn = 1;
10035             } else {
10036                 DisplayError(_("Can't seek on game file"), 0);
10037                 return FALSE;
10038             }
10039         }
10040     }
10041     lastLoadGameFP = f;
10042     lastLoadGameNumber = gameNumber;
10043     strcpy(lastLoadGameTitle, title);
10044     lastLoadGameUseList = useList;
10045
10046     yynewfile(f);
10047
10048     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10049       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10050                 lg->gameInfo.black);
10051             DisplayTitle(buf);
10052     } else if (*title != NULLCHAR) {
10053         if (gameNumber > 1) {
10054             sprintf(buf, "%s %d", title, gameNumber);
10055             DisplayTitle(buf);
10056         } else {
10057             DisplayTitle(title);
10058         }
10059     }
10060
10061     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10062         gameMode = PlayFromGameFile;
10063         ModeHighlight();
10064     }
10065
10066     currentMove = forwardMostMove = backwardMostMove = 0;
10067     CopyBoard(boards[0], initialPosition);
10068     StopClocks();
10069
10070     /*
10071      * Skip the first gn-1 games in the file.
10072      * Also skip over anything that precedes an identifiable 
10073      * start of game marker, to avoid being confused by 
10074      * garbage at the start of the file.  Currently 
10075      * recognized start of game markers are the move number "1",
10076      * the pattern "gnuchess .* game", the pattern
10077      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10078      * A game that starts with one of the latter two patterns
10079      * will also have a move number 1, possibly
10080      * following a position diagram.
10081      * 5-4-02: Let's try being more lenient and allowing a game to
10082      * start with an unnumbered move.  Does that break anything?
10083      */
10084     cm = lastLoadGameStart = (ChessMove) 0;
10085     while (gn > 0) {
10086         yyboardindex = forwardMostMove;
10087         cm = (ChessMove) yylex();
10088         switch (cm) {
10089           case (ChessMove) 0:
10090             if (cmailMsgLoaded) {
10091                 nCmailGames = CMAIL_MAX_GAMES - gn;
10092             } else {
10093                 Reset(TRUE, TRUE);
10094                 DisplayError(_("Game not found in file"), 0);
10095             }
10096             return FALSE;
10097
10098           case GNUChessGame:
10099           case XBoardGame:
10100             gn--;
10101             lastLoadGameStart = cm;
10102             break;
10103             
10104           case MoveNumberOne:
10105             switch (lastLoadGameStart) {
10106               case GNUChessGame:
10107               case XBoardGame:
10108               case PGNTag:
10109                 break;
10110               case MoveNumberOne:
10111               case (ChessMove) 0:
10112                 gn--;           /* count this game */
10113                 lastLoadGameStart = cm;
10114                 break;
10115               default:
10116                 /* impossible */
10117                 break;
10118             }
10119             break;
10120
10121           case PGNTag:
10122             switch (lastLoadGameStart) {
10123               case GNUChessGame:
10124               case PGNTag:
10125               case MoveNumberOne:
10126               case (ChessMove) 0:
10127                 gn--;           /* count this game */
10128                 lastLoadGameStart = cm;
10129                 break;
10130               case XBoardGame:
10131                 lastLoadGameStart = cm; /* game counted already */
10132                 break;
10133               default:
10134                 /* impossible */
10135                 break;
10136             }
10137             if (gn > 0) {
10138                 do {
10139                     yyboardindex = forwardMostMove;
10140                     cm = (ChessMove) yylex();
10141                 } while (cm == PGNTag || cm == Comment);
10142             }
10143             break;
10144
10145           case WhiteWins:
10146           case BlackWins:
10147           case GameIsDrawn:
10148             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10149                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10150                     != CMAIL_OLD_RESULT) {
10151                     nCmailResults ++ ;
10152                     cmailResult[  CMAIL_MAX_GAMES
10153                                 - gn - 1] = CMAIL_OLD_RESULT;
10154                 }
10155             }
10156             break;
10157
10158           case NormalMove:
10159             /* Only a NormalMove can be at the start of a game
10160              * without a position diagram. */
10161             if (lastLoadGameStart == (ChessMove) 0) {
10162               gn--;
10163               lastLoadGameStart = MoveNumberOne;
10164             }
10165             break;
10166
10167           default:
10168             break;
10169         }
10170     }
10171     
10172     if (appData.debugMode)
10173       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10174
10175     if (cm == XBoardGame) {
10176         /* Skip any header junk before position diagram and/or move 1 */
10177         for (;;) {
10178             yyboardindex = forwardMostMove;
10179             cm = (ChessMove) yylex();
10180
10181             if (cm == (ChessMove) 0 ||
10182                 cm == GNUChessGame || cm == XBoardGame) {
10183                 /* Empty game; pretend end-of-file and handle later */
10184                 cm = (ChessMove) 0;
10185                 break;
10186             }
10187
10188             if (cm == MoveNumberOne || cm == PositionDiagram ||
10189                 cm == PGNTag || cm == Comment)
10190               break;
10191         }
10192     } else if (cm == GNUChessGame) {
10193         if (gameInfo.event != NULL) {
10194             free(gameInfo.event);
10195         }
10196         gameInfo.event = StrSave(yy_text);
10197     }   
10198
10199     startedFromSetupPosition = FALSE;
10200     while (cm == PGNTag) {
10201         if (appData.debugMode) 
10202           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10203         err = ParsePGNTag(yy_text, &gameInfo);
10204         if (!err) numPGNTags++;
10205
10206         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10207         if(gameInfo.variant != oldVariant) {
10208             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10209             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10210             InitPosition(TRUE);
10211             oldVariant = gameInfo.variant;
10212             if (appData.debugMode) 
10213               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10214         }
10215
10216
10217         if (gameInfo.fen != NULL) {
10218           Board initial_position;
10219           startedFromSetupPosition = TRUE;
10220           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10221             Reset(TRUE, TRUE);
10222             DisplayError(_("Bad FEN position in file"), 0);
10223             return FALSE;
10224           }
10225           CopyBoard(boards[0], initial_position);
10226           if (blackPlaysFirst) {
10227             currentMove = forwardMostMove = backwardMostMove = 1;
10228             CopyBoard(boards[1], initial_position);
10229             strcpy(moveList[0], "");
10230             strcpy(parseList[0], "");
10231             timeRemaining[0][1] = whiteTimeRemaining;
10232             timeRemaining[1][1] = blackTimeRemaining;
10233             if (commentList[0] != NULL) {
10234               commentList[1] = commentList[0];
10235               commentList[0] = NULL;
10236             }
10237           } else {
10238             currentMove = forwardMostMove = backwardMostMove = 0;
10239           }
10240           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10241           {   int i;
10242               initialRulePlies = FENrulePlies;
10243               for( i=0; i< nrCastlingRights; i++ )
10244                   initialRights[i] = initial_position[CASTLING][i];
10245           }
10246           yyboardindex = forwardMostMove;
10247           free(gameInfo.fen);
10248           gameInfo.fen = NULL;
10249         }
10250
10251         yyboardindex = forwardMostMove;
10252         cm = (ChessMove) yylex();
10253
10254         /* Handle comments interspersed among the tags */
10255         while (cm == Comment) {
10256             char *p;
10257             if (appData.debugMode) 
10258               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10259             p = yy_text;
10260             AppendComment(currentMove, p, FALSE);
10261             yyboardindex = forwardMostMove;
10262             cm = (ChessMove) yylex();
10263         }
10264     }
10265
10266     /* don't rely on existence of Event tag since if game was
10267      * pasted from clipboard the Event tag may not exist
10268      */
10269     if (numPGNTags > 0){
10270         char *tags;
10271         if (gameInfo.variant == VariantNormal) {
10272           VariantClass v = StringToVariant(gameInfo.event);
10273           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10274           if(v < VariantShogi) gameInfo.variant = v;
10275         }
10276         if (!matchMode) {
10277           if( appData.autoDisplayTags ) {
10278             tags = PGNTags(&gameInfo);
10279             TagsPopUp(tags, CmailMsg());
10280             free(tags);
10281           }
10282         }
10283     } else {
10284         /* Make something up, but don't display it now */
10285         SetGameInfo();
10286         TagsPopDown();
10287     }
10288
10289     if (cm == PositionDiagram) {
10290         int i, j;
10291         char *p;
10292         Board initial_position;
10293
10294         if (appData.debugMode)
10295           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10296
10297         if (!startedFromSetupPosition) {
10298             p = yy_text;
10299             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10300               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10301                 switch (*p) {
10302                   case '[':
10303                   case '-':
10304                   case ' ':
10305                   case '\t':
10306                   case '\n':
10307                   case '\r':
10308                     break;
10309                   default:
10310                     initial_position[i][j++] = CharToPiece(*p);
10311                     break;
10312                 }
10313             while (*p == ' ' || *p == '\t' ||
10314                    *p == '\n' || *p == '\r') p++;
10315         
10316             if (strncmp(p, "black", strlen("black"))==0)
10317               blackPlaysFirst = TRUE;
10318             else
10319               blackPlaysFirst = FALSE;
10320             startedFromSetupPosition = TRUE;
10321         
10322             CopyBoard(boards[0], initial_position);
10323             if (blackPlaysFirst) {
10324                 currentMove = forwardMostMove = backwardMostMove = 1;
10325                 CopyBoard(boards[1], initial_position);
10326                 strcpy(moveList[0], "");
10327                 strcpy(parseList[0], "");
10328                 timeRemaining[0][1] = whiteTimeRemaining;
10329                 timeRemaining[1][1] = blackTimeRemaining;
10330                 if (commentList[0] != NULL) {
10331                     commentList[1] = commentList[0];
10332                     commentList[0] = NULL;
10333                 }
10334             } else {
10335                 currentMove = forwardMostMove = backwardMostMove = 0;
10336             }
10337         }
10338         yyboardindex = forwardMostMove;
10339         cm = (ChessMove) yylex();
10340     }
10341
10342     if (first.pr == NoProc) {
10343         StartChessProgram(&first);
10344     }
10345     InitChessProgram(&first, FALSE);
10346     SendToProgram("force\n", &first);
10347     if (startedFromSetupPosition) {
10348         SendBoard(&first, forwardMostMove);
10349     if (appData.debugMode) {
10350         fprintf(debugFP, "Load Game\n");
10351     }
10352         DisplayBothClocks();
10353     }      
10354
10355     /* [HGM] server: flag to write setup moves in broadcast file as one */
10356     loadFlag = appData.suppressLoadMoves;
10357
10358     while (cm == Comment) {
10359         char *p;
10360         if (appData.debugMode) 
10361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10362         p = yy_text;
10363         AppendComment(currentMove, p, FALSE);
10364         yyboardindex = forwardMostMove;
10365         cm = (ChessMove) yylex();
10366     }
10367
10368     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10369         cm == WhiteWins || cm == BlackWins ||
10370         cm == GameIsDrawn || cm == GameUnfinished) {
10371         DisplayMessage("", _("No moves in game"));
10372         if (cmailMsgLoaded) {
10373             if (appData.debugMode)
10374               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10375             ClearHighlights();
10376             flipView = FALSE;
10377         }
10378         DrawPosition(FALSE, boards[currentMove]);
10379         DisplayBothClocks();
10380         gameMode = EditGame;
10381         ModeHighlight();
10382         gameFileFP = NULL;
10383         cmailOldMove = 0;
10384         return TRUE;
10385     }
10386
10387     // [HGM] PV info: routine tests if comment empty
10388     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10389         DisplayComment(currentMove - 1, commentList[currentMove]);
10390     }
10391     if (!matchMode && appData.timeDelay != 0) 
10392       DrawPosition(FALSE, boards[currentMove]);
10393
10394     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10395       programStats.ok_to_send = 1;
10396     }
10397
10398     /* if the first token after the PGN tags is a move
10399      * and not move number 1, retrieve it from the parser 
10400      */
10401     if (cm != MoveNumberOne)
10402         LoadGameOneMove(cm);
10403
10404     /* load the remaining moves from the file */
10405     while (LoadGameOneMove((ChessMove)0)) {
10406       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10407       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10408     }
10409
10410     /* rewind to the start of the game */
10411     currentMove = backwardMostMove;
10412
10413     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10414
10415     if (oldGameMode == AnalyzeFile ||
10416         oldGameMode == AnalyzeMode) {
10417       AnalyzeFileEvent();
10418     }
10419
10420     if (matchMode || appData.timeDelay == 0) {
10421       ToEndEvent();
10422       gameMode = EditGame;
10423       ModeHighlight();
10424     } else if (appData.timeDelay > 0) {
10425       AutoPlayGameLoop();
10426     }
10427
10428     if (appData.debugMode) 
10429         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10430
10431     loadFlag = 0; /* [HGM] true game starts */
10432     return TRUE;
10433 }
10434
10435 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10436 int
10437 ReloadPosition(offset)
10438      int offset;
10439 {
10440     int positionNumber = lastLoadPositionNumber + offset;
10441     if (lastLoadPositionFP == NULL) {
10442         DisplayError(_("No position has been loaded yet"), 0);
10443         return FALSE;
10444     }
10445     if (positionNumber <= 0) {
10446         DisplayError(_("Can't back up any further"), 0);
10447         return FALSE;
10448     }
10449     return LoadPosition(lastLoadPositionFP, positionNumber,
10450                         lastLoadPositionTitle);
10451 }
10452
10453 /* Load the nth position from the given file */
10454 int
10455 LoadPositionFromFile(filename, n, title)
10456      char *filename;
10457      int n;
10458      char *title;
10459 {
10460     FILE *f;
10461     char buf[MSG_SIZ];
10462
10463     if (strcmp(filename, "-") == 0) {
10464         return LoadPosition(stdin, n, "stdin");
10465     } else {
10466         f = fopen(filename, "rb");
10467         if (f == NULL) {
10468             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10469             DisplayError(buf, errno);
10470             return FALSE;
10471         } else {
10472             return LoadPosition(f, n, title);
10473         }
10474     }
10475 }
10476
10477 /* Load the nth position from the given open file, and close it */
10478 int
10479 LoadPosition(f, positionNumber, title)
10480      FILE *f;
10481      int positionNumber;
10482      char *title;
10483 {
10484     char *p, line[MSG_SIZ];
10485     Board initial_position;
10486     int i, j, fenMode, pn;
10487     
10488     if (gameMode == Training )
10489         SetTrainingModeOff();
10490
10491     if (gameMode != BeginningOfGame) {
10492         Reset(FALSE, TRUE);
10493     }
10494     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10495         fclose(lastLoadPositionFP);
10496     }
10497     if (positionNumber == 0) positionNumber = 1;
10498     lastLoadPositionFP = f;
10499     lastLoadPositionNumber = positionNumber;
10500     strcpy(lastLoadPositionTitle, title);
10501     if (first.pr == NoProc) {
10502       StartChessProgram(&first);
10503       InitChessProgram(&first, FALSE);
10504     }    
10505     pn = positionNumber;
10506     if (positionNumber < 0) {
10507         /* Negative position number means to seek to that byte offset */
10508         if (fseek(f, -positionNumber, 0) == -1) {
10509             DisplayError(_("Can't seek on position file"), 0);
10510             return FALSE;
10511         };
10512         pn = 1;
10513     } else {
10514         if (fseek(f, 0, 0) == -1) {
10515             if (f == lastLoadPositionFP ?
10516                 positionNumber == lastLoadPositionNumber + 1 :
10517                 positionNumber == 1) {
10518                 pn = 1;
10519             } else {
10520                 DisplayError(_("Can't seek on position file"), 0);
10521                 return FALSE;
10522             }
10523         }
10524     }
10525     /* See if this file is FEN or old-style xboard */
10526     if (fgets(line, MSG_SIZ, f) == NULL) {
10527         DisplayError(_("Position not found in file"), 0);
10528         return FALSE;
10529     }
10530     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10531     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10532
10533     if (pn >= 2) {
10534         if (fenMode || line[0] == '#') pn--;
10535         while (pn > 0) {
10536             /* skip positions before number pn */
10537             if (fgets(line, MSG_SIZ, f) == NULL) {
10538                 Reset(TRUE, TRUE);
10539                 DisplayError(_("Position not found in file"), 0);
10540                 return FALSE;
10541             }
10542             if (fenMode || line[0] == '#') pn--;
10543         }
10544     }
10545
10546     if (fenMode) {
10547         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10548             DisplayError(_("Bad FEN position in file"), 0);
10549             return FALSE;
10550         }
10551     } else {
10552         (void) fgets(line, MSG_SIZ, f);
10553         (void) fgets(line, MSG_SIZ, f);
10554     
10555         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10556             (void) fgets(line, MSG_SIZ, f);
10557             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10558                 if (*p == ' ')
10559                   continue;
10560                 initial_position[i][j++] = CharToPiece(*p);
10561             }
10562         }
10563     
10564         blackPlaysFirst = FALSE;
10565         if (!feof(f)) {
10566             (void) fgets(line, MSG_SIZ, f);
10567             if (strncmp(line, "black", strlen("black"))==0)
10568               blackPlaysFirst = TRUE;
10569         }
10570     }
10571     startedFromSetupPosition = TRUE;
10572     
10573     SendToProgram("force\n", &first);
10574     CopyBoard(boards[0], initial_position);
10575     if (blackPlaysFirst) {
10576         currentMove = forwardMostMove = backwardMostMove = 1;
10577         strcpy(moveList[0], "");
10578         strcpy(parseList[0], "");
10579         CopyBoard(boards[1], initial_position);
10580         DisplayMessage("", _("Black to play"));
10581     } else {
10582         currentMove = forwardMostMove = backwardMostMove = 0;
10583         DisplayMessage("", _("White to play"));
10584     }
10585     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10586     SendBoard(&first, forwardMostMove);
10587     if (appData.debugMode) {
10588 int i, j;
10589   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10590   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10591         fprintf(debugFP, "Load Position\n");
10592     }
10593
10594     if (positionNumber > 1) {
10595         sprintf(line, "%s %d", title, positionNumber);
10596         DisplayTitle(line);
10597     } else {
10598         DisplayTitle(title);
10599     }
10600     gameMode = EditGame;
10601     ModeHighlight();
10602     ResetClocks();
10603     timeRemaining[0][1] = whiteTimeRemaining;
10604     timeRemaining[1][1] = blackTimeRemaining;
10605     DrawPosition(FALSE, boards[currentMove]);
10606    
10607     return TRUE;
10608 }
10609
10610
10611 void
10612 CopyPlayerNameIntoFileName(dest, src)
10613      char **dest, *src;
10614 {
10615     while (*src != NULLCHAR && *src != ',') {
10616         if (*src == ' ') {
10617             *(*dest)++ = '_';
10618             src++;
10619         } else {
10620             *(*dest)++ = *src++;
10621         }
10622     }
10623 }
10624
10625 char *DefaultFileName(ext)
10626      char *ext;
10627 {
10628     static char def[MSG_SIZ];
10629     char *p;
10630
10631     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10632         p = def;
10633         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10634         *p++ = '-';
10635         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10636         *p++ = '.';
10637         strcpy(p, ext);
10638     } else {
10639         def[0] = NULLCHAR;
10640     }
10641     return def;
10642 }
10643
10644 /* Save the current game to the given file */
10645 int
10646 SaveGameToFile(filename, append)
10647      char *filename;
10648      int append;
10649 {
10650     FILE *f;
10651     char buf[MSG_SIZ];
10652
10653     if (strcmp(filename, "-") == 0) {
10654         return SaveGame(stdout, 0, NULL);
10655     } else {
10656         f = fopen(filename, append ? "a" : "w");
10657         if (f == NULL) {
10658             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10659             DisplayError(buf, errno);
10660             return FALSE;
10661         } else {
10662             return SaveGame(f, 0, NULL);
10663         }
10664     }
10665 }
10666
10667 char *
10668 SavePart(str)
10669      char *str;
10670 {
10671     static char buf[MSG_SIZ];
10672     char *p;
10673     
10674     p = strchr(str, ' ');
10675     if (p == NULL) return str;
10676     strncpy(buf, str, p - str);
10677     buf[p - str] = NULLCHAR;
10678     return buf;
10679 }
10680
10681 #define PGN_MAX_LINE 75
10682
10683 #define PGN_SIDE_WHITE  0
10684 #define PGN_SIDE_BLACK  1
10685
10686 /* [AS] */
10687 static int FindFirstMoveOutOfBook( int side )
10688 {
10689     int result = -1;
10690
10691     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10692         int index = backwardMostMove;
10693         int has_book_hit = 0;
10694
10695         if( (index % 2) != side ) {
10696             index++;
10697         }
10698
10699         while( index < forwardMostMove ) {
10700             /* Check to see if engine is in book */
10701             int depth = pvInfoList[index].depth;
10702             int score = pvInfoList[index].score;
10703             int in_book = 0;
10704
10705             if( depth <= 2 ) {
10706                 in_book = 1;
10707             }
10708             else if( score == 0 && depth == 63 ) {
10709                 in_book = 1; /* Zappa */
10710             }
10711             else if( score == 2 && depth == 99 ) {
10712                 in_book = 1; /* Abrok */
10713             }
10714
10715             has_book_hit += in_book;
10716
10717             if( ! in_book ) {
10718                 result = index;
10719
10720                 break;
10721             }
10722
10723             index += 2;
10724         }
10725     }
10726
10727     return result;
10728 }
10729
10730 /* [AS] */
10731 void GetOutOfBookInfo( char * buf )
10732 {
10733     int oob[2];
10734     int i;
10735     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10736
10737     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10738     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10739
10740     *buf = '\0';
10741
10742     if( oob[0] >= 0 || oob[1] >= 0 ) {
10743         for( i=0; i<2; i++ ) {
10744             int idx = oob[i];
10745
10746             if( idx >= 0 ) {
10747                 if( i > 0 && oob[0] >= 0 ) {
10748                     strcat( buf, "   " );
10749                 }
10750
10751                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10752                 sprintf( buf+strlen(buf), "%s%.2f", 
10753                     pvInfoList[idx].score >= 0 ? "+" : "",
10754                     pvInfoList[idx].score / 100.0 );
10755             }
10756         }
10757     }
10758 }
10759
10760 /* Save game in PGN style and close the file */
10761 int
10762 SaveGamePGN(f)
10763      FILE *f;
10764 {
10765     int i, offset, linelen, newblock;
10766     time_t tm;
10767 //    char *movetext;
10768     char numtext[32];
10769     int movelen, numlen, blank;
10770     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10771
10772     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10773     
10774     tm = time((time_t *) NULL);
10775     
10776     PrintPGNTags(f, &gameInfo);
10777     
10778     if (backwardMostMove > 0 || startedFromSetupPosition) {
10779         char *fen = PositionToFEN(backwardMostMove, NULL);
10780         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10781         fprintf(f, "\n{--------------\n");
10782         PrintPosition(f, backwardMostMove);
10783         fprintf(f, "--------------}\n");
10784         free(fen);
10785     }
10786     else {
10787         /* [AS] Out of book annotation */
10788         if( appData.saveOutOfBookInfo ) {
10789             char buf[64];
10790
10791             GetOutOfBookInfo( buf );
10792
10793             if( buf[0] != '\0' ) {
10794                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10795             }
10796         }
10797
10798         fprintf(f, "\n");
10799     }
10800
10801     i = backwardMostMove;
10802     linelen = 0;
10803     newblock = TRUE;
10804
10805     while (i < forwardMostMove) {
10806         /* Print comments preceding this move */
10807         if (commentList[i] != NULL) {
10808             if (linelen > 0) fprintf(f, "\n");
10809             fprintf(f, "%s", commentList[i]);
10810             linelen = 0;
10811             newblock = TRUE;
10812         }
10813
10814         /* Format move number */
10815         if ((i % 2) == 0) {
10816             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10817         } else {
10818             if (newblock) {
10819                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10820             } else {
10821                 numtext[0] = NULLCHAR;
10822             }
10823         }
10824         numlen = strlen(numtext);
10825         newblock = FALSE;
10826
10827         /* Print move number */
10828         blank = linelen > 0 && numlen > 0;
10829         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10830             fprintf(f, "\n");
10831             linelen = 0;
10832             blank = 0;
10833         }
10834         if (blank) {
10835             fprintf(f, " ");
10836             linelen++;
10837         }
10838         fprintf(f, "%s", numtext);
10839         linelen += numlen;
10840
10841         /* Get move */
10842         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10843         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10844
10845         /* Print move */
10846         blank = linelen > 0 && movelen > 0;
10847         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10848             fprintf(f, "\n");
10849             linelen = 0;
10850             blank = 0;
10851         }
10852         if (blank) {
10853             fprintf(f, " ");
10854             linelen++;
10855         }
10856         fprintf(f, "%s", move_buffer);
10857         linelen += movelen;
10858
10859         /* [AS] Add PV info if present */
10860         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10861             /* [HGM] add time */
10862             char buf[MSG_SIZ]; int seconds;
10863
10864             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10865
10866             if( seconds <= 0) buf[0] = 0; else
10867             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10868                 seconds = (seconds + 4)/10; // round to full seconds
10869                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10870                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10871             }
10872
10873             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10874                 pvInfoList[i].score >= 0 ? "+" : "",
10875                 pvInfoList[i].score / 100.0,
10876                 pvInfoList[i].depth,
10877                 buf );
10878
10879             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10880
10881             /* Print score/depth */
10882             blank = linelen > 0 && movelen > 0;
10883             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10884                 fprintf(f, "\n");
10885                 linelen = 0;
10886                 blank = 0;
10887             }
10888             if (blank) {
10889                 fprintf(f, " ");
10890                 linelen++;
10891             }
10892             fprintf(f, "%s", move_buffer);
10893             linelen += movelen;
10894         }
10895
10896         i++;
10897     }
10898     
10899     /* Start a new line */
10900     if (linelen > 0) fprintf(f, "\n");
10901
10902     /* Print comments after last move */
10903     if (commentList[i] != NULL) {
10904         fprintf(f, "%s\n", commentList[i]);
10905     }
10906
10907     /* Print result */
10908     if (gameInfo.resultDetails != NULL &&
10909         gameInfo.resultDetails[0] != NULLCHAR) {
10910         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10911                 PGNResult(gameInfo.result));
10912     } else {
10913         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10914     }
10915
10916     fclose(f);
10917     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10918     return TRUE;
10919 }
10920
10921 /* Save game in old style and close the file */
10922 int
10923 SaveGameOldStyle(f)
10924      FILE *f;
10925 {
10926     int i, offset;
10927     time_t tm;
10928     
10929     tm = time((time_t *) NULL);
10930     
10931     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10932     PrintOpponents(f);
10933     
10934     if (backwardMostMove > 0 || startedFromSetupPosition) {
10935         fprintf(f, "\n[--------------\n");
10936         PrintPosition(f, backwardMostMove);
10937         fprintf(f, "--------------]\n");
10938     } else {
10939         fprintf(f, "\n");
10940     }
10941
10942     i = backwardMostMove;
10943     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10944
10945     while (i < forwardMostMove) {
10946         if (commentList[i] != NULL) {
10947             fprintf(f, "[%s]\n", commentList[i]);
10948         }
10949
10950         if ((i % 2) == 1) {
10951             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10952             i++;
10953         } else {
10954             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10955             i++;
10956             if (commentList[i] != NULL) {
10957                 fprintf(f, "\n");
10958                 continue;
10959             }
10960             if (i >= forwardMostMove) {
10961                 fprintf(f, "\n");
10962                 break;
10963             }
10964             fprintf(f, "%s\n", parseList[i]);
10965             i++;
10966         }
10967     }
10968     
10969     if (commentList[i] != NULL) {
10970         fprintf(f, "[%s]\n", commentList[i]);
10971     }
10972
10973     /* This isn't really the old style, but it's close enough */
10974     if (gameInfo.resultDetails != NULL &&
10975         gameInfo.resultDetails[0] != NULLCHAR) {
10976         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10977                 gameInfo.resultDetails);
10978     } else {
10979         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10980     }
10981
10982     fclose(f);
10983     return TRUE;
10984 }
10985
10986 /* Save the current game to open file f and close the file */
10987 int
10988 SaveGame(f, dummy, dummy2)
10989      FILE *f;
10990      int dummy;
10991      char *dummy2;
10992 {
10993     if (gameMode == EditPosition) EditPositionDone(TRUE);
10994     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10995     if (appData.oldSaveStyle)
10996       return SaveGameOldStyle(f);
10997     else
10998       return SaveGamePGN(f);
10999 }
11000
11001 /* Save the current position to the given file */
11002 int
11003 SavePositionToFile(filename)
11004      char *filename;
11005 {
11006     FILE *f;
11007     char buf[MSG_SIZ];
11008
11009     if (strcmp(filename, "-") == 0) {
11010         return SavePosition(stdout, 0, NULL);
11011     } else {
11012         f = fopen(filename, "a");
11013         if (f == NULL) {
11014             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11015             DisplayError(buf, errno);
11016             return FALSE;
11017         } else {
11018             SavePosition(f, 0, NULL);
11019             return TRUE;
11020         }
11021     }
11022 }
11023
11024 /* Save the current position to the given open file and close the file */
11025 int
11026 SavePosition(f, dummy, dummy2)
11027      FILE *f;
11028      int dummy;
11029      char *dummy2;
11030 {
11031     time_t tm;
11032     char *fen;
11033     
11034     if (gameMode == EditPosition) EditPositionDone(TRUE);
11035     if (appData.oldSaveStyle) {
11036         tm = time((time_t *) NULL);
11037     
11038         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11039         PrintOpponents(f);
11040         fprintf(f, "[--------------\n");
11041         PrintPosition(f, currentMove);
11042         fprintf(f, "--------------]\n");
11043     } else {
11044         fen = PositionToFEN(currentMove, NULL);
11045         fprintf(f, "%s\n", fen);
11046         free(fen);
11047     }
11048     fclose(f);
11049     return TRUE;
11050 }
11051
11052 void
11053 ReloadCmailMsgEvent(unregister)
11054      int unregister;
11055 {
11056 #if !WIN32
11057     static char *inFilename = NULL;
11058     static char *outFilename;
11059     int i;
11060     struct stat inbuf, outbuf;
11061     int status;
11062     
11063     /* Any registered moves are unregistered if unregister is set, */
11064     /* i.e. invoked by the signal handler */
11065     if (unregister) {
11066         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11067             cmailMoveRegistered[i] = FALSE;
11068             if (cmailCommentList[i] != NULL) {
11069                 free(cmailCommentList[i]);
11070                 cmailCommentList[i] = NULL;
11071             }
11072         }
11073         nCmailMovesRegistered = 0;
11074     }
11075
11076     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11077         cmailResult[i] = CMAIL_NOT_RESULT;
11078     }
11079     nCmailResults = 0;
11080
11081     if (inFilename == NULL) {
11082         /* Because the filenames are static they only get malloced once  */
11083         /* and they never get freed                                      */
11084         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11085         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11086
11087         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11088         sprintf(outFilename, "%s.out", appData.cmailGameName);
11089     }
11090     
11091     status = stat(outFilename, &outbuf);
11092     if (status < 0) {
11093         cmailMailedMove = FALSE;
11094     } else {
11095         status = stat(inFilename, &inbuf);
11096         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11097     }
11098     
11099     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11100        counts the games, notes how each one terminated, etc.
11101        
11102        It would be nice to remove this kludge and instead gather all
11103        the information while building the game list.  (And to keep it
11104        in the game list nodes instead of having a bunch of fixed-size
11105        parallel arrays.)  Note this will require getting each game's
11106        termination from the PGN tags, as the game list builder does
11107        not process the game moves.  --mann
11108        */
11109     cmailMsgLoaded = TRUE;
11110     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11111     
11112     /* Load first game in the file or popup game menu */
11113     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11114
11115 #endif /* !WIN32 */
11116     return;
11117 }
11118
11119 int
11120 RegisterMove()
11121 {
11122     FILE *f;
11123     char string[MSG_SIZ];
11124
11125     if (   cmailMailedMove
11126         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11127         return TRUE;            /* Allow free viewing  */
11128     }
11129
11130     /* Unregister move to ensure that we don't leave RegisterMove        */
11131     /* with the move registered when the conditions for registering no   */
11132     /* longer hold                                                       */
11133     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11134         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11135         nCmailMovesRegistered --;
11136
11137         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11138           {
11139               free(cmailCommentList[lastLoadGameNumber - 1]);
11140               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11141           }
11142     }
11143
11144     if (cmailOldMove == -1) {
11145         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11146         return FALSE;
11147     }
11148
11149     if (currentMove > cmailOldMove + 1) {
11150         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11151         return FALSE;
11152     }
11153
11154     if (currentMove < cmailOldMove) {
11155         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11156         return FALSE;
11157     }
11158
11159     if (forwardMostMove > currentMove) {
11160         /* Silently truncate extra moves */
11161         TruncateGame();
11162     }
11163
11164     if (   (currentMove == cmailOldMove + 1)
11165         || (   (currentMove == cmailOldMove)
11166             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11167                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11168         if (gameInfo.result != GameUnfinished) {
11169             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11170         }
11171
11172         if (commentList[currentMove] != NULL) {
11173             cmailCommentList[lastLoadGameNumber - 1]
11174               = StrSave(commentList[currentMove]);
11175         }
11176         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11177
11178         if (appData.debugMode)
11179           fprintf(debugFP, "Saving %s for game %d\n",
11180                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11181
11182         sprintf(string,
11183                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11184         
11185         f = fopen(string, "w");
11186         if (appData.oldSaveStyle) {
11187             SaveGameOldStyle(f); /* also closes the file */
11188             
11189             sprintf(string, "%s.pos.out", appData.cmailGameName);
11190             f = fopen(string, "w");
11191             SavePosition(f, 0, NULL); /* also closes the file */
11192         } else {
11193             fprintf(f, "{--------------\n");
11194             PrintPosition(f, currentMove);
11195             fprintf(f, "--------------}\n\n");
11196             
11197             SaveGame(f, 0, NULL); /* also closes the file*/
11198         }
11199         
11200         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11201         nCmailMovesRegistered ++;
11202     } else if (nCmailGames == 1) {
11203         DisplayError(_("You have not made a move yet"), 0);
11204         return FALSE;
11205     }
11206
11207     return TRUE;
11208 }
11209
11210 void
11211 MailMoveEvent()
11212 {
11213 #if !WIN32
11214     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11215     FILE *commandOutput;
11216     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11217     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11218     int nBuffers;
11219     int i;
11220     int archived;
11221     char *arcDir;
11222
11223     if (! cmailMsgLoaded) {
11224         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11225         return;
11226     }
11227
11228     if (nCmailGames == nCmailResults) {
11229         DisplayError(_("No unfinished games"), 0);
11230         return;
11231     }
11232
11233 #if CMAIL_PROHIBIT_REMAIL
11234     if (cmailMailedMove) {
11235         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);
11236         DisplayError(msg, 0);
11237         return;
11238     }
11239 #endif
11240
11241     if (! (cmailMailedMove || RegisterMove())) return;
11242     
11243     if (   cmailMailedMove
11244         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11245         sprintf(string, partCommandString,
11246                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11247         commandOutput = popen(string, "r");
11248
11249         if (commandOutput == NULL) {
11250             DisplayError(_("Failed to invoke cmail"), 0);
11251         } else {
11252             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11253                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11254             }
11255             if (nBuffers > 1) {
11256                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11257                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11258                 nBytes = MSG_SIZ - 1;
11259             } else {
11260                 (void) memcpy(msg, buffer, nBytes);
11261             }
11262             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11263
11264             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11265                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11266
11267                 archived = TRUE;
11268                 for (i = 0; i < nCmailGames; i ++) {
11269                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11270                         archived = FALSE;
11271                     }
11272                 }
11273                 if (   archived
11274                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11275                         != NULL)) {
11276                     sprintf(buffer, "%s/%s.%s.archive",
11277                             arcDir,
11278                             appData.cmailGameName,
11279                             gameInfo.date);
11280                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11281                     cmailMsgLoaded = FALSE;
11282                 }
11283             }
11284
11285             DisplayInformation(msg);
11286             pclose(commandOutput);
11287         }
11288     } else {
11289         if ((*cmailMsg) != '\0') {
11290             DisplayInformation(cmailMsg);
11291         }
11292     }
11293
11294     return;
11295 #endif /* !WIN32 */
11296 }
11297
11298 char *
11299 CmailMsg()
11300 {
11301 #if WIN32
11302     return NULL;
11303 #else
11304     int  prependComma = 0;
11305     char number[5];
11306     char string[MSG_SIZ];       /* Space for game-list */
11307     int  i;
11308     
11309     if (!cmailMsgLoaded) return "";
11310
11311     if (cmailMailedMove) {
11312         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11313     } else {
11314         /* Create a list of games left */
11315         sprintf(string, "[");
11316         for (i = 0; i < nCmailGames; i ++) {
11317             if (! (   cmailMoveRegistered[i]
11318                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11319                 if (prependComma) {
11320                     sprintf(number, ",%d", i + 1);
11321                 } else {
11322                     sprintf(number, "%d", i + 1);
11323                     prependComma = 1;
11324                 }
11325                 
11326                 strcat(string, number);
11327             }
11328         }
11329         strcat(string, "]");
11330
11331         if (nCmailMovesRegistered + nCmailResults == 0) {
11332             switch (nCmailGames) {
11333               case 1:
11334                 sprintf(cmailMsg,
11335                         _("Still need to make move for game\n"));
11336                 break;
11337                 
11338               case 2:
11339                 sprintf(cmailMsg,
11340                         _("Still need to make moves for both games\n"));
11341                 break;
11342                 
11343               default:
11344                 sprintf(cmailMsg,
11345                         _("Still need to make moves for all %d games\n"),
11346                         nCmailGames);
11347                 break;
11348             }
11349         } else {
11350             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11351               case 1:
11352                 sprintf(cmailMsg,
11353                         _("Still need to make a move for game %s\n"),
11354                         string);
11355                 break;
11356                 
11357               case 0:
11358                 if (nCmailResults == nCmailGames) {
11359                     sprintf(cmailMsg, _("No unfinished games\n"));
11360                 } else {
11361                     sprintf(cmailMsg, _("Ready to send mail\n"));
11362                 }
11363                 break;
11364                 
11365               default:
11366                 sprintf(cmailMsg,
11367                         _("Still need to make moves for games %s\n"),
11368                         string);
11369             }
11370         }
11371     }
11372     return cmailMsg;
11373 #endif /* WIN32 */
11374 }
11375
11376 void
11377 ResetGameEvent()
11378 {
11379     if (gameMode == Training)
11380       SetTrainingModeOff();
11381
11382     Reset(TRUE, TRUE);
11383     cmailMsgLoaded = FALSE;
11384     if (appData.icsActive) {
11385       SendToICS(ics_prefix);
11386       SendToICS("refresh\n");
11387     }
11388 }
11389
11390 void
11391 ExitEvent(status)
11392      int status;
11393 {
11394     exiting++;
11395     if (exiting > 2) {
11396       /* Give up on clean exit */
11397       exit(status);
11398     }
11399     if (exiting > 1) {
11400       /* Keep trying for clean exit */
11401       return;
11402     }
11403
11404     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11405
11406     if (telnetISR != NULL) {
11407       RemoveInputSource(telnetISR);
11408     }
11409     if (icsPR != NoProc) {
11410       DestroyChildProcess(icsPR, TRUE);
11411     }
11412
11413     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11414     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11415
11416     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11417     /* make sure this other one finishes before killing it!                  */
11418     if(endingGame) { int count = 0;
11419         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11420         while(endingGame && count++ < 10) DoSleep(1);
11421         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11422     }
11423
11424     /* Kill off chess programs */
11425     if (first.pr != NoProc) {
11426         ExitAnalyzeMode();
11427         
11428         DoSleep( appData.delayBeforeQuit );
11429         SendToProgram("quit\n", &first);
11430         DoSleep( appData.delayAfterQuit );
11431         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11432     }
11433     if (second.pr != NoProc) {
11434         DoSleep( appData.delayBeforeQuit );
11435         SendToProgram("quit\n", &second);
11436         DoSleep( appData.delayAfterQuit );
11437         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11438     }
11439     if (first.isr != NULL) {
11440         RemoveInputSource(first.isr);
11441     }
11442     if (second.isr != NULL) {
11443         RemoveInputSource(second.isr);
11444     }
11445
11446     ShutDownFrontEnd();
11447     exit(status);
11448 }
11449
11450 void
11451 PauseEvent()
11452 {
11453     if (appData.debugMode)
11454         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11455     if (pausing) {
11456         pausing = FALSE;
11457         ModeHighlight();
11458         if (gameMode == MachinePlaysWhite ||
11459             gameMode == MachinePlaysBlack) {
11460             StartClocks();
11461         } else {
11462             DisplayBothClocks();
11463         }
11464         if (gameMode == PlayFromGameFile) {
11465             if (appData.timeDelay >= 0) 
11466                 AutoPlayGameLoop();
11467         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11468             Reset(FALSE, TRUE);
11469             SendToICS(ics_prefix);
11470             SendToICS("refresh\n");
11471         } else if (currentMove < forwardMostMove) {
11472             ForwardInner(forwardMostMove);
11473         }
11474         pauseExamInvalid = FALSE;
11475     } else {
11476         switch (gameMode) {
11477           default:
11478             return;
11479           case IcsExamining:
11480             pauseExamForwardMostMove = forwardMostMove;
11481             pauseExamInvalid = FALSE;
11482             /* fall through */
11483           case IcsObserving:
11484           case IcsPlayingWhite:
11485           case IcsPlayingBlack:
11486             pausing = TRUE;
11487             ModeHighlight();
11488             return;
11489           case PlayFromGameFile:
11490             (void) StopLoadGameTimer();
11491             pausing = TRUE;
11492             ModeHighlight();
11493             break;
11494           case BeginningOfGame:
11495             if (appData.icsActive) return;
11496             /* else fall through */
11497           case MachinePlaysWhite:
11498           case MachinePlaysBlack:
11499           case TwoMachinesPlay:
11500             if (forwardMostMove == 0)
11501               return;           /* don't pause if no one has moved */
11502             if ((gameMode == MachinePlaysWhite &&
11503                  !WhiteOnMove(forwardMostMove)) ||
11504                 (gameMode == MachinePlaysBlack &&
11505                  WhiteOnMove(forwardMostMove))) {
11506                 StopClocks();
11507             }
11508             pausing = TRUE;
11509             ModeHighlight();
11510             break;
11511         }
11512     }
11513 }
11514
11515 void
11516 EditCommentEvent()
11517 {
11518     char title[MSG_SIZ];
11519
11520     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11521         strcpy(title, _("Edit comment"));
11522     } else {
11523         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11524                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11525                 parseList[currentMove - 1]);
11526     }
11527
11528     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11529 }
11530
11531
11532 void
11533 EditTagsEvent()
11534 {
11535     char *tags = PGNTags(&gameInfo);
11536     EditTagsPopUp(tags);
11537     free(tags);
11538 }
11539
11540 void
11541 AnalyzeModeEvent()
11542 {
11543     if (appData.noChessProgram || gameMode == AnalyzeMode)
11544       return;
11545
11546     if (gameMode != AnalyzeFile) {
11547         if (!appData.icsEngineAnalyze) {
11548                EditGameEvent();
11549                if (gameMode != EditGame) return;
11550         }
11551         ResurrectChessProgram();
11552         SendToProgram("analyze\n", &first);
11553         first.analyzing = TRUE;
11554         /*first.maybeThinking = TRUE;*/
11555         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11556         EngineOutputPopUp();
11557     }
11558     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11559     pausing = FALSE;
11560     ModeHighlight();
11561     SetGameInfo();
11562
11563     StartAnalysisClock();
11564     GetTimeMark(&lastNodeCountTime);
11565     lastNodeCount = 0;
11566 }
11567
11568 void
11569 AnalyzeFileEvent()
11570 {
11571     if (appData.noChessProgram || gameMode == AnalyzeFile)
11572       return;
11573
11574     if (gameMode != AnalyzeMode) {
11575         EditGameEvent();
11576         if (gameMode != EditGame) return;
11577         ResurrectChessProgram();
11578         SendToProgram("analyze\n", &first);
11579         first.analyzing = TRUE;
11580         /*first.maybeThinking = TRUE;*/
11581         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11582         EngineOutputPopUp();
11583     }
11584     gameMode = AnalyzeFile;
11585     pausing = FALSE;
11586     ModeHighlight();
11587     SetGameInfo();
11588
11589     StartAnalysisClock();
11590     GetTimeMark(&lastNodeCountTime);
11591     lastNodeCount = 0;
11592 }
11593
11594 void
11595 MachineWhiteEvent()
11596 {
11597     char buf[MSG_SIZ];
11598     char *bookHit = NULL;
11599
11600     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11601       return;
11602
11603
11604     if (gameMode == PlayFromGameFile || 
11605         gameMode == TwoMachinesPlay  || 
11606         gameMode == Training         || 
11607         gameMode == AnalyzeMode      || 
11608         gameMode == EndOfGame)
11609         EditGameEvent();
11610
11611     if (gameMode == EditPosition) 
11612         EditPositionDone(TRUE);
11613
11614     if (!WhiteOnMove(currentMove)) {
11615         DisplayError(_("It is not White's turn"), 0);
11616         return;
11617     }
11618   
11619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11620       ExitAnalyzeMode();
11621
11622     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11623         gameMode == AnalyzeFile)
11624         TruncateGame();
11625
11626     ResurrectChessProgram();    /* in case it isn't running */
11627     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11628         gameMode = MachinePlaysWhite;
11629         ResetClocks();
11630     } else
11631     gameMode = MachinePlaysWhite;
11632     pausing = FALSE;
11633     ModeHighlight();
11634     SetGameInfo();
11635     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11636     DisplayTitle(buf);
11637     if (first.sendName) {
11638       sprintf(buf, "name %s\n", gameInfo.black);
11639       SendToProgram(buf, &first);
11640     }
11641     if (first.sendTime) {
11642       if (first.useColors) {
11643         SendToProgram("black\n", &first); /*gnu kludge*/
11644       }
11645       SendTimeRemaining(&first, TRUE);
11646     }
11647     if (first.useColors) {
11648       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11649     }
11650     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11651     SetMachineThinkingEnables();
11652     first.maybeThinking = TRUE;
11653     StartClocks();
11654     firstMove = FALSE;
11655
11656     if (appData.autoFlipView && !flipView) {
11657       flipView = !flipView;
11658       DrawPosition(FALSE, NULL);
11659       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11660     }
11661
11662     if(bookHit) { // [HGM] book: simulate book reply
11663         static char bookMove[MSG_SIZ]; // a bit generous?
11664
11665         programStats.nodes = programStats.depth = programStats.time = 
11666         programStats.score = programStats.got_only_move = 0;
11667         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11668
11669         strcpy(bookMove, "move ");
11670         strcat(bookMove, bookHit);
11671         HandleMachineMove(bookMove, &first);
11672     }
11673 }
11674
11675 void
11676 MachineBlackEvent()
11677 {
11678     char buf[MSG_SIZ];
11679    char *bookHit = NULL;
11680
11681     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11682         return;
11683
11684
11685     if (gameMode == PlayFromGameFile || 
11686         gameMode == TwoMachinesPlay  || 
11687         gameMode == Training         || 
11688         gameMode == AnalyzeMode      || 
11689         gameMode == EndOfGame)
11690         EditGameEvent();
11691
11692     if (gameMode == EditPosition) 
11693         EditPositionDone(TRUE);
11694
11695     if (WhiteOnMove(currentMove)) {
11696         DisplayError(_("It is not Black's turn"), 0);
11697         return;
11698     }
11699     
11700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11701       ExitAnalyzeMode();
11702
11703     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11704         gameMode == AnalyzeFile)
11705         TruncateGame();
11706
11707     ResurrectChessProgram();    /* in case it isn't running */
11708     gameMode = MachinePlaysBlack;
11709     pausing = FALSE;
11710     ModeHighlight();
11711     SetGameInfo();
11712     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11713     DisplayTitle(buf);
11714     if (first.sendName) {
11715       sprintf(buf, "name %s\n", gameInfo.white);
11716       SendToProgram(buf, &first);
11717     }
11718     if (first.sendTime) {
11719       if (first.useColors) {
11720         SendToProgram("white\n", &first); /*gnu kludge*/
11721       }
11722       SendTimeRemaining(&first, FALSE);
11723     }
11724     if (first.useColors) {
11725       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11726     }
11727     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11728     SetMachineThinkingEnables();
11729     first.maybeThinking = TRUE;
11730     StartClocks();
11731
11732     if (appData.autoFlipView && flipView) {
11733       flipView = !flipView;
11734       DrawPosition(FALSE, NULL);
11735       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11736     }
11737     if(bookHit) { // [HGM] book: simulate book reply
11738         static char bookMove[MSG_SIZ]; // a bit generous?
11739
11740         programStats.nodes = programStats.depth = programStats.time = 
11741         programStats.score = programStats.got_only_move = 0;
11742         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11743
11744         strcpy(bookMove, "move ");
11745         strcat(bookMove, bookHit);
11746         HandleMachineMove(bookMove, &first);
11747     }
11748 }
11749
11750
11751 void
11752 DisplayTwoMachinesTitle()
11753 {
11754     char buf[MSG_SIZ];
11755     if (appData.matchGames > 0) {
11756         if (first.twoMachinesColor[0] == 'w') {
11757             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11758                     gameInfo.white, gameInfo.black,
11759                     first.matchWins, second.matchWins,
11760                     matchGame - 1 - (first.matchWins + second.matchWins));
11761         } else {
11762             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11763                     gameInfo.white, gameInfo.black,
11764                     second.matchWins, first.matchWins,
11765                     matchGame - 1 - (first.matchWins + second.matchWins));
11766         }
11767     } else {
11768         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11769     }
11770     DisplayTitle(buf);
11771 }
11772
11773 void
11774 TwoMachinesEvent P((void))
11775 {
11776     int i;
11777     char buf[MSG_SIZ];
11778     ChessProgramState *onmove;
11779     char *bookHit = NULL;
11780     
11781     if (appData.noChessProgram) return;
11782
11783     switch (gameMode) {
11784       case TwoMachinesPlay:
11785         return;
11786       case MachinePlaysWhite:
11787       case MachinePlaysBlack:
11788         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11789             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11790             return;
11791         }
11792         /* fall through */
11793       case BeginningOfGame:
11794       case PlayFromGameFile:
11795       case EndOfGame:
11796         EditGameEvent();
11797         if (gameMode != EditGame) return;
11798         break;
11799       case EditPosition:
11800         EditPositionDone(TRUE);
11801         break;
11802       case AnalyzeMode:
11803       case AnalyzeFile:
11804         ExitAnalyzeMode();
11805         break;
11806       case EditGame:
11807       default:
11808         break;
11809     }
11810
11811 //    forwardMostMove = currentMove;
11812     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11813     ResurrectChessProgram();    /* in case first program isn't running */
11814
11815     if (second.pr == NULL) {
11816         StartChessProgram(&second);
11817         if (second.protocolVersion == 1) {
11818           TwoMachinesEventIfReady();
11819         } else {
11820           /* kludge: allow timeout for initial "feature" command */
11821           FreezeUI();
11822           DisplayMessage("", _("Starting second chess program"));
11823           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11824         }
11825         return;
11826     }
11827     DisplayMessage("", "");
11828     InitChessProgram(&second, FALSE);
11829     SendToProgram("force\n", &second);
11830     if (startedFromSetupPosition) {
11831         SendBoard(&second, backwardMostMove);
11832     if (appData.debugMode) {
11833         fprintf(debugFP, "Two Machines\n");
11834     }
11835     }
11836     for (i = backwardMostMove; i < forwardMostMove; i++) {
11837         SendMoveToProgram(i, &second);
11838     }
11839
11840     gameMode = TwoMachinesPlay;
11841     pausing = FALSE;
11842     ModeHighlight();
11843     SetGameInfo();
11844     DisplayTwoMachinesTitle();
11845     firstMove = TRUE;
11846     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11847         onmove = &first;
11848     } else {
11849         onmove = &second;
11850     }
11851
11852     SendToProgram(first.computerString, &first);
11853     if (first.sendName) {
11854       sprintf(buf, "name %s\n", second.tidy);
11855       SendToProgram(buf, &first);
11856     }
11857     SendToProgram(second.computerString, &second);
11858     if (second.sendName) {
11859       sprintf(buf, "name %s\n", first.tidy);
11860       SendToProgram(buf, &second);
11861     }
11862
11863     ResetClocks();
11864     if (!first.sendTime || !second.sendTime) {
11865         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11866         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11867     }
11868     if (onmove->sendTime) {
11869       if (onmove->useColors) {
11870         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11871       }
11872       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11873     }
11874     if (onmove->useColors) {
11875       SendToProgram(onmove->twoMachinesColor, onmove);
11876     }
11877     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11878 //    SendToProgram("go\n", onmove);
11879     onmove->maybeThinking = TRUE;
11880     SetMachineThinkingEnables();
11881
11882     StartClocks();
11883
11884     if(bookHit) { // [HGM] book: simulate book reply
11885         static char bookMove[MSG_SIZ]; // a bit generous?
11886
11887         programStats.nodes = programStats.depth = programStats.time = 
11888         programStats.score = programStats.got_only_move = 0;
11889         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11890
11891         strcpy(bookMove, "move ");
11892         strcat(bookMove, bookHit);
11893         savedMessage = bookMove; // args for deferred call
11894         savedState = onmove;
11895         ScheduleDelayedEvent(DeferredBookMove, 1);
11896     }
11897 }
11898
11899 void
11900 TrainingEvent()
11901 {
11902     if (gameMode == Training) {
11903       SetTrainingModeOff();
11904       gameMode = PlayFromGameFile;
11905       DisplayMessage("", _("Training mode off"));
11906     } else {
11907       gameMode = Training;
11908       animateTraining = appData.animate;
11909
11910       /* make sure we are not already at the end of the game */
11911       if (currentMove < forwardMostMove) {
11912         SetTrainingModeOn();
11913         DisplayMessage("", _("Training mode on"));
11914       } else {
11915         gameMode = PlayFromGameFile;
11916         DisplayError(_("Already at end of game"), 0);
11917       }
11918     }
11919     ModeHighlight();
11920 }
11921
11922 void
11923 IcsClientEvent()
11924 {
11925     if (!appData.icsActive) return;
11926     switch (gameMode) {
11927       case IcsPlayingWhite:
11928       case IcsPlayingBlack:
11929       case IcsObserving:
11930       case IcsIdle:
11931       case BeginningOfGame:
11932       case IcsExamining:
11933         return;
11934
11935       case EditGame:
11936         break;
11937
11938       case EditPosition:
11939         EditPositionDone(TRUE);
11940         break;
11941
11942       case AnalyzeMode:
11943       case AnalyzeFile:
11944         ExitAnalyzeMode();
11945         break;
11946         
11947       default:
11948         EditGameEvent();
11949         break;
11950     }
11951
11952     gameMode = IcsIdle;
11953     ModeHighlight();
11954     return;
11955 }
11956
11957
11958 void
11959 EditGameEvent()
11960 {
11961     int i;
11962
11963     switch (gameMode) {
11964       case Training:
11965         SetTrainingModeOff();
11966         break;
11967       case MachinePlaysWhite:
11968       case MachinePlaysBlack:
11969       case BeginningOfGame:
11970         SendToProgram("force\n", &first);
11971         SetUserThinkingEnables();
11972         break;
11973       case PlayFromGameFile:
11974         (void) StopLoadGameTimer();
11975         if (gameFileFP != NULL) {
11976             gameFileFP = NULL;
11977         }
11978         break;
11979       case EditPosition:
11980         EditPositionDone(TRUE);
11981         break;
11982       case AnalyzeMode:
11983       case AnalyzeFile:
11984         ExitAnalyzeMode();
11985         SendToProgram("force\n", &first);
11986         break;
11987       case TwoMachinesPlay:
11988         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11989         ResurrectChessProgram();
11990         SetUserThinkingEnables();
11991         break;
11992       case EndOfGame:
11993         ResurrectChessProgram();
11994         break;
11995       case IcsPlayingBlack:
11996       case IcsPlayingWhite:
11997         DisplayError(_("Warning: You are still playing a game"), 0);
11998         break;
11999       case IcsObserving:
12000         DisplayError(_("Warning: You are still observing a game"), 0);
12001         break;
12002       case IcsExamining:
12003         DisplayError(_("Warning: You are still examining a game"), 0);
12004         break;
12005       case IcsIdle:
12006         break;
12007       case EditGame:
12008       default:
12009         return;
12010     }
12011     
12012     pausing = FALSE;
12013     StopClocks();
12014     first.offeredDraw = second.offeredDraw = 0;
12015
12016     if (gameMode == PlayFromGameFile) {
12017         whiteTimeRemaining = timeRemaining[0][currentMove];
12018         blackTimeRemaining = timeRemaining[1][currentMove];
12019         DisplayTitle("");
12020     }
12021
12022     if (gameMode == MachinePlaysWhite ||
12023         gameMode == MachinePlaysBlack ||
12024         gameMode == TwoMachinesPlay ||
12025         gameMode == EndOfGame) {
12026         i = forwardMostMove;
12027         while (i > currentMove) {
12028             SendToProgram("undo\n", &first);
12029             i--;
12030         }
12031         whiteTimeRemaining = timeRemaining[0][currentMove];
12032         blackTimeRemaining = timeRemaining[1][currentMove];
12033         DisplayBothClocks();
12034         if (whiteFlag || blackFlag) {
12035             whiteFlag = blackFlag = 0;
12036         }
12037         DisplayTitle("");
12038     }           
12039     
12040     gameMode = EditGame;
12041     ModeHighlight();
12042     SetGameInfo();
12043 }
12044
12045
12046 void
12047 EditPositionEvent()
12048 {
12049     if (gameMode == EditPosition) {
12050         EditGameEvent();
12051         return;
12052     }
12053     
12054     EditGameEvent();
12055     if (gameMode != EditGame) return;
12056     
12057     gameMode = EditPosition;
12058     ModeHighlight();
12059     SetGameInfo();
12060     if (currentMove > 0)
12061       CopyBoard(boards[0], boards[currentMove]);
12062     
12063     blackPlaysFirst = !WhiteOnMove(currentMove);
12064     ResetClocks();
12065     currentMove = forwardMostMove = backwardMostMove = 0;
12066     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12067     DisplayMove(-1);
12068 }
12069
12070 void
12071 ExitAnalyzeMode()
12072 {
12073     /* [DM] icsEngineAnalyze - possible call from other functions */
12074     if (appData.icsEngineAnalyze) {
12075         appData.icsEngineAnalyze = FALSE;
12076
12077         DisplayMessage("",_("Close ICS engine analyze..."));
12078     }
12079     if (first.analysisSupport && first.analyzing) {
12080       SendToProgram("exit\n", &first);
12081       first.analyzing = FALSE;
12082     }
12083     thinkOutput[0] = NULLCHAR;
12084 }
12085
12086 void
12087 EditPositionDone(Boolean fakeRights)
12088 {
12089     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12090
12091     startedFromSetupPosition = TRUE;
12092     InitChessProgram(&first, FALSE);
12093     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12094       boards[0][EP_STATUS] = EP_NONE;
12095       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12096     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12097         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12098         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12099       } else boards[0][CASTLING][2] = NoRights;
12100     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12101         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12102         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12103       } else boards[0][CASTLING][5] = NoRights;
12104     }
12105     SendToProgram("force\n", &first);
12106     if (blackPlaysFirst) {
12107         strcpy(moveList[0], "");
12108         strcpy(parseList[0], "");
12109         currentMove = forwardMostMove = backwardMostMove = 1;
12110         CopyBoard(boards[1], boards[0]);
12111     } else {
12112         currentMove = forwardMostMove = backwardMostMove = 0;
12113     }
12114     SendBoard(&first, forwardMostMove);
12115     if (appData.debugMode) {
12116         fprintf(debugFP, "EditPosDone\n");
12117     }
12118     DisplayTitle("");
12119     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12120     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12121     gameMode = EditGame;
12122     ModeHighlight();
12123     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12124     ClearHighlights(); /* [AS] */
12125 }
12126
12127 /* Pause for `ms' milliseconds */
12128 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12129 void
12130 TimeDelay(ms)
12131      long ms;
12132 {
12133     TimeMark m1, m2;
12134
12135     GetTimeMark(&m1);
12136     do {
12137         GetTimeMark(&m2);
12138     } while (SubtractTimeMarks(&m2, &m1) < ms);
12139 }
12140
12141 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12142 void
12143 SendMultiLineToICS(buf)
12144      char *buf;
12145 {
12146     char temp[MSG_SIZ+1], *p;
12147     int len;
12148
12149     len = strlen(buf);
12150     if (len > MSG_SIZ)
12151       len = MSG_SIZ;
12152   
12153     strncpy(temp, buf, len);
12154     temp[len] = 0;
12155
12156     p = temp;
12157     while (*p) {
12158         if (*p == '\n' || *p == '\r')
12159           *p = ' ';
12160         ++p;
12161     }
12162
12163     strcat(temp, "\n");
12164     SendToICS(temp);
12165     SendToPlayer(temp, strlen(temp));
12166 }
12167
12168 void
12169 SetWhiteToPlayEvent()
12170 {
12171     if (gameMode == EditPosition) {
12172         blackPlaysFirst = FALSE;
12173         DisplayBothClocks();    /* works because currentMove is 0 */
12174     } else if (gameMode == IcsExamining) {
12175         SendToICS(ics_prefix);
12176         SendToICS("tomove white\n");
12177     }
12178 }
12179
12180 void
12181 SetBlackToPlayEvent()
12182 {
12183     if (gameMode == EditPosition) {
12184         blackPlaysFirst = TRUE;
12185         currentMove = 1;        /* kludge */
12186         DisplayBothClocks();
12187         currentMove = 0;
12188     } else if (gameMode == IcsExamining) {
12189         SendToICS(ics_prefix);
12190         SendToICS("tomove black\n");
12191     }
12192 }
12193
12194 void
12195 EditPositionMenuEvent(selection, x, y)
12196      ChessSquare selection;
12197      int x, y;
12198 {
12199     char buf[MSG_SIZ];
12200     ChessSquare piece = boards[0][y][x];
12201
12202     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12203
12204     switch (selection) {
12205       case ClearBoard:
12206         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12207             SendToICS(ics_prefix);
12208             SendToICS("bsetup clear\n");
12209         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12210             SendToICS(ics_prefix);
12211             SendToICS("clearboard\n");
12212         } else {
12213             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12214                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12215                 for (y = 0; y < BOARD_HEIGHT; y++) {
12216                     if (gameMode == IcsExamining) {
12217                         if (boards[currentMove][y][x] != EmptySquare) {
12218                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12219                                     AAA + x, ONE + y);
12220                             SendToICS(buf);
12221                         }
12222                     } else {
12223                         boards[0][y][x] = p;
12224                     }
12225                 }
12226             }
12227         }
12228         if (gameMode == EditPosition) {
12229             DrawPosition(FALSE, boards[0]);
12230         }
12231         break;
12232
12233       case WhitePlay:
12234         SetWhiteToPlayEvent();
12235         break;
12236
12237       case BlackPlay:
12238         SetBlackToPlayEvent();
12239         break;
12240
12241       case EmptySquare:
12242         if (gameMode == IcsExamining) {
12243             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12244             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12245             SendToICS(buf);
12246         } else {
12247             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12248                 if(x == BOARD_LEFT-2) {
12249                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12250                     boards[0][y][1] = 0;
12251                 } else
12252                 if(x == BOARD_RGHT+1) {
12253                     if(y >= gameInfo.holdingsSize) break;
12254                     boards[0][y][BOARD_WIDTH-2] = 0;
12255                 } else break;
12256             }
12257             boards[0][y][x] = EmptySquare;
12258             DrawPosition(FALSE, boards[0]);
12259         }
12260         break;
12261
12262       case PromotePiece:
12263         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12264            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12265             selection = (ChessSquare) (PROMOTED piece);
12266         } else if(piece == EmptySquare) selection = WhiteSilver;
12267         else selection = (ChessSquare)((int)piece - 1);
12268         goto defaultlabel;
12269
12270       case DemotePiece:
12271         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12272            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12273             selection = (ChessSquare) (DEMOTED piece);
12274         } else if(piece == EmptySquare) selection = BlackSilver;
12275         else selection = (ChessSquare)((int)piece + 1);       
12276         goto defaultlabel;
12277
12278       case WhiteQueen:
12279       case BlackQueen:
12280         if(gameInfo.variant == VariantShatranj ||
12281            gameInfo.variant == VariantXiangqi  ||
12282            gameInfo.variant == VariantCourier  ||
12283            gameInfo.variant == VariantMakruk     )
12284             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12285         goto defaultlabel;
12286
12287       case WhiteKing:
12288       case BlackKing:
12289         if(gameInfo.variant == VariantXiangqi)
12290             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12291         if(gameInfo.variant == VariantKnightmate)
12292             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12293       default:
12294         defaultlabel:
12295         if (gameMode == IcsExamining) {
12296             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12297             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12298                     PieceToChar(selection), AAA + x, ONE + y);
12299             SendToICS(buf);
12300         } else {
12301             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12302                 int n;
12303                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12304                     n = PieceToNumber(selection - BlackPawn);
12305                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12306                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12307                     boards[0][BOARD_HEIGHT-1-n][1]++;
12308                 } else
12309                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12310                     n = PieceToNumber(selection);
12311                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12312                     boards[0][n][BOARD_WIDTH-1] = selection;
12313                     boards[0][n][BOARD_WIDTH-2]++;
12314                 }
12315             } else
12316             boards[0][y][x] = selection;
12317             DrawPosition(TRUE, boards[0]);
12318         }
12319         break;
12320     }
12321 }
12322
12323
12324 void
12325 DropMenuEvent(selection, x, y)
12326      ChessSquare selection;
12327      int x, y;
12328 {
12329     ChessMove moveType;
12330
12331     switch (gameMode) {
12332       case IcsPlayingWhite:
12333       case MachinePlaysBlack:
12334         if (!WhiteOnMove(currentMove)) {
12335             DisplayMoveError(_("It is Black's turn"));
12336             return;
12337         }
12338         moveType = WhiteDrop;
12339         break;
12340       case IcsPlayingBlack:
12341       case MachinePlaysWhite:
12342         if (WhiteOnMove(currentMove)) {
12343             DisplayMoveError(_("It is White's turn"));
12344             return;
12345         }
12346         moveType = BlackDrop;
12347         break;
12348       case EditGame:
12349         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12350         break;
12351       default:
12352         return;
12353     }
12354
12355     if (moveType == BlackDrop && selection < BlackPawn) {
12356       selection = (ChessSquare) ((int) selection
12357                                  + (int) BlackPawn - (int) WhitePawn);
12358     }
12359     if (boards[currentMove][y][x] != EmptySquare) {
12360         DisplayMoveError(_("That square is occupied"));
12361         return;
12362     }
12363
12364     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12365 }
12366
12367 void
12368 AcceptEvent()
12369 {
12370     /* Accept a pending offer of any kind from opponent */
12371     
12372     if (appData.icsActive) {
12373         SendToICS(ics_prefix);
12374         SendToICS("accept\n");
12375     } else if (cmailMsgLoaded) {
12376         if (currentMove == cmailOldMove &&
12377             commentList[cmailOldMove] != NULL &&
12378             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12379                    "Black offers a draw" : "White offers a draw")) {
12380             TruncateGame();
12381             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12382             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12383         } else {
12384             DisplayError(_("There is no pending offer on this move"), 0);
12385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12386         }
12387     } else {
12388         /* Not used for offers from chess program */
12389     }
12390 }
12391
12392 void
12393 DeclineEvent()
12394 {
12395     /* Decline a pending offer of any kind from opponent */
12396     
12397     if (appData.icsActive) {
12398         SendToICS(ics_prefix);
12399         SendToICS("decline\n");
12400     } else if (cmailMsgLoaded) {
12401         if (currentMove == cmailOldMove &&
12402             commentList[cmailOldMove] != NULL &&
12403             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12404                    "Black offers a draw" : "White offers a draw")) {
12405 #ifdef NOTDEF
12406             AppendComment(cmailOldMove, "Draw declined", TRUE);
12407             DisplayComment(cmailOldMove - 1, "Draw declined");
12408 #endif /*NOTDEF*/
12409         } else {
12410             DisplayError(_("There is no pending offer on this move"), 0);
12411         }
12412     } else {
12413         /* Not used for offers from chess program */
12414     }
12415 }
12416
12417 void
12418 RematchEvent()
12419 {
12420     /* Issue ICS rematch command */
12421     if (appData.icsActive) {
12422         SendToICS(ics_prefix);
12423         SendToICS("rematch\n");
12424     }
12425 }
12426
12427 void
12428 CallFlagEvent()
12429 {
12430     /* Call your opponent's flag (claim a win on time) */
12431     if (appData.icsActive) {
12432         SendToICS(ics_prefix);
12433         SendToICS("flag\n");
12434     } else {
12435         switch (gameMode) {
12436           default:
12437             return;
12438           case MachinePlaysWhite:
12439             if (whiteFlag) {
12440                 if (blackFlag)
12441                   GameEnds(GameIsDrawn, "Both players ran out of time",
12442                            GE_PLAYER);
12443                 else
12444                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12445             } else {
12446                 DisplayError(_("Your opponent is not out of time"), 0);
12447             }
12448             break;
12449           case MachinePlaysBlack:
12450             if (blackFlag) {
12451                 if (whiteFlag)
12452                   GameEnds(GameIsDrawn, "Both players ran out of time",
12453                            GE_PLAYER);
12454                 else
12455                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12456             } else {
12457                 DisplayError(_("Your opponent is not out of time"), 0);
12458             }
12459             break;
12460         }
12461     }
12462 }
12463
12464 void
12465 DrawEvent()
12466 {
12467     /* Offer draw or accept pending draw offer from opponent */
12468     
12469     if (appData.icsActive) {
12470         /* Note: tournament rules require draw offers to be
12471            made after you make your move but before you punch
12472            your clock.  Currently ICS doesn't let you do that;
12473            instead, you immediately punch your clock after making
12474            a move, but you can offer a draw at any time. */
12475         
12476         SendToICS(ics_prefix);
12477         SendToICS("draw\n");
12478         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12479     } else if (cmailMsgLoaded) {
12480         if (currentMove == cmailOldMove &&
12481             commentList[cmailOldMove] != NULL &&
12482             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12483                    "Black offers a draw" : "White offers a draw")) {
12484             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12486         } else if (currentMove == cmailOldMove + 1) {
12487             char *offer = WhiteOnMove(cmailOldMove) ?
12488               "White offers a draw" : "Black offers a draw";
12489             AppendComment(currentMove, offer, TRUE);
12490             DisplayComment(currentMove - 1, offer);
12491             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12492         } else {
12493             DisplayError(_("You must make your move before offering a draw"), 0);
12494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12495         }
12496     } else if (first.offeredDraw) {
12497         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12498     } else {
12499         if (first.sendDrawOffers) {
12500             SendToProgram("draw\n", &first);
12501             userOfferedDraw = TRUE;
12502         }
12503     }
12504 }
12505
12506 void
12507 AdjournEvent()
12508 {
12509     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12510     
12511     if (appData.icsActive) {
12512         SendToICS(ics_prefix);
12513         SendToICS("adjourn\n");
12514     } else {
12515         /* Currently GNU Chess doesn't offer or accept Adjourns */
12516     }
12517 }
12518
12519
12520 void
12521 AbortEvent()
12522 {
12523     /* Offer Abort or accept pending Abort offer from opponent */
12524     
12525     if (appData.icsActive) {
12526         SendToICS(ics_prefix);
12527         SendToICS("abort\n");
12528     } else {
12529         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12530     }
12531 }
12532
12533 void
12534 ResignEvent()
12535 {
12536     /* Resign.  You can do this even if it's not your turn. */
12537     
12538     if (appData.icsActive) {
12539         SendToICS(ics_prefix);
12540         SendToICS("resign\n");
12541     } else {
12542         switch (gameMode) {
12543           case MachinePlaysWhite:
12544             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12545             break;
12546           case MachinePlaysBlack:
12547             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12548             break;
12549           case EditGame:
12550             if (cmailMsgLoaded) {
12551                 TruncateGame();
12552                 if (WhiteOnMove(cmailOldMove)) {
12553                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12554                 } else {
12555                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12556                 }
12557                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12558             }
12559             break;
12560           default:
12561             break;
12562         }
12563     }
12564 }
12565
12566
12567 void
12568 StopObservingEvent()
12569 {
12570     /* Stop observing current games */
12571     SendToICS(ics_prefix);
12572     SendToICS("unobserve\n");
12573 }
12574
12575 void
12576 StopExaminingEvent()
12577 {
12578     /* Stop observing current game */
12579     SendToICS(ics_prefix);
12580     SendToICS("unexamine\n");
12581 }
12582
12583 void
12584 ForwardInner(target)
12585      int target;
12586 {
12587     int limit;
12588
12589     if (appData.debugMode)
12590         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12591                 target, currentMove, forwardMostMove);
12592
12593     if (gameMode == EditPosition)
12594       return;
12595
12596     if (gameMode == PlayFromGameFile && !pausing)
12597       PauseEvent();
12598     
12599     if (gameMode == IcsExamining && pausing)
12600       limit = pauseExamForwardMostMove;
12601     else
12602       limit = forwardMostMove;
12603     
12604     if (target > limit) target = limit;
12605
12606     if (target > 0 && moveList[target - 1][0]) {
12607         int fromX, fromY, toX, toY;
12608         toX = moveList[target - 1][2] - AAA;
12609         toY = moveList[target - 1][3] - ONE;
12610         if (moveList[target - 1][1] == '@') {
12611             if (appData.highlightLastMove) {
12612                 SetHighlights(-1, -1, toX, toY);
12613             }
12614         } else {
12615             fromX = moveList[target - 1][0] - AAA;
12616             fromY = moveList[target - 1][1] - ONE;
12617             if (target == currentMove + 1) {
12618                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12619             }
12620             if (appData.highlightLastMove) {
12621                 SetHighlights(fromX, fromY, toX, toY);
12622             }
12623         }
12624     }
12625     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12626         gameMode == Training || gameMode == PlayFromGameFile || 
12627         gameMode == AnalyzeFile) {
12628         while (currentMove < target) {
12629             SendMoveToProgram(currentMove++, &first);
12630         }
12631     } else {
12632         currentMove = target;
12633     }
12634     
12635     if (gameMode == EditGame || gameMode == EndOfGame) {
12636         whiteTimeRemaining = timeRemaining[0][currentMove];
12637         blackTimeRemaining = timeRemaining[1][currentMove];
12638     }
12639     DisplayBothClocks();
12640     DisplayMove(currentMove - 1);
12641     DrawPosition(FALSE, boards[currentMove]);
12642     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12643     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12644         DisplayComment(currentMove - 1, commentList[currentMove]);
12645     }
12646 }
12647
12648
12649 void
12650 ForwardEvent()
12651 {
12652     if (gameMode == IcsExamining && !pausing) {
12653         SendToICS(ics_prefix);
12654         SendToICS("forward\n");
12655     } else {
12656         ForwardInner(currentMove + 1);
12657     }
12658 }
12659
12660 void
12661 ToEndEvent()
12662 {
12663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12664         /* to optimze, we temporarily turn off analysis mode while we feed
12665          * the remaining moves to the engine. Otherwise we get analysis output
12666          * after each move.
12667          */ 
12668         if (first.analysisSupport) {
12669           SendToProgram("exit\nforce\n", &first);
12670           first.analyzing = FALSE;
12671         }
12672     }
12673         
12674     if (gameMode == IcsExamining && !pausing) {
12675         SendToICS(ics_prefix);
12676         SendToICS("forward 999999\n");
12677     } else {
12678         ForwardInner(forwardMostMove);
12679     }
12680
12681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12682         /* we have fed all the moves, so reactivate analysis mode */
12683         SendToProgram("analyze\n", &first);
12684         first.analyzing = TRUE;
12685         /*first.maybeThinking = TRUE;*/
12686         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12687     }
12688 }
12689
12690 void
12691 BackwardInner(target)
12692      int target;
12693 {
12694     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12695
12696     if (appData.debugMode)
12697         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12698                 target, currentMove, forwardMostMove);
12699
12700     if (gameMode == EditPosition) return;
12701     if (currentMove <= backwardMostMove) {
12702         ClearHighlights();
12703         DrawPosition(full_redraw, boards[currentMove]);
12704         return;
12705     }
12706     if (gameMode == PlayFromGameFile && !pausing)
12707       PauseEvent();
12708     
12709     if (moveList[target][0]) {
12710         int fromX, fromY, toX, toY;
12711         toX = moveList[target][2] - AAA;
12712         toY = moveList[target][3] - ONE;
12713         if (moveList[target][1] == '@') {
12714             if (appData.highlightLastMove) {
12715                 SetHighlights(-1, -1, toX, toY);
12716             }
12717         } else {
12718             fromX = moveList[target][0] - AAA;
12719             fromY = moveList[target][1] - ONE;
12720             if (target == currentMove - 1) {
12721                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12722             }
12723             if (appData.highlightLastMove) {
12724                 SetHighlights(fromX, fromY, toX, toY);
12725             }
12726         }
12727     }
12728     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12729         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12730         while (currentMove > target) {
12731             SendToProgram("undo\n", &first);
12732             currentMove--;
12733         }
12734     } else {
12735         currentMove = target;
12736     }
12737     
12738     if (gameMode == EditGame || gameMode == EndOfGame) {
12739         whiteTimeRemaining = timeRemaining[0][currentMove];
12740         blackTimeRemaining = timeRemaining[1][currentMove];
12741     }
12742     DisplayBothClocks();
12743     DisplayMove(currentMove - 1);
12744     DrawPosition(full_redraw, boards[currentMove]);
12745     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12746     // [HGM] PV info: routine tests if comment empty
12747     DisplayComment(currentMove - 1, commentList[currentMove]);
12748 }
12749
12750 void
12751 BackwardEvent()
12752 {
12753     if (gameMode == IcsExamining && !pausing) {
12754         SendToICS(ics_prefix);
12755         SendToICS("backward\n");
12756     } else {
12757         BackwardInner(currentMove - 1);
12758     }
12759 }
12760
12761 void
12762 ToStartEvent()
12763 {
12764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12765         /* to optimize, we temporarily turn off analysis mode while we undo
12766          * all the moves. Otherwise we get analysis output after each undo.
12767          */ 
12768         if (first.analysisSupport) {
12769           SendToProgram("exit\nforce\n", &first);
12770           first.analyzing = FALSE;
12771         }
12772     }
12773
12774     if (gameMode == IcsExamining && !pausing) {
12775         SendToICS(ics_prefix);
12776         SendToICS("backward 999999\n");
12777     } else {
12778         BackwardInner(backwardMostMove);
12779     }
12780
12781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12782         /* we have fed all the moves, so reactivate analysis mode */
12783         SendToProgram("analyze\n", &first);
12784         first.analyzing = TRUE;
12785         /*first.maybeThinking = TRUE;*/
12786         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12787     }
12788 }
12789
12790 void
12791 ToNrEvent(int to)
12792 {
12793   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12794   if (to >= forwardMostMove) to = forwardMostMove;
12795   if (to <= backwardMostMove) to = backwardMostMove;
12796   if (to < currentMove) {
12797     BackwardInner(to);
12798   } else {
12799     ForwardInner(to);
12800   }
12801 }
12802
12803 void
12804 RevertEvent(Boolean annotate)
12805 {
12806     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12807         return;
12808     }
12809     if (gameMode != IcsExamining) {
12810         DisplayError(_("You are not examining a game"), 0);
12811         return;
12812     }
12813     if (pausing) {
12814         DisplayError(_("You can't revert while pausing"), 0);
12815         return;
12816     }
12817     SendToICS(ics_prefix);
12818     SendToICS("revert\n");
12819 }
12820
12821 void
12822 RetractMoveEvent()
12823 {
12824     switch (gameMode) {
12825       case MachinePlaysWhite:
12826       case MachinePlaysBlack:
12827         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12828             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12829             return;
12830         }
12831         if (forwardMostMove < 2) return;
12832         currentMove = forwardMostMove = forwardMostMove - 2;
12833         whiteTimeRemaining = timeRemaining[0][currentMove];
12834         blackTimeRemaining = timeRemaining[1][currentMove];
12835         DisplayBothClocks();
12836         DisplayMove(currentMove - 1);
12837         ClearHighlights();/*!! could figure this out*/
12838         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12839         SendToProgram("remove\n", &first);
12840         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12841         break;
12842
12843       case BeginningOfGame:
12844       default:
12845         break;
12846
12847       case IcsPlayingWhite:
12848       case IcsPlayingBlack:
12849         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12850             SendToICS(ics_prefix);
12851             SendToICS("takeback 2\n");
12852         } else {
12853             SendToICS(ics_prefix);
12854             SendToICS("takeback 1\n");
12855         }
12856         break;
12857     }
12858 }
12859
12860 void
12861 MoveNowEvent()
12862 {
12863     ChessProgramState *cps;
12864
12865     switch (gameMode) {
12866       case MachinePlaysWhite:
12867         if (!WhiteOnMove(forwardMostMove)) {
12868             DisplayError(_("It is your turn"), 0);
12869             return;
12870         }
12871         cps = &first;
12872         break;
12873       case MachinePlaysBlack:
12874         if (WhiteOnMove(forwardMostMove)) {
12875             DisplayError(_("It is your turn"), 0);
12876             return;
12877         }
12878         cps = &first;
12879         break;
12880       case TwoMachinesPlay:
12881         if (WhiteOnMove(forwardMostMove) ==
12882             (first.twoMachinesColor[0] == 'w')) {
12883             cps = &first;
12884         } else {
12885             cps = &second;
12886         }
12887         break;
12888       case BeginningOfGame:
12889       default:
12890         return;
12891     }
12892     SendToProgram("?\n", cps);
12893 }
12894
12895 void
12896 TruncateGameEvent()
12897 {
12898     EditGameEvent();
12899     if (gameMode != EditGame) return;
12900     TruncateGame();
12901 }
12902
12903 void
12904 TruncateGame()
12905 {
12906     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12907     if (forwardMostMove > currentMove) {
12908         if (gameInfo.resultDetails != NULL) {
12909             free(gameInfo.resultDetails);
12910             gameInfo.resultDetails = NULL;
12911             gameInfo.result = GameUnfinished;
12912         }
12913         forwardMostMove = currentMove;
12914         HistorySet(parseList, backwardMostMove, forwardMostMove,
12915                    currentMove-1);
12916     }
12917 }
12918
12919 void
12920 HintEvent()
12921 {
12922     if (appData.noChessProgram) return;
12923     switch (gameMode) {
12924       case MachinePlaysWhite:
12925         if (WhiteOnMove(forwardMostMove)) {
12926             DisplayError(_("Wait until your turn"), 0);
12927             return;
12928         }
12929         break;
12930       case BeginningOfGame:
12931       case MachinePlaysBlack:
12932         if (!WhiteOnMove(forwardMostMove)) {
12933             DisplayError(_("Wait until your turn"), 0);
12934             return;
12935         }
12936         break;
12937       default:
12938         DisplayError(_("No hint available"), 0);
12939         return;
12940     }
12941     SendToProgram("hint\n", &first);
12942     hintRequested = TRUE;
12943 }
12944
12945 void
12946 BookEvent()
12947 {
12948     if (appData.noChessProgram) return;
12949     switch (gameMode) {
12950       case MachinePlaysWhite:
12951         if (WhiteOnMove(forwardMostMove)) {
12952             DisplayError(_("Wait until your turn"), 0);
12953             return;
12954         }
12955         break;
12956       case BeginningOfGame:
12957       case MachinePlaysBlack:
12958         if (!WhiteOnMove(forwardMostMove)) {
12959             DisplayError(_("Wait until your turn"), 0);
12960             return;
12961         }
12962         break;
12963       case EditPosition:
12964         EditPositionDone(TRUE);
12965         break;
12966       case TwoMachinesPlay:
12967         return;
12968       default:
12969         break;
12970     }
12971     SendToProgram("bk\n", &first);
12972     bookOutput[0] = NULLCHAR;
12973     bookRequested = TRUE;
12974 }
12975
12976 void
12977 AboutGameEvent()
12978 {
12979     char *tags = PGNTags(&gameInfo);
12980     TagsPopUp(tags, CmailMsg());
12981     free(tags);
12982 }
12983
12984 /* end button procedures */
12985
12986 void
12987 PrintPosition(fp, move)
12988      FILE *fp;
12989      int move;
12990 {
12991     int i, j;
12992     
12993     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12994         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12995             char c = PieceToChar(boards[move][i][j]);
12996             fputc(c == 'x' ? '.' : c, fp);
12997             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12998         }
12999     }
13000     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13001       fprintf(fp, "white to play\n");
13002     else
13003       fprintf(fp, "black to play\n");
13004 }
13005
13006 void
13007 PrintOpponents(fp)
13008      FILE *fp;
13009 {
13010     if (gameInfo.white != NULL) {
13011         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13012     } else {
13013         fprintf(fp, "\n");
13014     }
13015 }
13016
13017 /* Find last component of program's own name, using some heuristics */
13018 void
13019 TidyProgramName(prog, host, buf)
13020      char *prog, *host, buf[MSG_SIZ];
13021 {
13022     char *p, *q;
13023     int local = (strcmp(host, "localhost") == 0);
13024     while (!local && (p = strchr(prog, ';')) != NULL) {
13025         p++;
13026         while (*p == ' ') p++;
13027         prog = p;
13028     }
13029     if (*prog == '"' || *prog == '\'') {
13030         q = strchr(prog + 1, *prog);
13031     } else {
13032         q = strchr(prog, ' ');
13033     }
13034     if (q == NULL) q = prog + strlen(prog);
13035     p = q;
13036     while (p >= prog && *p != '/' && *p != '\\') p--;
13037     p++;
13038     if(p == prog && *p == '"') p++;
13039     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13040     memcpy(buf, p, q - p);
13041     buf[q - p] = NULLCHAR;
13042     if (!local) {
13043         strcat(buf, "@");
13044         strcat(buf, host);
13045     }
13046 }
13047
13048 char *
13049 TimeControlTagValue()
13050 {
13051     char buf[MSG_SIZ];
13052     if (!appData.clockMode) {
13053         strcpy(buf, "-");
13054     } else if (movesPerSession > 0) {
13055         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13056     } else if (timeIncrement == 0) {
13057         sprintf(buf, "%ld", timeControl/1000);
13058     } else {
13059         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13060     }
13061     return StrSave(buf);
13062 }
13063
13064 void
13065 SetGameInfo()
13066 {
13067     /* This routine is used only for certain modes */
13068     VariantClass v = gameInfo.variant;
13069     ChessMove r = GameUnfinished;
13070     char *p = NULL;
13071
13072     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13073         r = gameInfo.result; 
13074         p = gameInfo.resultDetails; 
13075         gameInfo.resultDetails = NULL;
13076     }
13077     ClearGameInfo(&gameInfo);
13078     gameInfo.variant = v;
13079
13080     switch (gameMode) {
13081       case MachinePlaysWhite:
13082         gameInfo.event = StrSave( appData.pgnEventHeader );
13083         gameInfo.site = StrSave(HostName());
13084         gameInfo.date = PGNDate();
13085         gameInfo.round = StrSave("-");
13086         gameInfo.white = StrSave(first.tidy);
13087         gameInfo.black = StrSave(UserName());
13088         gameInfo.timeControl = TimeControlTagValue();
13089         break;
13090
13091       case MachinePlaysBlack:
13092         gameInfo.event = StrSave( appData.pgnEventHeader );
13093         gameInfo.site = StrSave(HostName());
13094         gameInfo.date = PGNDate();
13095         gameInfo.round = StrSave("-");
13096         gameInfo.white = StrSave(UserName());
13097         gameInfo.black = StrSave(first.tidy);
13098         gameInfo.timeControl = TimeControlTagValue();
13099         break;
13100
13101       case TwoMachinesPlay:
13102         gameInfo.event = StrSave( appData.pgnEventHeader );
13103         gameInfo.site = StrSave(HostName());
13104         gameInfo.date = PGNDate();
13105         if (matchGame > 0) {
13106             char buf[MSG_SIZ];
13107             sprintf(buf, "%d", matchGame);
13108             gameInfo.round = StrSave(buf);
13109         } else {
13110             gameInfo.round = StrSave("-");
13111         }
13112         if (first.twoMachinesColor[0] == 'w') {
13113             gameInfo.white = StrSave(first.tidy);
13114             gameInfo.black = StrSave(second.tidy);
13115         } else {
13116             gameInfo.white = StrSave(second.tidy);
13117             gameInfo.black = StrSave(first.tidy);
13118         }
13119         gameInfo.timeControl = TimeControlTagValue();
13120         break;
13121
13122       case EditGame:
13123         gameInfo.event = StrSave("Edited game");
13124         gameInfo.site = StrSave(HostName());
13125         gameInfo.date = PGNDate();
13126         gameInfo.round = StrSave("-");
13127         gameInfo.white = StrSave("-");
13128         gameInfo.black = StrSave("-");
13129         gameInfo.result = r;
13130         gameInfo.resultDetails = p;
13131         break;
13132
13133       case EditPosition:
13134         gameInfo.event = StrSave("Edited position");
13135         gameInfo.site = StrSave(HostName());
13136         gameInfo.date = PGNDate();
13137         gameInfo.round = StrSave("-");
13138         gameInfo.white = StrSave("-");
13139         gameInfo.black = StrSave("-");
13140         break;
13141
13142       case IcsPlayingWhite:
13143       case IcsPlayingBlack:
13144       case IcsObserving:
13145       case IcsExamining:
13146         break;
13147
13148       case PlayFromGameFile:
13149         gameInfo.event = StrSave("Game from non-PGN file");
13150         gameInfo.site = StrSave(HostName());
13151         gameInfo.date = PGNDate();
13152         gameInfo.round = StrSave("-");
13153         gameInfo.white = StrSave("?");
13154         gameInfo.black = StrSave("?");
13155         break;
13156
13157       default:
13158         break;
13159     }
13160 }
13161
13162 void
13163 ReplaceComment(index, text)
13164      int index;
13165      char *text;
13166 {
13167     int len;
13168
13169     while (*text == '\n') text++;
13170     len = strlen(text);
13171     while (len > 0 && text[len - 1] == '\n') len--;
13172
13173     if (commentList[index] != NULL)
13174       free(commentList[index]);
13175
13176     if (len == 0) {
13177         commentList[index] = NULL;
13178         return;
13179     }
13180   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13181       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13182       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13183     commentList[index] = (char *) malloc(len + 2);
13184     strncpy(commentList[index], text, len);
13185     commentList[index][len] = '\n';
13186     commentList[index][len + 1] = NULLCHAR;
13187   } else { 
13188     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13189     char *p;
13190     commentList[index] = (char *) malloc(len + 6);
13191     strcpy(commentList[index], "{\n");
13192     strncpy(commentList[index]+2, text, len);
13193     commentList[index][len+2] = NULLCHAR;
13194     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13195     strcat(commentList[index], "\n}\n");
13196   }
13197 }
13198
13199 void
13200 CrushCRs(text)
13201      char *text;
13202 {
13203   char *p = text;
13204   char *q = text;
13205   char ch;
13206
13207   do {
13208     ch = *p++;
13209     if (ch == '\r') continue;
13210     *q++ = ch;
13211   } while (ch != '\0');
13212 }
13213
13214 void
13215 AppendComment(index, text, addBraces)
13216      int index;
13217      char *text;
13218      Boolean addBraces; // [HGM] braces: tells if we should add {}
13219 {
13220     int oldlen, len;
13221     char *old;
13222
13223 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13224     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13225
13226     CrushCRs(text);
13227     while (*text == '\n') text++;
13228     len = strlen(text);
13229     while (len > 0 && text[len - 1] == '\n') len--;
13230
13231     if (len == 0) return;
13232
13233     if (commentList[index] != NULL) {
13234         old = commentList[index];
13235         oldlen = strlen(old);
13236         while(commentList[index][oldlen-1] ==  '\n')
13237           commentList[index][--oldlen] = NULLCHAR;
13238         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13239         strcpy(commentList[index], old);
13240         free(old);
13241         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13242         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13243           if(addBraces) addBraces = FALSE; else { text++; len--; }
13244           while (*text == '\n') { text++; len--; }
13245           commentList[index][--oldlen] = NULLCHAR;
13246       }
13247         if(addBraces) strcat(commentList[index], "\n{\n");
13248         else          strcat(commentList[index], "\n");
13249         strcat(commentList[index], text);
13250         if(addBraces) strcat(commentList[index], "\n}\n");
13251         else          strcat(commentList[index], "\n");
13252     } else {
13253         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13254         if(addBraces)
13255              strcpy(commentList[index], "{\n");
13256         else commentList[index][0] = NULLCHAR;
13257         strcat(commentList[index], text);
13258         strcat(commentList[index], "\n");
13259         if(addBraces) strcat(commentList[index], "}\n");
13260     }
13261 }
13262
13263 static char * FindStr( char * text, char * sub_text )
13264 {
13265     char * result = strstr( text, sub_text );
13266
13267     if( result != NULL ) {
13268         result += strlen( sub_text );
13269     }
13270
13271     return result;
13272 }
13273
13274 /* [AS] Try to extract PV info from PGN comment */
13275 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13276 char *GetInfoFromComment( int index, char * text )
13277 {
13278     char * sep = text;
13279
13280     if( text != NULL && index > 0 ) {
13281         int score = 0;
13282         int depth = 0;
13283         int time = -1, sec = 0, deci;
13284         char * s_eval = FindStr( text, "[%eval " );
13285         char * s_emt = FindStr( text, "[%emt " );
13286
13287         if( s_eval != NULL || s_emt != NULL ) {
13288             /* New style */
13289             char delim;
13290
13291             if( s_eval != NULL ) {
13292                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13293                     return text;
13294                 }
13295
13296                 if( delim != ']' ) {
13297                     return text;
13298                 }
13299             }
13300
13301             if( s_emt != NULL ) {
13302             }
13303                 return text;
13304         }
13305         else {
13306             /* We expect something like: [+|-]nnn.nn/dd */
13307             int score_lo = 0;
13308
13309             if(*text != '{') return text; // [HGM] braces: must be normal comment
13310
13311             sep = strchr( text, '/' );
13312             if( sep == NULL || sep < (text+4) ) {
13313                 return text;
13314             }
13315
13316             time = -1; sec = -1; deci = -1;
13317             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13318                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13319                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13320                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13321                 return text;
13322             }
13323
13324             if( score_lo < 0 || score_lo >= 100 ) {
13325                 return text;
13326             }
13327
13328             if(sec >= 0) time = 600*time + 10*sec; else
13329             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13330
13331             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13332
13333             /* [HGM] PV time: now locate end of PV info */
13334             while( *++sep >= '0' && *sep <= '9'); // strip depth
13335             if(time >= 0)
13336             while( *++sep >= '0' && *sep <= '9'); // strip time
13337             if(sec >= 0)
13338             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13339             if(deci >= 0)
13340             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13341             while(*sep == ' ') sep++;
13342         }
13343
13344         if( depth <= 0 ) {
13345             return text;
13346         }
13347
13348         if( time < 0 ) {
13349             time = -1;
13350         }
13351
13352         pvInfoList[index-1].depth = depth;
13353         pvInfoList[index-1].score = score;
13354         pvInfoList[index-1].time  = 10*time; // centi-sec
13355         if(*sep == '}') *sep = 0; else *--sep = '{';
13356     }
13357     return sep;
13358 }
13359
13360 void
13361 SendToProgram(message, cps)
13362      char *message;
13363      ChessProgramState *cps;
13364 {
13365     int count, outCount, error;
13366     char buf[MSG_SIZ];
13367
13368     if (cps->pr == NULL) return;
13369     Attention(cps);
13370     
13371     if (appData.debugMode) {
13372         TimeMark now;
13373         GetTimeMark(&now);
13374         fprintf(debugFP, "%ld >%-6s: %s", 
13375                 SubtractTimeMarks(&now, &programStartTime),
13376                 cps->which, message);
13377     }
13378     
13379     count = strlen(message);
13380     outCount = OutputToProcess(cps->pr, message, count, &error);
13381     if (outCount < count && !exiting 
13382                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13383         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13384         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13385             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13386                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13387                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13388             } else {
13389                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13390             }
13391             gameInfo.resultDetails = StrSave(buf);
13392         }
13393         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13394     }
13395 }
13396
13397 void
13398 ReceiveFromProgram(isr, closure, message, count, error)
13399      InputSourceRef isr;
13400      VOIDSTAR closure;
13401      char *message;
13402      int count;
13403      int error;
13404 {
13405     char *end_str;
13406     char buf[MSG_SIZ];
13407     ChessProgramState *cps = (ChessProgramState *)closure;
13408
13409     if (isr != cps->isr) return; /* Killed intentionally */
13410     if (count <= 0) {
13411         if (count == 0) {
13412             sprintf(buf,
13413                     _("Error: %s chess program (%s) exited unexpectedly"),
13414                     cps->which, cps->program);
13415         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13416                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13417                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13418                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13419                 } else {
13420                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13421                 }
13422                 gameInfo.resultDetails = StrSave(buf);
13423             }
13424             RemoveInputSource(cps->isr);
13425             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13426         } else {
13427             sprintf(buf,
13428                     _("Error reading from %s chess program (%s)"),
13429                     cps->which, cps->program);
13430             RemoveInputSource(cps->isr);
13431
13432             /* [AS] Program is misbehaving badly... kill it */
13433             if( count == -2 ) {
13434                 DestroyChildProcess( cps->pr, 9 );
13435                 cps->pr = NoProc;
13436             }
13437
13438             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13439         }
13440         return;
13441     }
13442     
13443     if ((end_str = strchr(message, '\r')) != NULL)
13444       *end_str = NULLCHAR;
13445     if ((end_str = strchr(message, '\n')) != NULL)
13446       *end_str = NULLCHAR;
13447     
13448     if (appData.debugMode) {
13449         TimeMark now; int print = 1;
13450         char *quote = ""; char c; int i;
13451
13452         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13453                 char start = message[0];
13454                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13455                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13456                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13457                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13458                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13459                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13460                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13461                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13462                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13463                     print = (appData.engineComments >= 2);
13464                 }
13465                 message[0] = start; // restore original message
13466         }
13467         if(print) {
13468                 GetTimeMark(&now);
13469                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13470                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13471                         quote,
13472                         message);
13473         }
13474     }
13475
13476     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13477     if (appData.icsEngineAnalyze) {
13478         if (strstr(message, "whisper") != NULL ||
13479              strstr(message, "kibitz") != NULL || 
13480             strstr(message, "tellics") != NULL) return;
13481     }
13482
13483     HandleMachineMove(message, cps);
13484 }
13485
13486
13487 void
13488 SendTimeControl(cps, mps, tc, inc, sd, st)
13489      ChessProgramState *cps;
13490      int mps, inc, sd, st;
13491      long tc;
13492 {
13493     char buf[MSG_SIZ];
13494     int seconds;
13495
13496     if( timeControl_2 > 0 ) {
13497         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13498             tc = timeControl_2;
13499         }
13500     }
13501     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13502     inc /= cps->timeOdds;
13503     st  /= cps->timeOdds;
13504
13505     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13506
13507     if (st > 0) {
13508       /* Set exact time per move, normally using st command */
13509       if (cps->stKludge) {
13510         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13511         seconds = st % 60;
13512         if (seconds == 0) {
13513           sprintf(buf, "level 1 %d\n", st/60);
13514         } else {
13515           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13516         }
13517       } else {
13518         sprintf(buf, "st %d\n", st);
13519       }
13520     } else {
13521       /* Set conventional or incremental time control, using level command */
13522       if (seconds == 0) {
13523         /* Note old gnuchess bug -- minutes:seconds used to not work.
13524            Fixed in later versions, but still avoid :seconds
13525            when seconds is 0. */
13526         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13527       } else {
13528         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13529                 seconds, inc/1000);
13530       }
13531     }
13532     SendToProgram(buf, cps);
13533
13534     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13535     /* Orthogonally, limit search to given depth */
13536     if (sd > 0) {
13537       if (cps->sdKludge) {
13538         sprintf(buf, "depth\n%d\n", sd);
13539       } else {
13540         sprintf(buf, "sd %d\n", sd);
13541       }
13542       SendToProgram(buf, cps);
13543     }
13544
13545     if(cps->nps > 0) { /* [HGM] nps */
13546         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13547         else {
13548                 sprintf(buf, "nps %d\n", cps->nps);
13549               SendToProgram(buf, cps);
13550         }
13551     }
13552 }
13553
13554 ChessProgramState *WhitePlayer()
13555 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13556 {
13557     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13558        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13559         return &second;
13560     return &first;
13561 }
13562
13563 void
13564 SendTimeRemaining(cps, machineWhite)
13565      ChessProgramState *cps;
13566      int /*boolean*/ machineWhite;
13567 {
13568     char message[MSG_SIZ];
13569     long time, otime;
13570
13571     /* Note: this routine must be called when the clocks are stopped
13572        or when they have *just* been set or switched; otherwise
13573        it will be off by the time since the current tick started.
13574     */
13575     if (machineWhite) {
13576         time = whiteTimeRemaining / 10;
13577         otime = blackTimeRemaining / 10;
13578     } else {
13579         time = blackTimeRemaining / 10;
13580         otime = whiteTimeRemaining / 10;
13581     }
13582     /* [HGM] translate opponent's time by time-odds factor */
13583     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13584     if (appData.debugMode) {
13585         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13586     }
13587
13588     if (time <= 0) time = 1;
13589     if (otime <= 0) otime = 1;
13590     
13591     sprintf(message, "time %ld\n", time);
13592     SendToProgram(message, cps);
13593
13594     sprintf(message, "otim %ld\n", otime);
13595     SendToProgram(message, cps);
13596 }
13597
13598 int
13599 BoolFeature(p, name, loc, cps)
13600      char **p;
13601      char *name;
13602      int *loc;
13603      ChessProgramState *cps;
13604 {
13605   char buf[MSG_SIZ];
13606   int len = strlen(name);
13607   int val;
13608   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13609     (*p) += len + 1;
13610     sscanf(*p, "%d", &val);
13611     *loc = (val != 0);
13612     while (**p && **p != ' ') (*p)++;
13613     sprintf(buf, "accepted %s\n", name);
13614     SendToProgram(buf, cps);
13615     return TRUE;
13616   }
13617   return FALSE;
13618 }
13619
13620 int
13621 IntFeature(p, name, loc, cps)
13622      char **p;
13623      char *name;
13624      int *loc;
13625      ChessProgramState *cps;
13626 {
13627   char buf[MSG_SIZ];
13628   int len = strlen(name);
13629   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13630     (*p) += len + 1;
13631     sscanf(*p, "%d", loc);
13632     while (**p && **p != ' ') (*p)++;
13633     sprintf(buf, "accepted %s\n", name);
13634     SendToProgram(buf, cps);
13635     return TRUE;
13636   }
13637   return FALSE;
13638 }
13639
13640 int
13641 StringFeature(p, name, loc, cps)
13642      char **p;
13643      char *name;
13644      char loc[];
13645      ChessProgramState *cps;
13646 {
13647   char buf[MSG_SIZ];
13648   int len = strlen(name);
13649   if (strncmp((*p), name, len) == 0
13650       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13651     (*p) += len + 2;
13652     sscanf(*p, "%[^\"]", loc);
13653     while (**p && **p != '\"') (*p)++;
13654     if (**p == '\"') (*p)++;
13655     sprintf(buf, "accepted %s\n", name);
13656     SendToProgram(buf, cps);
13657     return TRUE;
13658   }
13659   return FALSE;
13660 }
13661
13662 int 
13663 ParseOption(Option *opt, ChessProgramState *cps)
13664 // [HGM] options: process the string that defines an engine option, and determine
13665 // name, type, default value, and allowed value range
13666 {
13667         char *p, *q, buf[MSG_SIZ];
13668         int n, min = (-1)<<31, max = 1<<31, def;
13669
13670         if(p = strstr(opt->name, " -spin ")) {
13671             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13672             if(max < min) max = min; // enforce consistency
13673             if(def < min) def = min;
13674             if(def > max) def = max;
13675             opt->value = def;
13676             opt->min = min;
13677             opt->max = max;
13678             opt->type = Spin;
13679         } else if((p = strstr(opt->name, " -slider "))) {
13680             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13681             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13682             if(max < min) max = min; // enforce consistency
13683             if(def < min) def = min;
13684             if(def > max) def = max;
13685             opt->value = def;
13686             opt->min = min;
13687             opt->max = max;
13688             opt->type = Spin; // Slider;
13689         } else if((p = strstr(opt->name, " -string "))) {
13690             opt->textValue = p+9;
13691             opt->type = TextBox;
13692         } else if((p = strstr(opt->name, " -file "))) {
13693             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13694             opt->textValue = p+7;
13695             opt->type = TextBox; // FileName;
13696         } else if((p = strstr(opt->name, " -path "))) {
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; // PathName;
13700         } else if(p = strstr(opt->name, " -check ")) {
13701             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13702             opt->value = (def != 0);
13703             opt->type = CheckBox;
13704         } else if(p = strstr(opt->name, " -combo ")) {
13705             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13706             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13707             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13708             opt->value = n = 0;
13709             while(q = StrStr(q, " /// ")) {
13710                 n++; *q = 0;    // count choices, and null-terminate each of them
13711                 q += 5;
13712                 if(*q == '*') { // remember default, which is marked with * prefix
13713                     q++;
13714                     opt->value = n;
13715                 }
13716                 cps->comboList[cps->comboCnt++] = q;
13717             }
13718             cps->comboList[cps->comboCnt++] = NULL;
13719             opt->max = n + 1;
13720             opt->type = ComboBox;
13721         } else if(p = strstr(opt->name, " -button")) {
13722             opt->type = Button;
13723         } else if(p = strstr(opt->name, " -save")) {
13724             opt->type = SaveButton;
13725         } else return FALSE;
13726         *p = 0; // terminate option name
13727         // now look if the command-line options define a setting for this engine option.
13728         if(cps->optionSettings && cps->optionSettings[0])
13729             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13730         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13731                 sprintf(buf, "option %s", p);
13732                 if(p = strstr(buf, ",")) *p = 0;
13733                 strcat(buf, "\n");
13734                 SendToProgram(buf, cps);
13735         }
13736         return TRUE;
13737 }
13738
13739 void
13740 FeatureDone(cps, val)
13741      ChessProgramState* cps;
13742      int val;
13743 {
13744   DelayedEventCallback cb = GetDelayedEvent();
13745   if ((cb == InitBackEnd3 && cps == &first) ||
13746       (cb == TwoMachinesEventIfReady && cps == &second)) {
13747     CancelDelayedEvent();
13748     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13749   }
13750   cps->initDone = val;
13751 }
13752
13753 /* Parse feature command from engine */
13754 void
13755 ParseFeatures(args, cps)
13756      char* args;
13757      ChessProgramState *cps;  
13758 {
13759   char *p = args;
13760   char *q;
13761   int val;
13762   char buf[MSG_SIZ];
13763
13764   for (;;) {
13765     while (*p == ' ') p++;
13766     if (*p == NULLCHAR) return;
13767
13768     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13769     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13770     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13771     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13772     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13773     if (BoolFeature(&p, "reuse", &val, cps)) {
13774       /* Engine can disable reuse, but can't enable it if user said no */
13775       if (!val) cps->reuse = FALSE;
13776       continue;
13777     }
13778     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13779     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13780       if (gameMode == TwoMachinesPlay) {
13781         DisplayTwoMachinesTitle();
13782       } else {
13783         DisplayTitle("");
13784       }
13785       continue;
13786     }
13787     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13788     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13789     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13790     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13791     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13792     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13793     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13794     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13795     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13796     if (IntFeature(&p, "done", &val, cps)) {
13797       FeatureDone(cps, val);
13798       continue;
13799     }
13800     /* Added by Tord: */
13801     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13802     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13803     /* End of additions by Tord */
13804
13805     /* [HGM] added features: */
13806     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13807     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13808     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13809     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13810     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13811     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13812     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13813         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13814             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13815             SendToProgram(buf, cps);
13816             continue;
13817         }
13818         if(cps->nrOptions >= MAX_OPTIONS) {
13819             cps->nrOptions--;
13820             sprintf(buf, "%s engine has too many options\n", cps->which);
13821             DisplayError(buf, 0);
13822         }
13823         continue;
13824     }
13825     /* End of additions by HGM */
13826
13827     /* unknown feature: complain and skip */
13828     q = p;
13829     while (*q && *q != '=') q++;
13830     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13831     SendToProgram(buf, cps);
13832     p = q;
13833     if (*p == '=') {
13834       p++;
13835       if (*p == '\"') {
13836         p++;
13837         while (*p && *p != '\"') p++;
13838         if (*p == '\"') p++;
13839       } else {
13840         while (*p && *p != ' ') p++;
13841       }
13842     }
13843   }
13844
13845 }
13846
13847 void
13848 PeriodicUpdatesEvent(newState)
13849      int newState;
13850 {
13851     if (newState == appData.periodicUpdates)
13852       return;
13853
13854     appData.periodicUpdates=newState;
13855
13856     /* Display type changes, so update it now */
13857 //    DisplayAnalysis();
13858
13859     /* Get the ball rolling again... */
13860     if (newState) {
13861         AnalysisPeriodicEvent(1);
13862         StartAnalysisClock();
13863     }
13864 }
13865
13866 void
13867 PonderNextMoveEvent(newState)
13868      int newState;
13869 {
13870     if (newState == appData.ponderNextMove) return;
13871     if (gameMode == EditPosition) EditPositionDone(TRUE);
13872     if (newState) {
13873         SendToProgram("hard\n", &first);
13874         if (gameMode == TwoMachinesPlay) {
13875             SendToProgram("hard\n", &second);
13876         }
13877     } else {
13878         SendToProgram("easy\n", &first);
13879         thinkOutput[0] = NULLCHAR;
13880         if (gameMode == TwoMachinesPlay) {
13881             SendToProgram("easy\n", &second);
13882         }
13883     }
13884     appData.ponderNextMove = newState;
13885 }
13886
13887 void
13888 NewSettingEvent(option, feature, command, value)
13889      char *command;
13890      int option, value, *feature;
13891 {
13892     char buf[MSG_SIZ];
13893
13894     if (gameMode == EditPosition) EditPositionDone(TRUE);
13895     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13896     if(feature == NULL || *feature) SendToProgram(buf, &first);
13897     if (gameMode == TwoMachinesPlay) {
13898         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13899     }
13900 }
13901
13902 void
13903 ShowThinkingEvent()
13904 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13905 {
13906     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13907     int newState = appData.showThinking
13908         // [HGM] thinking: other features now need thinking output as well
13909         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13910     
13911     if (oldState == newState) return;
13912     oldState = newState;
13913     if (gameMode == EditPosition) EditPositionDone(TRUE);
13914     if (oldState) {
13915         SendToProgram("post\n", &first);
13916         if (gameMode == TwoMachinesPlay) {
13917             SendToProgram("post\n", &second);
13918         }
13919     } else {
13920         SendToProgram("nopost\n", &first);
13921         thinkOutput[0] = NULLCHAR;
13922         if (gameMode == TwoMachinesPlay) {
13923             SendToProgram("nopost\n", &second);
13924         }
13925     }
13926 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13927 }
13928
13929 void
13930 AskQuestionEvent(title, question, replyPrefix, which)
13931      char *title; char *question; char *replyPrefix; char *which;
13932 {
13933   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13934   if (pr == NoProc) return;
13935   AskQuestion(title, question, replyPrefix, pr);
13936 }
13937
13938 void
13939 DisplayMove(moveNumber)
13940      int moveNumber;
13941 {
13942     char message[MSG_SIZ];
13943     char res[MSG_SIZ];
13944     char cpThinkOutput[MSG_SIZ];
13945
13946     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13947     
13948     if (moveNumber == forwardMostMove - 1 || 
13949         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13950
13951         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13952
13953         if (strchr(cpThinkOutput, '\n')) {
13954             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13955         }
13956     } else {
13957         *cpThinkOutput = NULLCHAR;
13958     }
13959
13960     /* [AS] Hide thinking from human user */
13961     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13962         *cpThinkOutput = NULLCHAR;
13963         if( thinkOutput[0] != NULLCHAR ) {
13964             int i;
13965
13966             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13967                 cpThinkOutput[i] = '.';
13968             }
13969             cpThinkOutput[i] = NULLCHAR;
13970             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13971         }
13972     }
13973
13974     if (moveNumber == forwardMostMove - 1 &&
13975         gameInfo.resultDetails != NULL) {
13976         if (gameInfo.resultDetails[0] == NULLCHAR) {
13977             sprintf(res, " %s", PGNResult(gameInfo.result));
13978         } else {
13979             sprintf(res, " {%s} %s",
13980                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13981         }
13982     } else {
13983         res[0] = NULLCHAR;
13984     }
13985
13986     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13987         DisplayMessage(res, cpThinkOutput);
13988     } else {
13989         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13990                 WhiteOnMove(moveNumber) ? " " : ".. ",
13991                 parseList[moveNumber], res);
13992         DisplayMessage(message, cpThinkOutput);
13993     }
13994 }
13995
13996 void
13997 DisplayComment(moveNumber, text)
13998      int moveNumber;
13999      char *text;
14000 {
14001     char title[MSG_SIZ];
14002     char buf[8000]; // comment can be long!
14003     int score, depth;
14004     
14005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14006       strcpy(title, "Comment");
14007     } else {
14008       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14009               WhiteOnMove(moveNumber) ? " " : ".. ",
14010               parseList[moveNumber]);
14011     }
14012     // [HGM] PV info: display PV info together with (or as) comment
14013     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14014       if(text == NULL) text = "";                                           
14015       score = pvInfoList[moveNumber].score;
14016       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14017               depth, (pvInfoList[moveNumber].time+50)/100, text);
14018       text = buf;
14019     }
14020     if (text != NULL && (appData.autoDisplayComment || commentUp))
14021         CommentPopUp(title, text);
14022 }
14023
14024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14025  * might be busy thinking or pondering.  It can be omitted if your
14026  * gnuchess is configured to stop thinking immediately on any user
14027  * input.  However, that gnuchess feature depends on the FIONREAD
14028  * ioctl, which does not work properly on some flavors of Unix.
14029  */
14030 void
14031 Attention(cps)
14032      ChessProgramState *cps;
14033 {
14034 #if ATTENTION
14035     if (!cps->useSigint) return;
14036     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14037     switch (gameMode) {
14038       case MachinePlaysWhite:
14039       case MachinePlaysBlack:
14040       case TwoMachinesPlay:
14041       case IcsPlayingWhite:
14042       case IcsPlayingBlack:
14043       case AnalyzeMode:
14044       case AnalyzeFile:
14045         /* Skip if we know it isn't thinking */
14046         if (!cps->maybeThinking) return;
14047         if (appData.debugMode)
14048           fprintf(debugFP, "Interrupting %s\n", cps->which);
14049         InterruptChildProcess(cps->pr);
14050         cps->maybeThinking = FALSE;
14051         break;
14052       default:
14053         break;
14054     }
14055 #endif /*ATTENTION*/
14056 }
14057
14058 int
14059 CheckFlags()
14060 {
14061     if (whiteTimeRemaining <= 0) {
14062         if (!whiteFlag) {
14063             whiteFlag = TRUE;
14064             if (appData.icsActive) {
14065                 if (appData.autoCallFlag &&
14066                     gameMode == IcsPlayingBlack && !blackFlag) {
14067                   SendToICS(ics_prefix);
14068                   SendToICS("flag\n");
14069                 }
14070             } else {
14071                 if (blackFlag) {
14072                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14073                 } else {
14074                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14075                     if (appData.autoCallFlag) {
14076                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14077                         return TRUE;
14078                     }
14079                 }
14080             }
14081         }
14082     }
14083     if (blackTimeRemaining <= 0) {
14084         if (!blackFlag) {
14085             blackFlag = TRUE;
14086             if (appData.icsActive) {
14087                 if (appData.autoCallFlag &&
14088                     gameMode == IcsPlayingWhite && !whiteFlag) {
14089                   SendToICS(ics_prefix);
14090                   SendToICS("flag\n");
14091                 }
14092             } else {
14093                 if (whiteFlag) {
14094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14095                 } else {
14096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14097                     if (appData.autoCallFlag) {
14098                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14099                         return TRUE;
14100                     }
14101                 }
14102             }
14103         }
14104     }
14105     return FALSE;
14106 }
14107
14108 void
14109 CheckTimeControl()
14110 {
14111     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14112         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14113
14114     /*
14115      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14116      */
14117     if ( !WhiteOnMove(forwardMostMove) )
14118         /* White made time control */
14119         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14120         /* [HGM] time odds: correct new time quota for time odds! */
14121                                             / WhitePlayer()->timeOdds;
14122       else
14123         /* Black made time control */
14124         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14125                                             / WhitePlayer()->other->timeOdds;
14126 }
14127
14128 void
14129 DisplayBothClocks()
14130 {
14131     int wom = gameMode == EditPosition ?
14132       !blackPlaysFirst : WhiteOnMove(currentMove);
14133     DisplayWhiteClock(whiteTimeRemaining, wom);
14134     DisplayBlackClock(blackTimeRemaining, !wom);
14135 }
14136
14137
14138 /* Timekeeping seems to be a portability nightmare.  I think everyone
14139    has ftime(), but I'm really not sure, so I'm including some ifdefs
14140    to use other calls if you don't.  Clocks will be less accurate if
14141    you have neither ftime nor gettimeofday.
14142 */
14143
14144 /* VS 2008 requires the #include outside of the function */
14145 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14146 #include <sys/timeb.h>
14147 #endif
14148
14149 /* Get the current time as a TimeMark */
14150 void
14151 GetTimeMark(tm)
14152      TimeMark *tm;
14153 {
14154 #if HAVE_GETTIMEOFDAY
14155
14156     struct timeval timeVal;
14157     struct timezone timeZone;
14158
14159     gettimeofday(&timeVal, &timeZone);
14160     tm->sec = (long) timeVal.tv_sec; 
14161     tm->ms = (int) (timeVal.tv_usec / 1000L);
14162
14163 #else /*!HAVE_GETTIMEOFDAY*/
14164 #if HAVE_FTIME
14165
14166 // include <sys/timeb.h> / moved to just above start of function
14167     struct timeb timeB;
14168
14169     ftime(&timeB);
14170     tm->sec = (long) timeB.time;
14171     tm->ms = (int) timeB.millitm;
14172
14173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14174     tm->sec = (long) time(NULL);
14175     tm->ms = 0;
14176 #endif
14177 #endif
14178 }
14179
14180 /* Return the difference in milliseconds between two
14181    time marks.  We assume the difference will fit in a long!
14182 */
14183 long
14184 SubtractTimeMarks(tm2, tm1)
14185      TimeMark *tm2, *tm1;
14186 {
14187     return 1000L*(tm2->sec - tm1->sec) +
14188            (long) (tm2->ms - tm1->ms);
14189 }
14190
14191
14192 /*
14193  * Code to manage the game clocks.
14194  *
14195  * In tournament play, black starts the clock and then white makes a move.
14196  * We give the human user a slight advantage if he is playing white---the
14197  * clocks don't run until he makes his first move, so it takes zero time.
14198  * Also, we don't account for network lag, so we could get out of sync
14199  * with GNU Chess's clock -- but then, referees are always right.  
14200  */
14201
14202 static TimeMark tickStartTM;
14203 static long intendedTickLength;
14204
14205 long
14206 NextTickLength(timeRemaining)
14207      long timeRemaining;
14208 {
14209     long nominalTickLength, nextTickLength;
14210
14211     if (timeRemaining > 0L && timeRemaining <= 10000L)
14212       nominalTickLength = 100L;
14213     else
14214       nominalTickLength = 1000L;
14215     nextTickLength = timeRemaining % nominalTickLength;
14216     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14217
14218     return nextTickLength;
14219 }
14220
14221 /* Adjust clock one minute up or down */
14222 void
14223 AdjustClock(Boolean which, int dir)
14224 {
14225     if(which) blackTimeRemaining += 60000*dir;
14226     else      whiteTimeRemaining += 60000*dir;
14227     DisplayBothClocks();
14228 }
14229
14230 /* Stop clocks and reset to a fresh time control */
14231 void
14232 ResetClocks() 
14233 {
14234     (void) StopClockTimer();
14235     if (appData.icsActive) {
14236         whiteTimeRemaining = blackTimeRemaining = 0;
14237     } else if (searchTime) {
14238         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14239         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14240     } else { /* [HGM] correct new time quote for time odds */
14241         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14242         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14243     }
14244     if (whiteFlag || blackFlag) {
14245         DisplayTitle("");
14246         whiteFlag = blackFlag = FALSE;
14247     }
14248     DisplayBothClocks();
14249 }
14250
14251 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14252
14253 /* Decrement running clock by amount of time that has passed */
14254 void
14255 DecrementClocks()
14256 {
14257     long timeRemaining;
14258     long lastTickLength, fudge;
14259     TimeMark now;
14260
14261     if (!appData.clockMode) return;
14262     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14263         
14264     GetTimeMark(&now);
14265
14266     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14267
14268     /* Fudge if we woke up a little too soon */
14269     fudge = intendedTickLength - lastTickLength;
14270     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14271
14272     if (WhiteOnMove(forwardMostMove)) {
14273         if(whiteNPS >= 0) lastTickLength = 0;
14274         timeRemaining = whiteTimeRemaining -= lastTickLength;
14275         DisplayWhiteClock(whiteTimeRemaining - fudge,
14276                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14277     } else {
14278         if(blackNPS >= 0) lastTickLength = 0;
14279         timeRemaining = blackTimeRemaining -= lastTickLength;
14280         DisplayBlackClock(blackTimeRemaining - fudge,
14281                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14282     }
14283
14284     if (CheckFlags()) return;
14285         
14286     tickStartTM = now;
14287     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14288     StartClockTimer(intendedTickLength);
14289
14290     /* if the time remaining has fallen below the alarm threshold, sound the
14291      * alarm. if the alarm has sounded and (due to a takeback or time control
14292      * with increment) the time remaining has increased to a level above the
14293      * threshold, reset the alarm so it can sound again. 
14294      */
14295     
14296     if (appData.icsActive && appData.icsAlarm) {
14297
14298         /* make sure we are dealing with the user's clock */
14299         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14300                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14301            )) return;
14302
14303         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14304             alarmSounded = FALSE;
14305         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14306             PlayAlarmSound();
14307             alarmSounded = TRUE;
14308         }
14309     }
14310 }
14311
14312
14313 /* A player has just moved, so stop the previously running
14314    clock and (if in clock mode) start the other one.
14315    We redisplay both clocks in case we're in ICS mode, because
14316    ICS gives us an update to both clocks after every move.
14317    Note that this routine is called *after* forwardMostMove
14318    is updated, so the last fractional tick must be subtracted
14319    from the color that is *not* on move now.
14320 */
14321 void
14322 SwitchClocks(int newMoveNr)
14323 {
14324     long lastTickLength;
14325     TimeMark now;
14326     int flagged = FALSE;
14327
14328     GetTimeMark(&now);
14329
14330     if (StopClockTimer() && appData.clockMode) {
14331         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14332         if (!WhiteOnMove(forwardMostMove)) {
14333             if(blackNPS >= 0) lastTickLength = 0;
14334             blackTimeRemaining -= lastTickLength;
14335            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14336 //         if(pvInfoList[forwardMostMove-1].time == -1)
14337                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14338                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14339         } else {
14340            if(whiteNPS >= 0) lastTickLength = 0;
14341            whiteTimeRemaining -= lastTickLength;
14342            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14343 //         if(pvInfoList[forwardMostMove-1].time == -1)
14344                  pvInfoList[forwardMostMove-1].time = 
14345                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14346         }
14347         flagged = CheckFlags();
14348     }
14349     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14350     CheckTimeControl();
14351
14352     if (flagged || !appData.clockMode) return;
14353
14354     switch (gameMode) {
14355       case MachinePlaysBlack:
14356       case MachinePlaysWhite:
14357       case BeginningOfGame:
14358         if (pausing) return;
14359         break;
14360
14361       case EditGame:
14362       case PlayFromGameFile:
14363       case IcsExamining:
14364         return;
14365
14366       default:
14367         break;
14368     }
14369
14370     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14371         if(WhiteOnMove(forwardMostMove))
14372              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14373         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14374     }
14375
14376     tickStartTM = now;
14377     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14378       whiteTimeRemaining : blackTimeRemaining);
14379     StartClockTimer(intendedTickLength);
14380 }
14381         
14382
14383 /* Stop both clocks */
14384 void
14385 StopClocks()
14386 {       
14387     long lastTickLength;
14388     TimeMark now;
14389
14390     if (!StopClockTimer()) return;
14391     if (!appData.clockMode) return;
14392
14393     GetTimeMark(&now);
14394
14395     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14396     if (WhiteOnMove(forwardMostMove)) {
14397         if(whiteNPS >= 0) lastTickLength = 0;
14398         whiteTimeRemaining -= lastTickLength;
14399         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14400     } else {
14401         if(blackNPS >= 0) lastTickLength = 0;
14402         blackTimeRemaining -= lastTickLength;
14403         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14404     }
14405     CheckFlags();
14406 }
14407         
14408 /* Start clock of player on move.  Time may have been reset, so
14409    if clock is already running, stop and restart it. */
14410 void
14411 StartClocks()
14412 {
14413     (void) StopClockTimer(); /* in case it was running already */
14414     DisplayBothClocks();
14415     if (CheckFlags()) return;
14416
14417     if (!appData.clockMode) return;
14418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14419
14420     GetTimeMark(&tickStartTM);
14421     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14422       whiteTimeRemaining : blackTimeRemaining);
14423
14424    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14425     whiteNPS = blackNPS = -1; 
14426     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14427        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14428         whiteNPS = first.nps;
14429     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14430        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14431         blackNPS = first.nps;
14432     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14433         whiteNPS = second.nps;
14434     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14435         blackNPS = second.nps;
14436     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14437
14438     StartClockTimer(intendedTickLength);
14439 }
14440
14441 char *
14442 TimeString(ms)
14443      long ms;
14444 {
14445     long second, minute, hour, day;
14446     char *sign = "";
14447     static char buf[32];
14448     
14449     if (ms > 0 && ms <= 9900) {
14450       /* convert milliseconds to tenths, rounding up */
14451       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14452
14453       sprintf(buf, " %03.1f ", tenths/10.0);
14454       return buf;
14455     }
14456
14457     /* convert milliseconds to seconds, rounding up */
14458     /* use floating point to avoid strangeness of integer division
14459        with negative dividends on many machines */
14460     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14461
14462     if (second < 0) {
14463         sign = "-";
14464         second = -second;
14465     }
14466     
14467     day = second / (60 * 60 * 24);
14468     second = second % (60 * 60 * 24);
14469     hour = second / (60 * 60);
14470     second = second % (60 * 60);
14471     minute = second / 60;
14472     second = second % 60;
14473     
14474     if (day > 0)
14475       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14476               sign, day, hour, minute, second);
14477     else if (hour > 0)
14478       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14479     else
14480       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14481     
14482     return buf;
14483 }
14484
14485
14486 /*
14487  * This is necessary because some C libraries aren't ANSI C compliant yet.
14488  */
14489 char *
14490 StrStr(string, match)
14491      char *string, *match;
14492 {
14493     int i, length;
14494     
14495     length = strlen(match);
14496     
14497     for (i = strlen(string) - length; i >= 0; i--, string++)
14498       if (!strncmp(match, string, length))
14499         return string;
14500     
14501     return NULL;
14502 }
14503
14504 char *
14505 StrCaseStr(string, match)
14506      char *string, *match;
14507 {
14508     int i, j, length;
14509     
14510     length = strlen(match);
14511     
14512     for (i = strlen(string) - length; i >= 0; i--, string++) {
14513         for (j = 0; j < length; j++) {
14514             if (ToLower(match[j]) != ToLower(string[j]))
14515               break;
14516         }
14517         if (j == length) return string;
14518     }
14519
14520     return NULL;
14521 }
14522
14523 #ifndef _amigados
14524 int
14525 StrCaseCmp(s1, s2)
14526      char *s1, *s2;
14527 {
14528     char c1, c2;
14529     
14530     for (;;) {
14531         c1 = ToLower(*s1++);
14532         c2 = ToLower(*s2++);
14533         if (c1 > c2) return 1;
14534         if (c1 < c2) return -1;
14535         if (c1 == NULLCHAR) return 0;
14536     }
14537 }
14538
14539
14540 int
14541 ToLower(c)
14542      int c;
14543 {
14544     return isupper(c) ? tolower(c) : c;
14545 }
14546
14547
14548 int
14549 ToUpper(c)
14550      int c;
14551 {
14552     return islower(c) ? toupper(c) : c;
14553 }
14554 #endif /* !_amigados    */
14555
14556 char *
14557 StrSave(s)
14558      char *s;
14559 {
14560     char *ret;
14561
14562     if ((ret = (char *) malloc(strlen(s) + 1))) {
14563         strcpy(ret, s);
14564     }
14565     return ret;
14566 }
14567
14568 char *
14569 StrSavePtr(s, savePtr)
14570      char *s, **savePtr;
14571 {
14572     if (*savePtr) {
14573         free(*savePtr);
14574     }
14575     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14576         strcpy(*savePtr, s);
14577     }
14578     return(*savePtr);
14579 }
14580
14581 char *
14582 PGNDate()
14583 {
14584     time_t clock;
14585     struct tm *tm;
14586     char buf[MSG_SIZ];
14587
14588     clock = time((time_t *)NULL);
14589     tm = localtime(&clock);
14590     sprintf(buf, "%04d.%02d.%02d",
14591             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14592     return StrSave(buf);
14593 }
14594
14595
14596 char *
14597 PositionToFEN(move, overrideCastling)
14598      int move;
14599      char *overrideCastling;
14600 {
14601     int i, j, fromX, fromY, toX, toY;
14602     int whiteToPlay;
14603     char buf[128];
14604     char *p, *q;
14605     int emptycount;
14606     ChessSquare piece;
14607
14608     whiteToPlay = (gameMode == EditPosition) ?
14609       !blackPlaysFirst : (move % 2 == 0);
14610     p = buf;
14611
14612     /* Piece placement data */
14613     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14614         emptycount = 0;
14615         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14616             if (boards[move][i][j] == EmptySquare) {
14617                 emptycount++;
14618             } else { ChessSquare piece = boards[move][i][j];
14619                 if (emptycount > 0) {
14620                     if(emptycount<10) /* [HGM] can be >= 10 */
14621                         *p++ = '0' + emptycount;
14622                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14623                     emptycount = 0;
14624                 }
14625                 if(PieceToChar(piece) == '+') {
14626                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14627                     *p++ = '+';
14628                     piece = (ChessSquare)(DEMOTED piece);
14629                 } 
14630                 *p++ = PieceToChar(piece);
14631                 if(p[-1] == '~') {
14632                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14633                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14634                     *p++ = '~';
14635                 }
14636             }
14637         }
14638         if (emptycount > 0) {
14639             if(emptycount<10) /* [HGM] can be >= 10 */
14640                 *p++ = '0' + emptycount;
14641             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14642             emptycount = 0;
14643         }
14644         *p++ = '/';
14645     }
14646     *(p - 1) = ' ';
14647
14648     /* [HGM] print Crazyhouse or Shogi holdings */
14649     if( gameInfo.holdingsWidth ) {
14650         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14651         q = p;
14652         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14653             piece = boards[move][i][BOARD_WIDTH-1];
14654             if( piece != EmptySquare )
14655               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14656                   *p++ = PieceToChar(piece);
14657         }
14658         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14659             piece = boards[move][BOARD_HEIGHT-i-1][0];
14660             if( piece != EmptySquare )
14661               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14662                   *p++ = PieceToChar(piece);
14663         }
14664
14665         if( q == p ) *p++ = '-';
14666         *p++ = ']';
14667         *p++ = ' ';
14668     }
14669
14670     /* Active color */
14671     *p++ = whiteToPlay ? 'w' : 'b';
14672     *p++ = ' ';
14673
14674   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14675     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14676   } else {
14677   if(nrCastlingRights) {
14678      q = p;
14679      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14680        /* [HGM] write directly from rights */
14681            if(boards[move][CASTLING][2] != NoRights &&
14682               boards[move][CASTLING][0] != NoRights   )
14683                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14684            if(boards[move][CASTLING][2] != NoRights &&
14685               boards[move][CASTLING][1] != NoRights   )
14686                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14687            if(boards[move][CASTLING][5] != NoRights &&
14688               boards[move][CASTLING][3] != NoRights   )
14689                 *p++ = boards[move][CASTLING][3] + AAA;
14690            if(boards[move][CASTLING][5] != NoRights &&
14691               boards[move][CASTLING][4] != NoRights   )
14692                 *p++ = boards[move][CASTLING][4] + AAA;
14693      } else {
14694
14695         /* [HGM] write true castling rights */
14696         if( nrCastlingRights == 6 ) {
14697             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14698                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14699             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14700                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14701             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14702                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14703             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14704                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14705         }
14706      }
14707      if (q == p) *p++ = '-'; /* No castling rights */
14708      *p++ = ' ';
14709   }
14710
14711   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14712      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14713     /* En passant target square */
14714     if (move > backwardMostMove) {
14715         fromX = moveList[move - 1][0] - AAA;
14716         fromY = moveList[move - 1][1] - ONE;
14717         toX = moveList[move - 1][2] - AAA;
14718         toY = moveList[move - 1][3] - ONE;
14719         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14720             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14721             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14722             fromX == toX) {
14723             /* 2-square pawn move just happened */
14724             *p++ = toX + AAA;
14725             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14726         } else {
14727             *p++ = '-';
14728         }
14729     } else if(move == backwardMostMove) {
14730         // [HGM] perhaps we should always do it like this, and forget the above?
14731         if((signed char)boards[move][EP_STATUS] >= 0) {
14732             *p++ = boards[move][EP_STATUS] + AAA;
14733             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14734         } else {
14735             *p++ = '-';
14736         }
14737     } else {
14738         *p++ = '-';
14739     }
14740     *p++ = ' ';
14741   }
14742   }
14743
14744     /* [HGM] find reversible plies */
14745     {   int i = 0, j=move;
14746
14747         if (appData.debugMode) { int k;
14748             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14749             for(k=backwardMostMove; k<=forwardMostMove; k++)
14750                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14751
14752         }
14753
14754         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14755         if( j == backwardMostMove ) i += initialRulePlies;
14756         sprintf(p, "%d ", i);
14757         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14758     }
14759     /* Fullmove number */
14760     sprintf(p, "%d", (move / 2) + 1);
14761     
14762     return StrSave(buf);
14763 }
14764
14765 Boolean
14766 ParseFEN(board, blackPlaysFirst, fen)
14767     Board board;
14768      int *blackPlaysFirst;
14769      char *fen;
14770 {
14771     int i, j;
14772     char *p, c;
14773     int emptycount;
14774     ChessSquare piece;
14775
14776     p = fen;
14777
14778     /* [HGM] by default clear Crazyhouse holdings, if present */
14779     if(gameInfo.holdingsWidth) {
14780        for(i=0; i<BOARD_HEIGHT; i++) {
14781            board[i][0]             = EmptySquare; /* black holdings */
14782            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14783            board[i][1]             = (ChessSquare) 0; /* black counts */
14784            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14785        }
14786     }
14787
14788     /* Piece placement data */
14789     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14790         j = 0;
14791         for (;;) {
14792             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14793                 if (*p == '/') p++;
14794                 emptycount = gameInfo.boardWidth - j;
14795                 while (emptycount--)
14796                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14797                 break;
14798 #if(BOARD_FILES >= 10)
14799             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14800                 p++; emptycount=10;
14801                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14802                 while (emptycount--)
14803                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14804 #endif
14805             } else if (isdigit(*p)) {
14806                 emptycount = *p++ - '0';
14807                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14808                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14809                 while (emptycount--)
14810                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14811             } else if (*p == '+' || isalpha(*p)) {
14812                 if (j >= gameInfo.boardWidth) return FALSE;
14813                 if(*p=='+') {
14814                     piece = CharToPiece(*++p);
14815                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14816                     piece = (ChessSquare) (PROMOTED piece ); p++;
14817                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14818                 } else piece = CharToPiece(*p++);
14819
14820                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14821                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14822                     piece = (ChessSquare) (PROMOTED piece);
14823                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14824                     p++;
14825                 }
14826                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14827             } else {
14828                 return FALSE;
14829             }
14830         }
14831     }
14832     while (*p == '/' || *p == ' ') p++;
14833
14834     /* [HGM] look for Crazyhouse holdings here */
14835     while(*p==' ') p++;
14836     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14837         if(*p == '[') p++;
14838         if(*p == '-' ) *p++; /* empty holdings */ else {
14839             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14840             /* if we would allow FEN reading to set board size, we would   */
14841             /* have to add holdings and shift the board read so far here   */
14842             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14843                 *p++;
14844                 if((int) piece >= (int) BlackPawn ) {
14845                     i = (int)piece - (int)BlackPawn;
14846                     i = PieceToNumber((ChessSquare)i);
14847                     if( i >= gameInfo.holdingsSize ) return FALSE;
14848                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14849                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14850                 } else {
14851                     i = (int)piece - (int)WhitePawn;
14852                     i = PieceToNumber((ChessSquare)i);
14853                     if( i >= gameInfo.holdingsSize ) return FALSE;
14854                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14855                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14856                 }
14857             }
14858         }
14859         if(*p == ']') *p++;
14860     }
14861
14862     while(*p == ' ') p++;
14863
14864     /* Active color */
14865     c = *p++;
14866     if(appData.colorNickNames) {
14867       if( c == appData.colorNickNames[0] ) c = 'w'; else
14868       if( c == appData.colorNickNames[1] ) c = 'b';
14869     }
14870     switch (c) {
14871       case 'w':
14872         *blackPlaysFirst = FALSE;
14873         break;
14874       case 'b': 
14875         *blackPlaysFirst = TRUE;
14876         break;
14877       default:
14878         return FALSE;
14879     }
14880
14881     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14882     /* return the extra info in global variiables             */
14883
14884     /* set defaults in case FEN is incomplete */
14885     board[EP_STATUS] = EP_UNKNOWN;
14886     for(i=0; i<nrCastlingRights; i++ ) {
14887         board[CASTLING][i] =
14888             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14889     }   /* assume possible unless obviously impossible */
14890     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14891     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14892     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14893                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14894     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14895     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14896     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14897                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14898     FENrulePlies = 0;
14899
14900     while(*p==' ') p++;
14901     if(nrCastlingRights) {
14902       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14903           /* castling indicator present, so default becomes no castlings */
14904           for(i=0; i<nrCastlingRights; i++ ) {
14905                  board[CASTLING][i] = NoRights;
14906           }
14907       }
14908       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14909              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14910              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14911              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14912         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14913
14914         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14915             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14916             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14917         }
14918         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14919             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14920         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14921                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14922         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14923                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14924         switch(c) {
14925           case'K':
14926               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14927               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14928               board[CASTLING][2] = whiteKingFile;
14929               break;
14930           case'Q':
14931               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14932               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14933               board[CASTLING][2] = whiteKingFile;
14934               break;
14935           case'k':
14936               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14937               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14938               board[CASTLING][5] = blackKingFile;
14939               break;
14940           case'q':
14941               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14942               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14943               board[CASTLING][5] = blackKingFile;
14944           case '-':
14945               break;
14946           default: /* FRC castlings */
14947               if(c >= 'a') { /* black rights */
14948                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14949                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14950                   if(i == BOARD_RGHT) break;
14951                   board[CASTLING][5] = i;
14952                   c -= AAA;
14953                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14954                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14955                   if(c > i)
14956                       board[CASTLING][3] = c;
14957                   else
14958                       board[CASTLING][4] = c;
14959               } else { /* white rights */
14960                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14961                     if(board[0][i] == WhiteKing) break;
14962                   if(i == BOARD_RGHT) break;
14963                   board[CASTLING][2] = i;
14964                   c -= AAA - 'a' + 'A';
14965                   if(board[0][c] >= WhiteKing) break;
14966                   if(c > i)
14967                       board[CASTLING][0] = c;
14968                   else
14969                       board[CASTLING][1] = c;
14970               }
14971         }
14972       }
14973       for(i=0; i<nrCastlingRights; i++)
14974         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14975     if (appData.debugMode) {
14976         fprintf(debugFP, "FEN castling rights:");
14977         for(i=0; i<nrCastlingRights; i++)
14978         fprintf(debugFP, " %d", board[CASTLING][i]);
14979         fprintf(debugFP, "\n");
14980     }
14981
14982       while(*p==' ') p++;
14983     }
14984
14985     /* read e.p. field in games that know e.p. capture */
14986     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14987        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14988       if(*p=='-') {
14989         p++; board[EP_STATUS] = EP_NONE;
14990       } else {
14991          char c = *p++ - AAA;
14992
14993          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14994          if(*p >= '0' && *p <='9') *p++;
14995          board[EP_STATUS] = c;
14996       }
14997     }
14998
14999
15000     if(sscanf(p, "%d", &i) == 1) {
15001         FENrulePlies = i; /* 50-move ply counter */
15002         /* (The move number is still ignored)    */
15003     }
15004
15005     return TRUE;
15006 }
15007       
15008 void
15009 EditPositionPasteFEN(char *fen)
15010 {
15011   if (fen != NULL) {
15012     Board initial_position;
15013
15014     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15015       DisplayError(_("Bad FEN position in clipboard"), 0);
15016       return ;
15017     } else {
15018       int savedBlackPlaysFirst = blackPlaysFirst;
15019       EditPositionEvent();
15020       blackPlaysFirst = savedBlackPlaysFirst;
15021       CopyBoard(boards[0], initial_position);
15022       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15023       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15024       DisplayBothClocks();
15025       DrawPosition(FALSE, boards[currentMove]);
15026     }
15027   }
15028 }
15029
15030 static char cseq[12] = "\\   ";
15031
15032 Boolean set_cont_sequence(char *new_seq)
15033 {
15034     int len;
15035     Boolean ret;
15036
15037     // handle bad attempts to set the sequence
15038         if (!new_seq)
15039                 return 0; // acceptable error - no debug
15040
15041     len = strlen(new_seq);
15042     ret = (len > 0) && (len < sizeof(cseq));
15043     if (ret)
15044         strcpy(cseq, new_seq);
15045     else if (appData.debugMode)
15046         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15047     return ret;
15048 }
15049
15050 /*
15051     reformat a source message so words don't cross the width boundary.  internal
15052     newlines are not removed.  returns the wrapped size (no null character unless
15053     included in source message).  If dest is NULL, only calculate the size required
15054     for the dest buffer.  lp argument indicats line position upon entry, and it's
15055     passed back upon exit.
15056 */
15057 int wrap(char *dest, char *src, int count, int width, int *lp)
15058 {
15059     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15060
15061     cseq_len = strlen(cseq);
15062     old_line = line = *lp;
15063     ansi = len = clen = 0;
15064
15065     for (i=0; i < count; i++)
15066     {
15067         if (src[i] == '\033')
15068             ansi = 1;
15069
15070         // if we hit the width, back up
15071         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15072         {
15073             // store i & len in case the word is too long
15074             old_i = i, old_len = len;
15075
15076             // find the end of the last word
15077             while (i && src[i] != ' ' && src[i] != '\n')
15078             {
15079                 i--;
15080                 len--;
15081             }
15082
15083             // word too long?  restore i & len before splitting it
15084             if ((old_i-i+clen) >= width)
15085             {
15086                 i = old_i;
15087                 len = old_len;
15088             }
15089
15090             // extra space?
15091             if (i && src[i-1] == ' ')
15092                 len--;
15093
15094             if (src[i] != ' ' && src[i] != '\n')
15095             {
15096                 i--;
15097                 if (len)
15098                     len--;
15099             }
15100
15101             // now append the newline and continuation sequence
15102             if (dest)
15103                 dest[len] = '\n';
15104             len++;
15105             if (dest)
15106                 strncpy(dest+len, cseq, cseq_len);
15107             len += cseq_len;
15108             line = cseq_len;
15109             clen = cseq_len;
15110             continue;
15111         }
15112
15113         if (dest)
15114             dest[len] = src[i];
15115         len++;
15116         if (!ansi)
15117             line++;
15118         if (src[i] == '\n')
15119             line = 0;
15120         if (src[i] == 'm')
15121             ansi = 0;
15122     }
15123     if (dest && appData.debugMode)
15124     {
15125         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15126             count, width, line, len, *lp);
15127         show_bytes(debugFP, src, count);
15128         fprintf(debugFP, "\ndest: ");
15129         show_bytes(debugFP, dest, len);
15130         fprintf(debugFP, "\n");
15131     }
15132     *lp = dest ? line : old_line;
15133
15134     return len;
15135 }
15136
15137 // [HGM] vari: routines for shelving variations
15138
15139 void 
15140 PushTail(int firstMove, int lastMove)
15141 {
15142         int i, j, nrMoves = lastMove - firstMove;
15143
15144         if(appData.icsActive) { // only in local mode
15145                 forwardMostMove = currentMove; // mimic old ICS behavior
15146                 return;
15147         }
15148         if(storedGames >= MAX_VARIATIONS-1) return;
15149
15150         // push current tail of game on stack
15151         savedResult[storedGames] = gameInfo.result;
15152         savedDetails[storedGames] = gameInfo.resultDetails;
15153         gameInfo.resultDetails = NULL;
15154         savedFirst[storedGames] = firstMove;
15155         savedLast [storedGames] = lastMove;
15156         savedFramePtr[storedGames] = framePtr;
15157         framePtr -= nrMoves; // reserve space for the boards
15158         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15159             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15160             for(j=0; j<MOVE_LEN; j++)
15161                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15162             for(j=0; j<2*MOVE_LEN; j++)
15163                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15164             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15165             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15166             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15167             pvInfoList[firstMove+i-1].depth = 0;
15168             commentList[framePtr+i] = commentList[firstMove+i];
15169             commentList[firstMove+i] = NULL;
15170         }
15171
15172         storedGames++;
15173         forwardMostMove = firstMove; // truncate game so we can start variation
15174         if(storedGames == 1) GreyRevert(FALSE);
15175 }
15176
15177 Boolean
15178 PopTail(Boolean annotate)
15179 {
15180         int i, j, nrMoves;
15181         char buf[8000], moveBuf[20];
15182
15183         if(appData.icsActive) return FALSE; // only in local mode
15184         if(!storedGames) return FALSE; // sanity
15185         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15186
15187         storedGames--;
15188         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15189         nrMoves = savedLast[storedGames] - currentMove;
15190         if(annotate) {
15191                 int cnt = 10;
15192                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15193                 else strcpy(buf, "(");
15194                 for(i=currentMove; i<forwardMostMove; i++) {
15195                         if(WhiteOnMove(i))
15196                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15197                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15198                         strcat(buf, moveBuf);
15199                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15200                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15201                 }
15202                 strcat(buf, ")");
15203         }
15204         for(i=1; i<=nrMoves; i++) { // copy last variation back
15205             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15206             for(j=0; j<MOVE_LEN; j++)
15207                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15208             for(j=0; j<2*MOVE_LEN; j++)
15209                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15210             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15211             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15212             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15213             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15214             commentList[currentMove+i] = commentList[framePtr+i];
15215             commentList[framePtr+i] = NULL;
15216         }
15217         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15218         framePtr = savedFramePtr[storedGames];
15219         gameInfo.result = savedResult[storedGames];
15220         if(gameInfo.resultDetails != NULL) {
15221             free(gameInfo.resultDetails);
15222       }
15223         gameInfo.resultDetails = savedDetails[storedGames];
15224         forwardMostMove = currentMove + nrMoves;
15225         if(storedGames == 0) GreyRevert(TRUE);
15226         return TRUE;
15227 }
15228
15229 void 
15230 CleanupTail()
15231 {       // remove all shelved variations
15232         int i;
15233         for(i=0; i<storedGames; i++) {
15234             if(savedDetails[i])
15235                 free(savedDetails[i]);
15236             savedDetails[i] = NULL;
15237         }
15238         for(i=framePtr; i<MAX_MOVES; i++) {
15239                 if(commentList[i]) free(commentList[i]);
15240                 commentList[i] = NULL;
15241         }
15242         framePtr = MAX_MOVES-1;
15243         storedGames = 0;
15244 }
15245
15246 void
15247 LoadVariation(int index, char *text)
15248 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15249         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15250         int level = 0, move;
15251
15252         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15253         // first find outermost bracketing variation
15254         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15255             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15256                 if(*p == '{') wait = '}'; else
15257                 if(*p == '[') wait = ']'; else
15258                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15259                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15260             }
15261             if(*p == wait) wait = NULLCHAR; // closing ]} found
15262             p++;
15263         }
15264         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15265         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15266         end[1] = NULLCHAR; // clip off comment beyond variation
15267         ToNrEvent(currentMove-1);
15268         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15269         // kludge: use ParsePV() to append variation to game
15270         move = currentMove;
15271         ParsePV(start, TRUE);
15272         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15273         ClearPremoveHighlights();
15274         CommentPopDown();
15275         ToNrEvent(currentMove+1);
15276 }
15277