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