99aceae437f82668867df245f8072c8ee16932a4
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 int partnerHighlight[2];
247 Boolean partnerBoardValid = 0;
248 char partnerStatus[MSG_SIZ];
249 Boolean partnerUp;
250 Boolean originalFlip;
251 Boolean twoBoards = 0;
252 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
253 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
254 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
255 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
256 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
257 int opponentKibitzes;
258 int lastSavedGame; /* [HGM] save: ID of game */
259 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
260 extern int chatCount;
261 int chattingPartner;
262 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
263
264 /* States for ics_getting_history */
265 #define H_FALSE 0
266 #define H_REQUESTED 1
267 #define H_GOT_REQ_HEADER 2
268 #define H_GOT_UNREQ_HEADER 3
269 #define H_GETTING_MOVES 4
270 #define H_GOT_UNWANTED_HEADER 5
271
272 /* whosays values for GameEnds */
273 #define GE_ICS 0
274 #define GE_ENGINE 1
275 #define GE_PLAYER 2
276 #define GE_FILE 3
277 #define GE_XBOARD 4
278 #define GE_ENGINE1 5
279 #define GE_ENGINE2 6
280
281 /* Maximum number of games in a cmail message */
282 #define CMAIL_MAX_GAMES 20
283
284 /* Different types of move when calling RegisterMove */
285 #define CMAIL_MOVE   0
286 #define CMAIL_RESIGN 1
287 #define CMAIL_DRAW   2
288 #define CMAIL_ACCEPT 3
289
290 /* Different types of result to remember for each game */
291 #define CMAIL_NOT_RESULT 0
292 #define CMAIL_OLD_RESULT 1
293 #define CMAIL_NEW_RESULT 2
294
295 /* Telnet protocol constants */
296 #define TN_WILL 0373
297 #define TN_WONT 0374
298 #define TN_DO   0375
299 #define TN_DONT 0376
300 #define TN_IAC  0377
301 #define TN_ECHO 0001
302 #define TN_SGA  0003
303 #define TN_PORT 23
304
305 /* [AS] */
306 static char * safeStrCpy( char * dst, const char * src, size_t count )
307 {
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     strncpy( dst, src, count );
313     dst[ count-1 ] = '\0';
314     return dst;
315 }
316
317 /* Some compiler can't cast u64 to double
318  * This function do the job for us:
319
320  * We use the highest bit for cast, this only
321  * works if the highest bit is not
322  * in use (This should not happen)
323  *
324  * We used this for all compiler
325  */
326 double
327 u64ToDouble(u64 value)
328 {
329   double r;
330   u64 tmp = value & u64Const(0x7fffffffffffffff);
331   r = (double)(s64)tmp;
332   if (value & u64Const(0x8000000000000000))
333        r +=  9.2233720368547758080e18; /* 2^63 */
334  return r;
335 }
336
337 /* Fake up flags for now, as we aren't keeping track of castling
338    availability yet. [HGM] Change of logic: the flag now only
339    indicates the type of castlings allowed by the rule of the game.
340    The actual rights themselves are maintained in the array
341    castlingRights, as part of the game history, and are not probed
342    by this function.
343  */
344 int
345 PosFlags(index)
346 {
347   int flags = F_ALL_CASTLE_OK;
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
349   switch (gameInfo.variant) {
350   case VariantSuicide:
351     flags &= ~F_ALL_CASTLE_OK;
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
353     flags |= F_IGNORE_CHECK;
354   case VariantLosers:
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
356     break;
357   case VariantAtomic:
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359     break;
360   case VariantKriegspiel:
361     flags |= F_KRIEGSPIEL_CAPTURE;
362     break;
363   case VariantCapaRandom: 
364   case VariantFischeRandom:
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
366   case VariantNoCastle:
367   case VariantShatranj:
368   case VariantCourier:
369   case VariantMakruk:
370     flags &= ~F_ALL_CASTLE_OK;
371     break;
372   default:
373     break;
374   }
375   return flags;
376 }
377
378 FILE *gameFileFP, *debugFP;
379
380 /* 
381     [AS] Note: sometimes, the sscanf() function is used to parse the input
382     into a fixed-size buffer. Because of this, we must be prepared to
383     receive strings as long as the size of the input buffer, which is currently
384     set to 4K for Windows and 8K for the rest.
385     So, we must either allocate sufficiently large buffers here, or
386     reduce the size of the input buffer in the input reading part.
387 */
388
389 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
390 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
391 char thinkOutput1[MSG_SIZ*10];
392
393 ChessProgramState first, second;
394
395 /* premove variables */
396 int premoveToX = 0;
397 int premoveToY = 0;
398 int premoveFromX = 0;
399 int premoveFromY = 0;
400 int premovePromoChar = 0;
401 int gotPremove = 0;
402 Boolean alarmSounded;
403 /* end premove variables */
404
405 char *ics_prefix = "$";
406 int ics_type = ICS_GENERIC;
407
408 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
409 int pauseExamForwardMostMove = 0;
410 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
411 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
412 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
413 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
414 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
415 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
416 int whiteFlag = FALSE, blackFlag = FALSE;
417 int userOfferedDraw = FALSE;
418 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
419 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
420 int cmailMoveType[CMAIL_MAX_GAMES];
421 long ics_clock_paused = 0;
422 ProcRef icsPR = NoProc, cmailPR = NoProc;
423 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
424 GameMode gameMode = BeginningOfGame;
425 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
426 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
427 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
428 int hiddenThinkOutputState = 0; /* [AS] */
429 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
430 int adjudicateLossPlies = 6;
431 char white_holding[64], black_holding[64];
432 TimeMark lastNodeCountTime;
433 long lastNodeCount=0;
434 int have_sent_ICS_logon = 0;
435 int movesPerSession;
436 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
437 long timeControl_2; /* [AS] Allow separate time controls */
438 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
439 long timeRemaining[2][MAX_MOVES];
440 int matchGame = 0;
441 TimeMark programStartTime;
442 char ics_handle[MSG_SIZ];
443 int have_set_title = 0;
444
445 /* animateTraining preserves the state of appData.animate
446  * when Training mode is activated. This allows the
447  * response to be animated when appData.animate == TRUE and
448  * appData.animateDragging == TRUE.
449  */
450 Boolean animateTraining;
451
452 GameInfo gameInfo;
453
454 AppData appData;
455
456 Board boards[MAX_MOVES];
457 /* [HGM] Following 7 needed for accurate legality tests: */
458 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
459 signed char  initialRights[BOARD_FILES];
460 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
461 int   initialRulePlies, FENrulePlies;
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
463 int loadFlag = 0; 
464 int shuffleOpenings;
465 int mute; // mute all sounds
466
467 // [HGM] vari: next 12 to save and restore variations
468 #define MAX_VARIATIONS 10
469 int framePtr = MAX_MOVES-1; // points to free stack entry
470 int storedGames = 0;
471 int savedFirst[MAX_VARIATIONS];
472 int savedLast[MAX_VARIATIONS];
473 int savedFramePtr[MAX_VARIATIONS];
474 char *savedDetails[MAX_VARIATIONS];
475 ChessMove savedResult[MAX_VARIATIONS];
476
477 void PushTail P((int firstMove, int lastMove));
478 Boolean PopTail P((Boolean annotate));
479 void CleanupTail P((void));
480
481 ChessSquare  FIDEArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackBishop, BlackKnight, BlackRook }
486 };
487
488 ChessSquare twoKingsArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
490         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
491     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
492         BlackKing, BlackKing, BlackKnight, BlackRook }
493 };
494
495 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
496     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
497         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
498     { BlackRook, BlackMan, BlackBishop, BlackQueen,
499         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
500 };
501
502 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
503     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
504         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
505     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
506         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
507 };
508
509 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
511         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
513         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
514 };
515
516 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
517     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
518         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackMan, BlackFerz,
520         BlackKing, BlackMan, BlackKnight, BlackRook }
521 };
522
523
524 #if (BOARD_FILES>=10)
525 ChessSquare ShogiArray[2][BOARD_FILES] = {
526     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
527         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
528     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
529         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
530 };
531
532 ChessSquare XiangqiArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
534         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
536         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare CapablancaArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
541         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
543         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
544 };
545
546 ChessSquare GreatArray[2][BOARD_FILES] = {
547     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
548         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
549     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
550         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
551 };
552
553 ChessSquare JanusArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
555         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
556     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
557         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
558 };
559
560 #ifdef GOTHIC
561 ChessSquare GothicArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
563         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
565         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
566 };
567 #else // !GOTHIC
568 #define GothicArray CapablancaArray
569 #endif // !GOTHIC
570
571 #ifdef FALCON
572 ChessSquare FalconArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
574         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
576         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
577 };
578 #else // !FALCON
579 #define FalconArray CapablancaArray
580 #endif // !FALCON
581
582 #else // !(BOARD_FILES>=10)
583 #define XiangqiPosition FIDEArray
584 #define CapablancaArray FIDEArray
585 #define GothicArray FIDEArray
586 #define GreatArray FIDEArray
587 #endif // !(BOARD_FILES>=10)
588
589 #if (BOARD_FILES>=12)
590 ChessSquare CourierArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
592         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
593     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
594         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
595 };
596 #else // !(BOARD_FILES>=12)
597 #define CourierArray CapablancaArray
598 #endif // !(BOARD_FILES>=12)
599
600
601 Board initialPosition;
602
603
604 /* Convert str to a rating. Checks for special cases of "----",
605
606    "++++", etc. Also strips ()'s */
607 int
608 string_to_rating(str)
609   char *str;
610 {
611   while(*str && !isdigit(*str)) ++str;
612   if (!*str)
613     return 0;   /* One of the special "no rating" cases */
614   else
615     return atoi(str);
616 }
617
618 void
619 ClearProgramStats()
620 {
621     /* Init programStats */
622     programStats.movelist[0] = 0;
623     programStats.depth = 0;
624     programStats.nr_moves = 0;
625     programStats.moves_left = 0;
626     programStats.nodes = 0;
627     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
628     programStats.score = 0;
629     programStats.got_only_move = 0;
630     programStats.got_fail = 0;
631     programStats.line_is_book = 0;
632 }
633
634 void
635 InitBackEnd1()
636 {
637     int matched, min, sec;
638
639     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
640
641     GetTimeMark(&programStartTime);
642     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
643
644     ClearProgramStats();
645     programStats.ok_to_send = 1;
646     programStats.seen_stat = 0;
647
648     /*
649      * Initialize game list
650      */
651     ListNew(&gameList);
652
653
654     /*
655      * Internet chess server status
656      */
657     if (appData.icsActive) {
658         appData.matchMode = FALSE;
659         appData.matchGames = 0;
660 #if ZIPPY       
661         appData.noChessProgram = !appData.zippyPlay;
662 #else
663         appData.zippyPlay = FALSE;
664         appData.zippyTalk = FALSE;
665         appData.noChessProgram = TRUE;
666 #endif
667         if (*appData.icsHelper != NULLCHAR) {
668             appData.useTelnet = TRUE;
669             appData.telnetProgram = appData.icsHelper;
670         }
671     } else {
672         appData.zippyTalk = appData.zippyPlay = FALSE;
673     }
674
675     /* [AS] Initialize pv info list [HGM] and game state */
676     {
677         int i, j;
678
679         for( i=0; i<=framePtr; i++ ) {
680             pvInfoList[i].depth = -1;
681             boards[i][EP_STATUS] = EP_NONE;
682             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
683         }
684     }
685
686     /*
687      * Parse timeControl resource
688      */
689     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
690                           appData.movesPerSession)) {
691         char buf[MSG_SIZ];
692         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
693         DisplayFatalError(buf, 0, 2);
694     }
695
696     /*
697      * Parse searchTime resource
698      */
699     if (*appData.searchTime != NULLCHAR) {
700         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
701         if (matched == 1) {
702             searchTime = min * 60;
703         } else if (matched == 2) {
704             searchTime = min * 60 + sec;
705         } else {
706             char buf[MSG_SIZ];
707             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
708             DisplayFatalError(buf, 0, 2);
709         }
710     }
711
712     /* [AS] Adjudication threshold */
713     adjudicateLossThreshold = appData.adjudicateLossThreshold;
714     
715     first.which = "first";
716     second.which = "second";
717     first.maybeThinking = second.maybeThinking = FALSE;
718     first.pr = second.pr = NoProc;
719     first.isr = second.isr = NULL;
720     first.sendTime = second.sendTime = 2;
721     first.sendDrawOffers = 1;
722     if (appData.firstPlaysBlack) {
723         first.twoMachinesColor = "black\n";
724         second.twoMachinesColor = "white\n";
725     } else {
726         first.twoMachinesColor = "white\n";
727         second.twoMachinesColor = "black\n";
728     }
729     first.program = appData.firstChessProgram;
730     second.program = appData.secondChessProgram;
731     first.host = appData.firstHost;
732     second.host = appData.secondHost;
733     first.dir = appData.firstDirectory;
734     second.dir = appData.secondDirectory;
735     first.other = &second;
736     second.other = &first;
737     first.initString = appData.initString;
738     second.initString = appData.secondInitString;
739     first.computerString = appData.firstComputerString;
740     second.computerString = appData.secondComputerString;
741     first.useSigint = second.useSigint = TRUE;
742     first.useSigterm = second.useSigterm = TRUE;
743     first.reuse = appData.reuseFirst;
744     second.reuse = appData.reuseSecond;
745     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
746     second.nps = appData.secondNPS;
747     first.useSetboard = second.useSetboard = FALSE;
748     first.useSAN = second.useSAN = FALSE;
749     first.usePing = second.usePing = FALSE;
750     first.lastPing = second.lastPing = 0;
751     first.lastPong = second.lastPong = 0;
752     first.usePlayother = second.usePlayother = FALSE;
753     first.useColors = second.useColors = TRUE;
754     first.useUsermove = second.useUsermove = FALSE;
755     first.sendICS = second.sendICS = FALSE;
756     first.sendName = second.sendName = appData.icsActive;
757     first.sdKludge = second.sdKludge = FALSE;
758     first.stKludge = second.stKludge = FALSE;
759     TidyProgramName(first.program, first.host, first.tidy);
760     TidyProgramName(second.program, second.host, second.tidy);
761     first.matchWins = second.matchWins = 0;
762     strcpy(first.variants, appData.variant);
763     strcpy(second.variants, appData.variant);
764     first.analysisSupport = second.analysisSupport = 2; /* detect */
765     first.analyzing = second.analyzing = FALSE;
766     first.initDone = second.initDone = FALSE;
767
768     /* New features added by Tord: */
769     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
770     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
771     /* End of new features added by Tord. */
772     first.fenOverride  = appData.fenOverride1;
773     second.fenOverride = appData.fenOverride2;
774
775     /* [HGM] time odds: set factor for each machine */
776     first.timeOdds  = appData.firstTimeOdds;
777     second.timeOdds = appData.secondTimeOdds;
778     { float norm = 1;
779         if(appData.timeOddsMode) {
780             norm = first.timeOdds;
781             if(norm > second.timeOdds) norm = second.timeOdds;
782         }
783         first.timeOdds /= norm;
784         second.timeOdds /= norm;
785     }
786
787     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
788     first.accumulateTC = appData.firstAccumulateTC;
789     second.accumulateTC = appData.secondAccumulateTC;
790     first.maxNrOfSessions = second.maxNrOfSessions = 1;
791
792     /* [HGM] debug */
793     first.debug = second.debug = FALSE;
794     first.supportsNPS = second.supportsNPS = UNKNOWN;
795
796     /* [HGM] options */
797     first.optionSettings  = appData.firstOptions;
798     second.optionSettings = appData.secondOptions;
799
800     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
801     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
802     first.isUCI = appData.firstIsUCI; /* [AS] */
803     second.isUCI = appData.secondIsUCI; /* [AS] */
804     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
805     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
806
807     if (appData.firstProtocolVersion > PROTOVER ||
808         appData.firstProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.firstProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       first.protocolVersion = appData.firstProtocolVersion;
815     }
816
817     if (appData.secondProtocolVersion > PROTOVER ||
818         appData.secondProtocolVersion < 1) {
819       char buf[MSG_SIZ];
820       sprintf(buf, _("protocol version %d not supported"),
821               appData.secondProtocolVersion);
822       DisplayFatalError(buf, 0, 2);
823     } else {
824       second.protocolVersion = appData.secondProtocolVersion;
825     }
826
827     if (appData.icsActive) {
828         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
829 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
830     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
831         appData.clockMode = FALSE;
832         first.sendTime = second.sendTime = 0;
833     }
834     
835 #if ZIPPY
836     /* Override some settings from environment variables, for backward
837        compatibility.  Unfortunately it's not feasible to have the env
838        vars just set defaults, at least in xboard.  Ugh.
839     */
840     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
841       ZippyInit();
842     }
843 #endif
844     
845     if (appData.noChessProgram) {
846         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
847         sprintf(programVersion, "%s", PACKAGE_STRING);
848     } else {
849       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
850       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
851       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
852     }
853
854     if (!appData.icsActive) {
855       char buf[MSG_SIZ];
856       /* Check for variants that are supported only in ICS mode,
857          or not at all.  Some that are accepted here nevertheless
858          have bugs; see comments below.
859       */
860       VariantClass variant = StringToVariant(appData.variant);
861       switch (variant) {
862       case VariantBughouse:     /* need four players and two boards */
863       case VariantKriegspiel:   /* need to hide pieces and move details */
864       /* case VariantFischeRandom: (Fabien: moved below) */
865         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
866         DisplayFatalError(buf, 0, 2);
867         return;
868
869       case VariantUnknown:
870       case VariantLoadable:
871       case Variant29:
872       case Variant30:
873       case Variant31:
874       case Variant32:
875       case Variant33:
876       case Variant34:
877       case Variant35:
878       case Variant36:
879       default:
880         sprintf(buf, _("Unknown variant name %s"), appData.variant);
881         DisplayFatalError(buf, 0, 2);
882         return;
883
884       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
885       case VariantFairy:      /* [HGM] TestLegality definitely off! */
886       case VariantGothic:     /* [HGM] should work */
887       case VariantCapablanca: /* [HGM] should work */
888       case VariantCourier:    /* [HGM] initial forced moves not implemented */
889       case VariantShogi:      /* [HGM] drops not tested for legality */
890       case VariantKnightmate: /* [HGM] should work */
891       case VariantCylinder:   /* [HGM] untested */
892       case VariantFalcon:     /* [HGM] untested */
893       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
894                                  offboard interposition not understood */
895       case VariantNormal:     /* definitely works! */
896       case VariantWildCastle: /* pieces not automatically shuffled */
897       case VariantNoCastle:   /* pieces not automatically shuffled */
898       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
899       case VariantLosers:     /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantSuicide:    /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantGiveaway:   /* should work except for win condition,
904                                  and doesn't know captures are mandatory */
905       case VariantTwoKings:   /* should work */
906       case VariantAtomic:     /* should work except for win condition */
907       case Variant3Check:     /* should work except for win condition */
908       case VariantShatranj:   /* should work except for all win conditions */
909       case VariantMakruk:     /* should work except for daw countdown */
910       case VariantBerolina:   /* might work if TestLegality is off */
911       case VariantCapaRandom: /* should work */
912       case VariantJanus:      /* should work */
913       case VariantSuper:      /* experimental */
914       case VariantGreat:      /* experimental, requires legality testing to be off */
915         break;
916       }
917     }
918
919     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
920     InitEngineUCI( installDir, &second );
921 }
922
923 int NextIntegerFromString( char ** str, long * value )
924 {
925     int result = -1;
926     char * s = *str;
927
928     while( *s == ' ' || *s == '\t' ) {
929         s++;
930     }
931
932     *value = 0;
933
934     if( *s >= '0' && *s <= '9' ) {
935         while( *s >= '0' && *s <= '9' ) {
936             *value = *value * 10 + (*s - '0');
937             s++;
938         }
939
940         result = 0;
941     }
942
943     *str = s;
944
945     return result;
946 }
947
948 int NextTimeControlFromString( char ** str, long * value )
949 {
950     long temp;
951     int result = NextIntegerFromString( str, &temp );
952
953     if( result == 0 ) {
954         *value = temp * 60; /* Minutes */
955         if( **str == ':' ) {
956             (*str)++;
957             result = NextIntegerFromString( str, &temp );
958             *value += temp; /* Seconds */
959         }
960     }
961
962     return result;
963 }
964
965 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
966 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
967     int result = -1; long temp, temp2;
968
969     if(**str != '+') return -1; // old params remain in force!
970     (*str)++;
971     if( NextTimeControlFromString( str, &temp ) ) return -1;
972
973     if(**str != '/') {
974         /* time only: incremental or sudden-death time control */
975         if(**str == '+') { /* increment follows; read it */
976             (*str)++;
977             if(result = NextIntegerFromString( str, &temp2)) return -1;
978             *inc = temp2 * 1000;
979         } else *inc = 0;
980         *moves = 0; *tc = temp * 1000; 
981         return 0;
982     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
983
984     (*str)++; /* classical time control */
985     result = NextTimeControlFromString( str, &temp2);
986     if(result == 0) {
987         *moves = temp/60;
988         *tc    = temp2 * 1000;
989         *inc   = 0;
990     }
991     return result;
992 }
993
994 int GetTimeQuota(int movenr)
995 {   /* [HGM] get time to add from the multi-session time-control string */
996     int moves=1; /* kludge to force reading of first session */
997     long time, increment;
998     char *s = fullTimeControlString;
999
1000     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1001     do {
1002         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1003         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1004         if(movenr == -1) return time;    /* last move before new session     */
1005         if(!moves) return increment;     /* current session is incremental   */
1006         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1007     } while(movenr >= -1);               /* try again for next session       */
1008
1009     return 0; // no new time quota on this move
1010 }
1011
1012 int
1013 ParseTimeControl(tc, ti, mps)
1014      char *tc;
1015      int ti;
1016      int mps;
1017 {
1018   long tc1;
1019   long tc2;
1020   char buf[MSG_SIZ];
1021   
1022   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1023   if(ti > 0) {
1024     if(mps)
1025       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1026     else sprintf(buf, "+%s+%d", tc, ti);
1027   } else {
1028     if(mps)
1029              sprintf(buf, "+%d/%s", mps, tc);
1030     else sprintf(buf, "+%s", tc);
1031   }
1032   fullTimeControlString = StrSave(buf);
1033   
1034   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1035     return FALSE;
1036   }
1037   
1038   if( *tc == '/' ) {
1039     /* Parse second time control */
1040     tc++;
1041     
1042     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043       return FALSE;
1044     }
1045     
1046     if( tc2 == 0 ) {
1047       return FALSE;
1048     }
1049     
1050     timeControl_2 = tc2 * 1000;
1051   }
1052   else {
1053     timeControl_2 = 0;
1054   }
1055   
1056   if( tc1 == 0 ) {
1057     return FALSE;
1058   }
1059   
1060   timeControl = tc1 * 1000;
1061   
1062   if (ti >= 0) {
1063     timeIncrement = ti * 1000;  /* convert to ms */
1064     movesPerSession = 0;
1065   } else {
1066     timeIncrement = 0;
1067     movesPerSession = mps;
1068   }
1069   return TRUE;
1070 }
1071
1072 void
1073 InitBackEnd2()
1074 {
1075     if (appData.debugMode) {
1076         fprintf(debugFP, "%s\n", programVersion);
1077     }
1078
1079     set_cont_sequence(appData.wrapContSeq);
1080     if (appData.matchGames > 0) {
1081         appData.matchMode = TRUE;
1082     } else if (appData.matchMode) {
1083         appData.matchGames = 1;
1084     }
1085     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1086         appData.matchGames = appData.sameColorGames;
1087     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1088         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1089         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1090     }
1091     Reset(TRUE, FALSE);
1092     if (appData.noChessProgram || first.protocolVersion == 1) {
1093       InitBackEnd3();
1094     } else {
1095       /* kludge: allow timeout for initial "feature" commands */
1096       FreezeUI();
1097       DisplayMessage("", _("Starting chess program"));
1098       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1099     }
1100 }
1101
1102 void
1103 InitBackEnd3 P((void))
1104 {
1105     GameMode initialMode;
1106     char buf[MSG_SIZ];
1107     int err;
1108
1109     InitChessProgram(&first, startedFromSetupPosition);
1110
1111     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1112         free(programVersion);
1113         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1114         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1115     }
1116
1117     if (appData.icsActive) {
1118 #ifdef WIN32
1119         /* [DM] Make a console window if needed [HGM] merged ifs */
1120         ConsoleCreate(); 
1121 #endif
1122         err = establish();
1123         if (err != 0) {
1124             if (*appData.icsCommPort != NULLCHAR) {
1125                 sprintf(buf, _("Could not open comm port %s"),  
1126                         appData.icsCommPort);
1127             } else {
1128                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1129                         appData.icsHost, appData.icsPort);
1130             }
1131             DisplayFatalError(buf, err, 1);
1132             return;
1133         }
1134         SetICSMode();
1135         telnetISR =
1136           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137         fromUserISR =
1138           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1139         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1140             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) {
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239             }
1240         }
1241         if (initialMode == AnalyzeMode) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248             return;
1249           }
1250           AnalyzeModeEvent();
1251         } else if (initialMode == AnalyzeFile) {
1252           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253           ShowThinkingEvent();
1254           AnalyzeFileEvent();
1255           AnalysisPeriodicEvent(1);
1256         } else if (initialMode == MachinePlaysWhite) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineWhiteEvent();
1268         } else if (initialMode == MachinePlaysBlack) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           MachineBlackEvent();
1280         } else if (initialMode == TwoMachinesPlay) {
1281           if (appData.noChessProgram) {
1282             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283                               0, 2);
1284             return;
1285           }
1286           if (appData.icsActive) {
1287             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1288                               0, 2);
1289             return;
1290           }
1291           TwoMachinesEvent();
1292         } else if (initialMode == EditGame) {
1293           EditGameEvent();
1294         } else if (initialMode == EditPosition) {
1295           EditPositionEvent();
1296         } else if (initialMode == Training) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299             return;
1300           }
1301           TrainingEvent();
1302         }
1303     }
1304 }
1305
1306 /*
1307  * Establish will establish a contact to a remote host.port.
1308  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309  *  used to talk to the host.
1310  * Returns 0 if okay, error code if not.
1311  */
1312 int
1313 establish()
1314 {
1315     char buf[MSG_SIZ];
1316
1317     if (*appData.icsCommPort != NULLCHAR) {
1318         /* Talk to the host through a serial comm port */
1319         return OpenCommPort(appData.icsCommPort, &icsPR);
1320
1321     } else if (*appData.gateway != NULLCHAR) {
1322         if (*appData.remoteShell == NULLCHAR) {
1323             /* Use the rcmd protocol to run telnet program on a gateway host */
1324             snprintf(buf, sizeof(buf), "%s %s %s",
1325                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1326             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327
1328         } else {
1329             /* Use the rsh program to run telnet program on a gateway host */
1330             if (*appData.remoteUser == NULLCHAR) {
1331                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332                         appData.gateway, appData.telnetProgram,
1333                         appData.icsHost, appData.icsPort);
1334             } else {
1335                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336                         appData.remoteShell, appData.gateway, 
1337                         appData.remoteUser, appData.telnetProgram,
1338                         appData.icsHost, appData.icsPort);
1339             }
1340             return StartChildProcess(buf, "", &icsPR);
1341
1342         }
1343     } else if (appData.useTelnet) {
1344         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345
1346     } else {
1347         /* TCP socket interface differs somewhat between
1348            Unix and NT; handle details in the front end.
1349            */
1350         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1351     }
1352 }
1353
1354 void EscapeExpand(char *p, char *q)
1355 {       // [HGM] initstring: routine to shape up string arguments
1356         while(*p++ = *q++) if(p[-1] == '\\')
1357             switch(*q++) {
1358                 case 'n': p[-1] = '\n'; break;
1359                 case 'r': p[-1] = '\r'; break;
1360                 case 't': p[-1] = '\t'; break;
1361                 case '\\': p[-1] = '\\'; break;
1362                 case 0: *p = 0; return;
1363                 default: p[-1] = q[-1]; break;
1364             }
1365 }
1366
1367 void
1368 show_bytes(fp, buf, count)
1369      FILE *fp;
1370      char *buf;
1371      int count;
1372 {
1373     while (count--) {
1374         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1375             fprintf(fp, "\\%03o", *buf & 0xff);
1376         } else {
1377             putc(*buf, fp);
1378         }
1379         buf++;
1380     }
1381     fflush(fp);
1382 }
1383
1384 /* Returns an errno value */
1385 int
1386 OutputMaybeTelnet(pr, message, count, outError)
1387      ProcRef pr;
1388      char *message;
1389      int count;
1390      int *outError;
1391 {
1392     char buf[8192], *p, *q, *buflim;
1393     int left, newcount, outcount;
1394
1395     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1396         *appData.gateway != NULLCHAR) {
1397         if (appData.debugMode) {
1398             fprintf(debugFP, ">ICS: ");
1399             show_bytes(debugFP, message, count);
1400             fprintf(debugFP, "\n");
1401         }
1402         return OutputToProcess(pr, message, count, outError);
1403     }
1404
1405     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1406     p = message;
1407     q = buf;
1408     left = count;
1409     newcount = 0;
1410     while (left) {
1411         if (q >= buflim) {
1412             if (appData.debugMode) {
1413                 fprintf(debugFP, ">ICS: ");
1414                 show_bytes(debugFP, buf, newcount);
1415                 fprintf(debugFP, "\n");
1416             }
1417             outcount = OutputToProcess(pr, buf, newcount, outError);
1418             if (outcount < newcount) return -1; /* to be sure */
1419             q = buf;
1420             newcount = 0;
1421         }
1422         if (*p == '\n') {
1423             *q++ = '\r';
1424             newcount++;
1425         } else if (((unsigned char) *p) == TN_IAC) {
1426             *q++ = (char) TN_IAC;
1427             newcount ++;
1428         }
1429         *q++ = *p++;
1430         newcount++;
1431         left--;
1432     }
1433     if (appData.debugMode) {
1434         fprintf(debugFP, ">ICS: ");
1435         show_bytes(debugFP, buf, newcount);
1436         fprintf(debugFP, "\n");
1437     }
1438     outcount = OutputToProcess(pr, buf, newcount, outError);
1439     if (outcount < newcount) return -1; /* to be sure */
1440     return count;
1441 }
1442
1443 void
1444 read_from_player(isr, closure, message, count, error)
1445      InputSourceRef isr;
1446      VOIDSTAR closure;
1447      char *message;
1448      int count;
1449      int error;
1450 {
1451     int outError, outCount;
1452     static int gotEof = 0;
1453
1454     /* Pass data read from player on to ICS */
1455     if (count > 0) {
1456         gotEof = 0;
1457         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1458         if (outCount < count) {
1459             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460         }
1461     } else if (count < 0) {
1462         RemoveInputSource(isr);
1463         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1464     } else if (gotEof++ > 0) {
1465         RemoveInputSource(isr);
1466         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1467     }
1468 }
1469
1470 void
1471 KeepAlive()
1472 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1473     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1474     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1475     SendToICS("date\n");
1476     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1477 }
1478
1479 /* added routine for printf style output to ics */
1480 void ics_printf(char *format, ...)
1481 {
1482     char buffer[MSG_SIZ];
1483     va_list args;
1484
1485     va_start(args, format);
1486     vsnprintf(buffer, sizeof(buffer), format, args);
1487     buffer[sizeof(buffer)-1] = '\0';
1488     SendToICS(buffer);
1489     va_end(args);
1490 }
1491
1492 void
1493 SendToICS(s)
1494      char *s;
1495 {
1496     int count, outCount, outError;
1497
1498     if (icsPR == NULL) return;
1499
1500     count = strlen(s);
1501     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507 /* This is used for sending logon scripts to the ICS. Sending
1508    without a delay causes problems when using timestamp on ICC
1509    (at least on my machine). */
1510 void
1511 SendToICSDelayed(s,msdelay)
1512      char *s;
1513      long msdelay;
1514 {
1515     int count, outCount, outError;
1516
1517     if (icsPR == NULL) return;
1518
1519     count = strlen(s);
1520     if (appData.debugMode) {
1521         fprintf(debugFP, ">ICS: ");
1522         show_bytes(debugFP, s, count);
1523         fprintf(debugFP, "\n");
1524     }
1525     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1526                                       msdelay);
1527     if (outCount < count) {
1528         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1529     }
1530 }
1531
1532
1533 /* Remove all highlighting escape sequences in s
1534    Also deletes any suffix starting with '(' 
1535    */
1536 char *
1537 StripHighlightAndTitle(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             if (*s == '(' || *s == '[') {
1550                 *p = NULLCHAR;
1551                 return retbuf;
1552             }
1553             *p++ = *s++;
1554         }
1555     }
1556     *p = NULLCHAR;
1557     return retbuf;
1558 }
1559
1560 /* Remove all highlighting escape sequences in s */
1561 char *
1562 StripHighlight(s)
1563      char *s;
1564 {
1565     static char retbuf[MSG_SIZ];
1566     char *p = retbuf;
1567
1568     while (*s != NULLCHAR) {
1569         while (*s == '\033') {
1570             while (*s != NULLCHAR && !isalpha(*s)) s++;
1571             if (*s != NULLCHAR) s++;
1572         }
1573         while (*s != NULLCHAR && *s != '\033') {
1574             *p++ = *s++;
1575         }
1576     }
1577     *p = NULLCHAR;
1578     return retbuf;
1579 }
1580
1581 char *variantNames[] = VARIANT_NAMES;
1582 char *
1583 VariantName(v)
1584      VariantClass v;
1585 {
1586     return variantNames[v];
1587 }
1588
1589
1590 /* Identify a variant from the strings the chess servers use or the
1591    PGN Variant tag names we use. */
1592 VariantClass
1593 StringToVariant(e)
1594      char *e;
1595 {
1596     char *p;
1597     int wnum = -1;
1598     VariantClass v = VariantNormal;
1599     int i, found = FALSE;
1600     char buf[MSG_SIZ];
1601
1602     if (!e) return v;
1603
1604     /* [HGM] skip over optional board-size prefixes */
1605     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1606         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1607         while( *e++ != '_');
1608     }
1609
1610     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1611         v = VariantNormal;
1612         found = TRUE;
1613     } else
1614     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1615       if (StrCaseStr(e, variantNames[i])) {
1616         v = (VariantClass) i;
1617         found = TRUE;
1618         break;
1619       }
1620     }
1621
1622     if (!found) {
1623       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1624           || StrCaseStr(e, "wild/fr") 
1625           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1626         v = VariantFischeRandom;
1627       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1628                  (i = 1, p = StrCaseStr(e, "w"))) {
1629         p += i;
1630         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1631         if (isdigit(*p)) {
1632           wnum = atoi(p);
1633         } else {
1634           wnum = -1;
1635         }
1636         switch (wnum) {
1637         case 0: /* FICS only, actually */
1638         case 1:
1639           /* Castling legal even if K starts on d-file */
1640           v = VariantWildCastle;
1641           break;
1642         case 2:
1643         case 3:
1644         case 4:
1645           /* Castling illegal even if K & R happen to start in
1646              normal positions. */
1647           v = VariantNoCastle;
1648           break;
1649         case 5:
1650         case 7:
1651         case 8:
1652         case 10:
1653         case 11:
1654         case 12:
1655         case 13:
1656         case 14:
1657         case 15:
1658         case 18:
1659         case 19:
1660           /* Castling legal iff K & R start in normal positions */
1661           v = VariantNormal;
1662           break;
1663         case 6:
1664         case 20:
1665         case 21:
1666           /* Special wilds for position setup; unclear what to do here */
1667           v = VariantLoadable;
1668           break;
1669         case 9:
1670           /* Bizarre ICC game */
1671           v = VariantTwoKings;
1672           break;
1673         case 16:
1674           v = VariantKriegspiel;
1675           break;
1676         case 17:
1677           v = VariantLosers;
1678           break;
1679         case 22:
1680           v = VariantFischeRandom;
1681           break;
1682         case 23:
1683           v = VariantCrazyhouse;
1684           break;
1685         case 24:
1686           v = VariantBughouse;
1687           break;
1688         case 25:
1689           v = Variant3Check;
1690           break;
1691         case 26:
1692           /* Not quite the same as FICS suicide! */
1693           v = VariantGiveaway;
1694           break;
1695         case 27:
1696           v = VariantAtomic;
1697           break;
1698         case 28:
1699           v = VariantShatranj;
1700           break;
1701
1702         /* Temporary names for future ICC types.  The name *will* change in 
1703            the next xboard/WinBoard release after ICC defines it. */
1704         case 29:
1705           v = Variant29;
1706           break;
1707         case 30:
1708           v = Variant30;
1709           break;
1710         case 31:
1711           v = Variant31;
1712           break;
1713         case 32:
1714           v = Variant32;
1715           break;
1716         case 33:
1717           v = Variant33;
1718           break;
1719         case 34:
1720           v = Variant34;
1721           break;
1722         case 35:
1723           v = Variant35;
1724           break;
1725         case 36:
1726           v = Variant36;
1727           break;
1728         case 37:
1729           v = VariantShogi;
1730           break;
1731         case 38:
1732           v = VariantXiangqi;
1733           break;
1734         case 39:
1735           v = VariantCourier;
1736           break;
1737         case 40:
1738           v = VariantGothic;
1739           break;
1740         case 41:
1741           v = VariantCapablanca;
1742           break;
1743         case 42:
1744           v = VariantKnightmate;
1745           break;
1746         case 43:
1747           v = VariantFairy;
1748           break;
1749         case 44:
1750           v = VariantCylinder;
1751           break;
1752         case 45:
1753           v = VariantFalcon;
1754           break;
1755         case 46:
1756           v = VariantCapaRandom;
1757           break;
1758         case 47:
1759           v = VariantBerolina;
1760           break;
1761         case 48:
1762           v = VariantJanus;
1763           break;
1764         case 49:
1765           v = VariantSuper;
1766           break;
1767         case 50:
1768           v = VariantGreat;
1769           break;
1770         case -1:
1771           /* Found "wild" or "w" in the string but no number;
1772              must assume it's normal chess. */
1773           v = VariantNormal;
1774           break;
1775         default:
1776           sprintf(buf, _("Unknown wild type %d"), wnum);
1777           DisplayError(buf, 0);
1778           v = VariantUnknown;
1779           break;
1780         }
1781       }
1782     }
1783     if (appData.debugMode) {
1784       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1785               e, wnum, VariantName(v));
1786     }
1787     return v;
1788 }
1789
1790 static int leftover_start = 0, leftover_len = 0;
1791 char star_match[STAR_MATCH_N][MSG_SIZ];
1792
1793 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1794    advance *index beyond it, and set leftover_start to the new value of
1795    *index; else return FALSE.  If pattern contains the character '*', it
1796    matches any sequence of characters not containing '\r', '\n', or the
1797    character following the '*' (if any), and the matched sequence(s) are
1798    copied into star_match.
1799    */
1800 int
1801 looking_at(buf, index, pattern)
1802      char *buf;
1803      int *index;
1804      char *pattern;
1805 {
1806     char *bufp = &buf[*index], *patternp = pattern;
1807     int star_count = 0;
1808     char *matchp = star_match[0];
1809     
1810     for (;;) {
1811         if (*patternp == NULLCHAR) {
1812             *index = leftover_start = bufp - buf;
1813             *matchp = NULLCHAR;
1814             return TRUE;
1815         }
1816         if (*bufp == NULLCHAR) return FALSE;
1817         if (*patternp == '*') {
1818             if (*bufp == *(patternp + 1)) {
1819                 *matchp = NULLCHAR;
1820                 matchp = star_match[++star_count];
1821                 patternp += 2;
1822                 bufp++;
1823                 continue;
1824             } else if (*bufp == '\n' || *bufp == '\r') {
1825                 patternp++;
1826                 if (*patternp == NULLCHAR)
1827                   continue;
1828                 else
1829                   return FALSE;
1830             } else {
1831                 *matchp++ = *bufp++;
1832                 continue;
1833             }
1834         }
1835         if (*patternp != *bufp) return FALSE;
1836         patternp++;
1837         bufp++;
1838     }
1839 }
1840
1841 void
1842 SendToPlayer(data, length)
1843      char *data;
1844      int length;
1845 {
1846     int error, outCount;
1847     outCount = OutputToProcess(NoProc, data, length, &error);
1848     if (outCount < length) {
1849         DisplayFatalError(_("Error writing to display"), error, 1);
1850     }
1851 }
1852
1853 void
1854 PackHolding(packed, holding)
1855      char packed[];
1856      char *holding;
1857 {
1858     char *p = holding;
1859     char *q = packed;
1860     int runlength = 0;
1861     int curr = 9999;
1862     do {
1863         if (*p == curr) {
1864             runlength++;
1865         } else {
1866             switch (runlength) {
1867               case 0:
1868                 break;
1869               case 1:
1870                 *q++ = curr;
1871                 break;
1872               case 2:
1873                 *q++ = curr;
1874                 *q++ = curr;
1875                 break;
1876               default:
1877                 sprintf(q, "%d", runlength);
1878                 while (*q) q++;
1879                 *q++ = curr;
1880                 break;
1881             }
1882             runlength = 1;
1883             curr = *p;
1884         }
1885     } while (*p++);
1886     *q = NULLCHAR;
1887 }
1888
1889 /* Telnet protocol requests from the front end */
1890 void
1891 TelnetRequest(ddww, option)
1892      unsigned char ddww, option;
1893 {
1894     unsigned char msg[3];
1895     int outCount, outError;
1896
1897     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1898
1899     if (appData.debugMode) {
1900         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1901         switch (ddww) {
1902           case TN_DO:
1903             ddwwStr = "DO";
1904             break;
1905           case TN_DONT:
1906             ddwwStr = "DONT";
1907             break;
1908           case TN_WILL:
1909             ddwwStr = "WILL";
1910             break;
1911           case TN_WONT:
1912             ddwwStr = "WONT";
1913             break;
1914           default:
1915             ddwwStr = buf1;
1916             sprintf(buf1, "%d", ddww);
1917             break;
1918         }
1919         switch (option) {
1920           case TN_ECHO:
1921             optionStr = "ECHO";
1922             break;
1923           default:
1924             optionStr = buf2;
1925             sprintf(buf2, "%d", option);
1926             break;
1927         }
1928         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1929     }
1930     msg[0] = TN_IAC;
1931     msg[1] = ddww;
1932     msg[2] = option;
1933     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1934     if (outCount < 3) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 void
1940 DoEcho()
1941 {
1942     if (!appData.icsActive) return;
1943     TelnetRequest(TN_DO, TN_ECHO);
1944 }
1945
1946 void
1947 DontEcho()
1948 {
1949     if (!appData.icsActive) return;
1950     TelnetRequest(TN_DONT, TN_ECHO);
1951 }
1952
1953 void
1954 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1955 {
1956     /* put the holdings sent to us by the server on the board holdings area */
1957     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1958     char p;
1959     ChessSquare piece;
1960
1961     if(gameInfo.holdingsWidth < 2)  return;
1962     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1963         return; // prevent overwriting by pre-board holdings
1964
1965     if( (int)lowestPiece >= BlackPawn ) {
1966         holdingsColumn = 0;
1967         countsColumn = 1;
1968         holdingsStartRow = BOARD_HEIGHT-1;
1969         direction = -1;
1970     } else {
1971         holdingsColumn = BOARD_WIDTH-1;
1972         countsColumn = BOARD_WIDTH-2;
1973         holdingsStartRow = 0;
1974         direction = 1;
1975     }
1976
1977     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1978         board[i][holdingsColumn] = EmptySquare;
1979         board[i][countsColumn]   = (ChessSquare) 0;
1980     }
1981     while( (p=*holdings++) != NULLCHAR ) {
1982         piece = CharToPiece( ToUpper(p) );
1983         if(piece == EmptySquare) continue;
1984         /*j = (int) piece - (int) WhitePawn;*/
1985         j = PieceToNumber(piece);
1986         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1987         if(j < 0) continue;               /* should not happen */
1988         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1989         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1990         board[holdingsStartRow+j*direction][countsColumn]++;
1991     }
1992 }
1993
1994
1995 void
1996 VariantSwitch(Board board, VariantClass newVariant)
1997 {
1998    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1999    static Board oldBoard;
2000
2001    startedFromPositionFile = FALSE;
2002    if(gameInfo.variant == newVariant) return;
2003
2004    /* [HGM] This routine is called each time an assignment is made to
2005     * gameInfo.variant during a game, to make sure the board sizes
2006     * are set to match the new variant. If that means adding or deleting
2007     * holdings, we shift the playing board accordingly
2008     * This kludge is needed because in ICS observe mode, we get boards
2009     * of an ongoing game without knowing the variant, and learn about the
2010     * latter only later. This can be because of the move list we requested,
2011     * in which case the game history is refilled from the beginning anyway,
2012     * but also when receiving holdings of a crazyhouse game. In the latter
2013     * case we want to add those holdings to the already received position.
2014     */
2015
2016    
2017    if (appData.debugMode) {
2018      fprintf(debugFP, "Switch board from %s to %s\n",
2019              VariantName(gameInfo.variant), VariantName(newVariant));
2020      setbuf(debugFP, NULL);
2021    }
2022    shuffleOpenings = 0;       /* [HGM] shuffle */
2023    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2024    switch(newVariant) 
2025      {
2026      case VariantShogi:
2027        newWidth = 9;  newHeight = 9;
2028        gameInfo.holdingsSize = 7;
2029      case VariantBughouse:
2030      case VariantCrazyhouse:
2031        newHoldingsWidth = 2; break;
2032      case VariantGreat:
2033        newWidth = 10;
2034      case VariantSuper:
2035        newHoldingsWidth = 2;
2036        gameInfo.holdingsSize = 8;
2037        break;
2038      case VariantGothic:
2039      case VariantCapablanca:
2040      case VariantCapaRandom:
2041        newWidth = 10;
2042      default:
2043        newHoldingsWidth = gameInfo.holdingsSize = 0;
2044      };
2045    
2046    if(newWidth  != gameInfo.boardWidth  ||
2047       newHeight != gameInfo.boardHeight ||
2048       newHoldingsWidth != gameInfo.holdingsWidth ) {
2049      
2050      /* shift position to new playing area, if needed */
2051      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2052        for(i=0; i<BOARD_HEIGHT; i++) 
2053          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2054            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2055              board[i][j];
2056        for(i=0; i<newHeight; i++) {
2057          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2058          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2059        }
2060      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++)
2062          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065      }
2066      gameInfo.boardWidth  = newWidth;
2067      gameInfo.boardHeight = newHeight;
2068      gameInfo.holdingsWidth = newHoldingsWidth;
2069      gameInfo.variant = newVariant;
2070      InitDrawingSizes(-2, 0);
2071    } else gameInfo.variant = newVariant;
2072    CopyBoard(oldBoard, board);   // remember correctly formatted board
2073      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2074    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2075 }
2076
2077 static int loggedOn = FALSE;
2078
2079 /*-- Game start info cache: --*/
2080 int gs_gamenum;
2081 char gs_kind[MSG_SIZ];
2082 static char player1Name[128] = "";
2083 static char player2Name[128] = "";
2084 static char cont_seq[] = "\n\\   ";
2085 static int player1Rating = -1;
2086 static int player2Rating = -1;
2087 /*----------------------------*/
2088
2089 ColorClass curColor = ColorNormal;
2090 int suppressKibitz = 0;
2091
2092 // [HGM] seekgraph
2093 Boolean soughtPending = FALSE;
2094 Boolean seekGraphUp;
2095 #define MAX_SEEK_ADS 200
2096 #define SQUARE 0x80
2097 char *seekAdList[MAX_SEEK_ADS];
2098 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2099 float tcList[MAX_SEEK_ADS];
2100 char colorList[MAX_SEEK_ADS];
2101 int nrOfSeekAds = 0;
2102 int minRating = 1010, maxRating = 2800;
2103 int hMargin = 10, vMargin = 20, h, w;
2104 extern int squareSize, lineGap;
2105
2106 void
2107 PlotSeekAd(int i)
2108 {
2109         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2110         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2111         if(r < minRating+100 && r >=0 ) r = minRating+100;
2112         if(r > maxRating) r = maxRating;
2113         if(tc < 1.) tc = 1.;
2114         if(tc > 95.) tc = 95.;
2115         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2116         y = ((double)r - minRating)/(maxRating - minRating)
2117             * (h-vMargin-squareSize/8-1) + vMargin;
2118         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2119         if(strstr(seekAdList[i], " u ")) color = 1;
2120         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2121            !strstr(seekAdList[i], "bullet") &&
2122            !strstr(seekAdList[i], "blitz") &&
2123            !strstr(seekAdList[i], "standard") ) color = 2;
2124         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2125         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2126 }
2127
2128 void
2129 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2130 {
2131         char buf[MSG_SIZ], *ext = "";
2132         VariantClass v = StringToVariant(type);
2133         if(strstr(type, "wild")) {
2134             ext = type + 4; // append wild number
2135             if(v == VariantFischeRandom) type = "chess960"; else
2136             if(v == VariantLoadable) type = "setup"; else
2137             type = VariantName(v);
2138         }
2139         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2140         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2141             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2142             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2143             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2144             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2145             seekNrList[nrOfSeekAds] = nr;
2146             zList[nrOfSeekAds] = 0;
2147             seekAdList[nrOfSeekAds++] = StrSave(buf);
2148             if(plot) PlotSeekAd(nrOfSeekAds-1);
2149         }
2150 }
2151
2152 void
2153 EraseSeekDot(int i)
2154 {
2155     int x = xList[i], y = yList[i], d=squareSize/4, k;
2156     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2157     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2158     // now replot every dot that overlapped
2159     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2160         int xx = xList[k], yy = yList[k];
2161         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2162             DrawSeekDot(xx, yy, colorList[k]);
2163     }
2164 }
2165
2166 void
2167 RemoveSeekAd(int nr)
2168 {
2169         int i;
2170         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2171             EraseSeekDot(i);
2172             if(seekAdList[i]) free(seekAdList[i]);
2173             seekAdList[i] = seekAdList[--nrOfSeekAds];
2174             seekNrList[i] = seekNrList[nrOfSeekAds];
2175             ratingList[i] = ratingList[nrOfSeekAds];
2176             colorList[i]  = colorList[nrOfSeekAds];
2177             tcList[i] = tcList[nrOfSeekAds];
2178             xList[i]  = xList[nrOfSeekAds];
2179             yList[i]  = yList[nrOfSeekAds];
2180             zList[i]  = zList[nrOfSeekAds];
2181             seekAdList[nrOfSeekAds] = NULL;
2182             break;
2183         }
2184 }
2185
2186 Boolean
2187 MatchSoughtLine(char *line)
2188 {
2189     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2190     int nr, base, inc, u=0; char dummy;
2191
2192     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2193        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2194        (u=1) &&
2195        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2196         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2197         // match: compact and save the line
2198         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2199         return TRUE;
2200     }
2201     return FALSE;
2202 }
2203
2204 int
2205 DrawSeekGraph()
2206 {
2207     if(!seekGraphUp) return FALSE;
2208     int i;
2209     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2210     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2211
2212     DrawSeekBackground(0, 0, w, h);
2213     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2214     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2215     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2216         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2217         yy = h-1-yy;
2218         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2219         if(i%500 == 0) {
2220             char buf[MSG_SIZ];
2221             sprintf(buf, "%d", i);
2222             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2223         }
2224     }
2225     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2226     for(i=1; i<100; i+=(i<10?1:5)) {
2227         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2228         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2229         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2230             char buf[MSG_SIZ];
2231             sprintf(buf, "%d", i);
2232             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2233         }
2234     }
2235     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2236     return TRUE;
2237 }
2238
2239 int SeekGraphClick(ClickType click, int x, int y, int moving)
2240 {
2241     static int lastDown = 0, displayed = 0, lastSecond;
2242     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2243         if(click == Release || moving) return FALSE;
2244         nrOfSeekAds = 0;
2245         soughtPending = TRUE;
2246         SendToICS(ics_prefix);
2247         SendToICS("sought\n"); // should this be "sought all"?
2248     } else { // issue challenge based on clicked ad
2249         int dist = 10000; int i, closest = 0, second = 0;
2250         for(i=0; i<nrOfSeekAds; i++) {
2251             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2252             if(d < dist) { dist = d; closest = i; }
2253             second += (d - zList[i] < 120); // count in-range ads
2254             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2255         }
2256         if(dist < 120) {
2257             char buf[MSG_SIZ];
2258             second = (second > 1);
2259             if(displayed != closest || second != lastSecond) {
2260                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2261                 lastSecond = second; displayed = closest;
2262             }
2263             if(click == Press) {
2264                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2265                 lastDown = closest;
2266                 return TRUE;
2267             } // on press 'hit', only show info
2268             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2269             sprintf(buf, "play %d\n", seekNrList[closest]);
2270             SendToICS(ics_prefix);
2271             SendToICS(buf);
2272             return TRUE; // let incoming board of started game pop down the graph
2273         } else if(click == Release) { // release 'miss' is ignored
2274             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2275             if(moving == 2) { // right up-click
2276                 nrOfSeekAds = 0; // refresh graph
2277                 soughtPending = TRUE;
2278                 SendToICS(ics_prefix);
2279                 SendToICS("sought\n"); // should this be "sought all"?
2280             }
2281             return TRUE;
2282         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2283         // press miss or release hit 'pop down' seek graph
2284         seekGraphUp = FALSE;
2285         DrawPosition(TRUE, NULL);
2286     }
2287     return TRUE;
2288 }
2289
2290 void
2291 read_from_ics(isr, closure, data, count, error)
2292      InputSourceRef isr;
2293      VOIDSTAR closure;
2294      char *data;
2295      int count;
2296      int error;
2297 {
2298 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2299 #define STARTED_NONE 0
2300 #define STARTED_MOVES 1
2301 #define STARTED_BOARD 2
2302 #define STARTED_OBSERVE 3
2303 #define STARTED_HOLDINGS 4
2304 #define STARTED_CHATTER 5
2305 #define STARTED_COMMENT 6
2306 #define STARTED_MOVES_NOHIDE 7
2307     
2308     static int started = STARTED_NONE;
2309     static char parse[20000];
2310     static int parse_pos = 0;
2311     static char buf[BUF_SIZE + 1];
2312     static int firstTime = TRUE, intfSet = FALSE;
2313     static ColorClass prevColor = ColorNormal;
2314     static int savingComment = FALSE;
2315     static int cmatch = 0; // continuation sequence match
2316     char *bp;
2317     char str[500];
2318     int i, oldi;
2319     int buf_len;
2320     int next_out;
2321     int tkind;
2322     int backup;    /* [DM] For zippy color lines */
2323     char *p;
2324     char talker[MSG_SIZ]; // [HGM] chat
2325     int channel;
2326
2327     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2328
2329     if (appData.debugMode) {
2330       if (!error) {
2331         fprintf(debugFP, "<ICS: ");
2332         show_bytes(debugFP, data, count);
2333         fprintf(debugFP, "\n");
2334       }
2335     }
2336
2337     if (appData.debugMode) { int f = forwardMostMove;
2338         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2339                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2340                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2341     }
2342     if (count > 0) {
2343         /* If last read ended with a partial line that we couldn't parse,
2344            prepend it to the new read and try again. */
2345         if (leftover_len > 0) {
2346             for (i=0; i<leftover_len; i++)
2347               buf[i] = buf[leftover_start + i];
2348         }
2349
2350     /* copy new characters into the buffer */
2351     bp = buf + leftover_len;
2352     buf_len=leftover_len;
2353     for (i=0; i<count; i++)
2354     {
2355         // ignore these
2356         if (data[i] == '\r')
2357             continue;
2358
2359         // join lines split by ICS?
2360         if (!appData.noJoin)
2361         {
2362             /*
2363                 Joining just consists of finding matches against the
2364                 continuation sequence, and discarding that sequence
2365                 if found instead of copying it.  So, until a match
2366                 fails, there's nothing to do since it might be the
2367                 complete sequence, and thus, something we don't want
2368                 copied.
2369             */
2370             if (data[i] == cont_seq[cmatch])
2371             {
2372                 cmatch++;
2373                 if (cmatch == strlen(cont_seq))
2374                 {
2375                     cmatch = 0; // complete match.  just reset the counter
2376
2377                     /*
2378                         it's possible for the ICS to not include the space
2379                         at the end of the last word, making our [correct]
2380                         join operation fuse two separate words.  the server
2381                         does this when the space occurs at the width setting.
2382                     */
2383                     if (!buf_len || buf[buf_len-1] != ' ')
2384                     {
2385                         *bp++ = ' ';
2386                         buf_len++;
2387                     }
2388                 }
2389                 continue;
2390             }
2391             else if (cmatch)
2392             {
2393                 /*
2394                     match failed, so we have to copy what matched before
2395                     falling through and copying this character.  In reality,
2396                     this will only ever be just the newline character, but
2397                     it doesn't hurt to be precise.
2398                 */
2399                 strncpy(bp, cont_seq, cmatch);
2400                 bp += cmatch;
2401                 buf_len += cmatch;
2402                 cmatch = 0;
2403             }
2404         }
2405
2406         // copy this char
2407         *bp++ = data[i];
2408         buf_len++;
2409     }
2410
2411         buf[buf_len] = NULLCHAR;
2412 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2413         next_out = 0;
2414         leftover_start = 0;
2415         
2416         i = 0;
2417         while (i < buf_len) {
2418             /* Deal with part of the TELNET option negotiation
2419                protocol.  We refuse to do anything beyond the
2420                defaults, except that we allow the WILL ECHO option,
2421                which ICS uses to turn off password echoing when we are
2422                directly connected to it.  We reject this option
2423                if localLineEditing mode is on (always on in xboard)
2424                and we are talking to port 23, which might be a real
2425                telnet server that will try to keep WILL ECHO on permanently.
2426              */
2427             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2428                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2429                 unsigned char option;
2430                 oldi = i;
2431                 switch ((unsigned char) buf[++i]) {
2432                   case TN_WILL:
2433                     if (appData.debugMode)
2434                       fprintf(debugFP, "\n<WILL ");
2435                     switch (option = (unsigned char) buf[++i]) {
2436                       case TN_ECHO:
2437                         if (appData.debugMode)
2438                           fprintf(debugFP, "ECHO ");
2439                         /* Reply only if this is a change, according
2440                            to the protocol rules. */
2441                         if (remoteEchoOption) break;
2442                         if (appData.localLineEditing &&
2443                             atoi(appData.icsPort) == TN_PORT) {
2444                             TelnetRequest(TN_DONT, TN_ECHO);
2445                         } else {
2446                             EchoOff();
2447                             TelnetRequest(TN_DO, TN_ECHO);
2448                             remoteEchoOption = TRUE;
2449                         }
2450                         break;
2451                       default:
2452                         if (appData.debugMode)
2453                           fprintf(debugFP, "%d ", option);
2454                         /* Whatever this is, we don't want it. */
2455                         TelnetRequest(TN_DONT, option);
2456                         break;
2457                     }
2458                     break;
2459                   case TN_WONT:
2460                     if (appData.debugMode)
2461                       fprintf(debugFP, "\n<WONT ");
2462                     switch (option = (unsigned char) buf[++i]) {
2463                       case TN_ECHO:
2464                         if (appData.debugMode)
2465                           fprintf(debugFP, "ECHO ");
2466                         /* Reply only if this is a change, according
2467                            to the protocol rules. */
2468                         if (!remoteEchoOption) break;
2469                         EchoOn();
2470                         TelnetRequest(TN_DONT, TN_ECHO);
2471                         remoteEchoOption = FALSE;
2472                         break;
2473                       default:
2474                         if (appData.debugMode)
2475                           fprintf(debugFP, "%d ", (unsigned char) option);
2476                         /* Whatever this is, it must already be turned
2477                            off, because we never agree to turn on
2478                            anything non-default, so according to the
2479                            protocol rules, we don't reply. */
2480                         break;
2481                     }
2482                     break;
2483                   case TN_DO:
2484                     if (appData.debugMode)
2485                       fprintf(debugFP, "\n<DO ");
2486                     switch (option = (unsigned char) buf[++i]) {
2487                       default:
2488                         /* Whatever this is, we refuse to do it. */
2489                         if (appData.debugMode)
2490                           fprintf(debugFP, "%d ", option);
2491                         TelnetRequest(TN_WONT, option);
2492                         break;
2493                     }
2494                     break;
2495                   case TN_DONT:
2496                     if (appData.debugMode)
2497                       fprintf(debugFP, "\n<DONT ");
2498                     switch (option = (unsigned char) buf[++i]) {
2499                       default:
2500                         if (appData.debugMode)
2501                           fprintf(debugFP, "%d ", option);
2502                         /* Whatever this is, we are already not doing
2503                            it, because we never agree to do anything
2504                            non-default, so according to the protocol
2505                            rules, we don't reply. */
2506                         break;
2507                     }
2508                     break;
2509                   case TN_IAC:
2510                     if (appData.debugMode)
2511                       fprintf(debugFP, "\n<IAC ");
2512                     /* Doubled IAC; pass it through */
2513                     i--;
2514                     break;
2515                   default:
2516                     if (appData.debugMode)
2517                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2518                     /* Drop all other telnet commands on the floor */
2519                     break;
2520                 }
2521                 if (oldi > next_out)
2522                   SendToPlayer(&buf[next_out], oldi - next_out);
2523                 if (++i > next_out)
2524                   next_out = i;
2525                 continue;
2526             }
2527                 
2528             /* OK, this at least will *usually* work */
2529             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2530                 loggedOn = TRUE;
2531             }
2532             
2533             if (loggedOn && !intfSet) {
2534                 if (ics_type == ICS_ICC) {
2535                   sprintf(str,
2536                           "/set-quietly interface %s\n/set-quietly style 12\n",
2537                           programVersion);
2538                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2539                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2540                 } else if (ics_type == ICS_CHESSNET) {
2541                   sprintf(str, "/style 12\n");
2542                 } else {
2543                   strcpy(str, "alias $ @\n$set interface ");
2544                   strcat(str, programVersion);
2545                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2546                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2547                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2548 #ifdef WIN32
2549                   strcat(str, "$iset nohighlight 1\n");
2550 #endif
2551                   strcat(str, "$iset lock 1\n$style 12\n");
2552                 }
2553                 SendToICS(str);
2554                 NotifyFrontendLogin();
2555                 intfSet = TRUE;
2556             }
2557
2558             if (started == STARTED_COMMENT) {
2559                 /* Accumulate characters in comment */
2560                 parse[parse_pos++] = buf[i];
2561                 if (buf[i] == '\n') {
2562                     parse[parse_pos] = NULLCHAR;
2563                     if(chattingPartner>=0) {
2564                         char mess[MSG_SIZ];
2565                         sprintf(mess, "%s%s", talker, parse);
2566                         OutputChatMessage(chattingPartner, mess);
2567                         chattingPartner = -1;
2568                         next_out = i+1; // [HGM] suppress printing in ICS window
2569                     } else
2570                     if(!suppressKibitz) // [HGM] kibitz
2571                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2572                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2573                         int nrDigit = 0, nrAlph = 0, j;
2574                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2575                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2576                         parse[parse_pos] = NULLCHAR;
2577                         // try to be smart: if it does not look like search info, it should go to
2578                         // ICS interaction window after all, not to engine-output window.
2579                         for(j=0; j<parse_pos; j++) { // count letters and digits
2580                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2581                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2582                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2583                         }
2584                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2585                             int depth=0; float score;
2586                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2587                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2588                                 pvInfoList[forwardMostMove-1].depth = depth;
2589                                 pvInfoList[forwardMostMove-1].score = 100*score;
2590                             }
2591                             OutputKibitz(suppressKibitz, parse);
2592                         } else {
2593                             char tmp[MSG_SIZ];
2594                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2595                             SendToPlayer(tmp, strlen(tmp));
2596                         }
2597                         next_out = i+1; // [HGM] suppress printing in ICS window
2598                     }
2599                     started = STARTED_NONE;
2600                 } else {
2601                     /* Don't match patterns against characters in comment */
2602                     i++;
2603                     continue;
2604                 }
2605             }
2606             if (started == STARTED_CHATTER) {
2607                 if (buf[i] != '\n') {
2608                     /* Don't match patterns against characters in chatter */
2609                     i++;
2610                     continue;
2611                 }
2612                 started = STARTED_NONE;
2613                 if(suppressKibitz) next_out = i+1;
2614             }
2615
2616             /* Kludge to deal with rcmd protocol */
2617             if (firstTime && looking_at(buf, &i, "\001*")) {
2618                 DisplayFatalError(&buf[1], 0, 1);
2619                 continue;
2620             } else {
2621                 firstTime = FALSE;
2622             }
2623
2624             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2625                 ics_type = ICS_ICC;
2626                 ics_prefix = "/";
2627                 if (appData.debugMode)
2628                   fprintf(debugFP, "ics_type %d\n", ics_type);
2629                 continue;
2630             }
2631             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2632                 ics_type = ICS_FICS;
2633                 ics_prefix = "$";
2634                 if (appData.debugMode)
2635                   fprintf(debugFP, "ics_type %d\n", ics_type);
2636                 continue;
2637             }
2638             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2639                 ics_type = ICS_CHESSNET;
2640                 ics_prefix = "/";
2641                 if (appData.debugMode)
2642                   fprintf(debugFP, "ics_type %d\n", ics_type);
2643                 continue;
2644             }
2645
2646             if (!loggedOn &&
2647                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2648                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2649                  looking_at(buf, &i, "will be \"*\""))) {
2650               strcpy(ics_handle, star_match[0]);
2651               continue;
2652             }
2653
2654             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2655               char buf[MSG_SIZ];
2656               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2657               DisplayIcsInteractionTitle(buf);
2658               have_set_title = TRUE;
2659             }
2660
2661             /* skip finger notes */
2662             if (started == STARTED_NONE &&
2663                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2664                  (buf[i] == '1' && buf[i+1] == '0')) &&
2665                 buf[i+2] == ':' && buf[i+3] == ' ') {
2666               started = STARTED_CHATTER;
2667               i += 3;
2668               continue;
2669             }
2670
2671             oldi = i;
2672             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2673             if(appData.seekGraph) {
2674                 if(soughtPending && MatchSoughtLine(buf+i)) {
2675                     i = strstr(buf+i, "rated") - buf;
2676                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2677                     next_out = leftover_start = i;
2678                     started = STARTED_CHATTER;
2679                     suppressKibitz = TRUE;
2680                     continue;
2681                 }
2682                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2683                         && looking_at(buf, &i, "* ads displayed")) {
2684                     soughtPending = FALSE;
2685                     seekGraphUp = TRUE;
2686                     DrawSeekGraph();
2687                     continue;
2688                 }
2689                 if(appData.autoRefresh) {
2690                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2691                         int s = (ics_type == ICS_ICC); // ICC format differs
2692                         if(seekGraphUp)
2693                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2694                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2695                         looking_at(buf, &i, "*% "); // eat prompt
2696                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2697                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2698                         next_out = i; // suppress
2699                         continue;
2700                     }
2701                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2702                         char *p = star_match[0];
2703                         while(*p) {
2704                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2705                             while(*p && *p++ != ' '); // next
2706                         }
2707                         looking_at(buf, &i, "*% "); // eat prompt
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         continue;
2711                     }
2712                 }
2713             }
2714
2715             /* skip formula vars */
2716             if (started == STARTED_NONE &&
2717                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2718               started = STARTED_CHATTER;
2719               i += 3;
2720               continue;
2721             }
2722
2723             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2724             if (appData.autoKibitz && started == STARTED_NONE && 
2725                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2726                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2727                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2728                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2729                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2730                         suppressKibitz = TRUE;
2731                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2732                         next_out = i;
2733                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2734                                 && (gameMode == IcsPlayingWhite)) ||
2735                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2736                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2737                             started = STARTED_CHATTER; // own kibitz we simply discard
2738                         else {
2739                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2740                             parse_pos = 0; parse[0] = NULLCHAR;
2741                             savingComment = TRUE;
2742                             suppressKibitz = gameMode != IcsObserving ? 2 :
2743                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2744                         } 
2745                         continue;
2746                 } else
2747                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2748                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2749                          && atoi(star_match[0])) {
2750                     // suppress the acknowledgements of our own autoKibitz
2751                     char *p;
2752                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2754                     SendToPlayer(star_match[0], strlen(star_match[0]));
2755                     if(looking_at(buf, &i, "*% ")) // eat prompt
2756                         suppressKibitz = FALSE;
2757                     next_out = i;
2758                     continue;
2759                 }
2760             } // [HGM] kibitz: end of patch
2761
2762             // [HGM] chat: intercept tells by users for which we have an open chat window
2763             channel = -1;
2764             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2765                                            looking_at(buf, &i, "* whispers:") ||
2766                                            looking_at(buf, &i, "* kibitzes:") ||
2767                                            looking_at(buf, &i, "* shouts:") ||
2768                                            looking_at(buf, &i, "* c-shouts:") ||
2769                                            looking_at(buf, &i, "--> * ") ||
2770                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2771                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2772                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2773                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2774                 int p;
2775                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2776                 chattingPartner = -1;
2777
2778                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2779                 for(p=0; p<MAX_CHAT; p++) {
2780                     if(channel == atoi(chatPartner[p])) {
2781                     talker[0] = '['; strcat(talker, "] ");
2782                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2783                     chattingPartner = p; break;
2784                     }
2785                 } else
2786                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2787                 for(p=0; p<MAX_CHAT; p++) {
2788                     if(!strcmp("kibitzes", chatPartner[p])) {
2789                         talker[0] = '['; strcat(talker, "] ");
2790                         chattingPartner = p; break;
2791                     }
2792                 } else
2793                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2794                 for(p=0; p<MAX_CHAT; p++) {
2795                     if(!strcmp("whispers", chatPartner[p])) {
2796                         talker[0] = '['; strcat(talker, "] ");
2797                         chattingPartner = p; break;
2798                     }
2799                 } else
2800                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2801                   if(buf[i-8] == '-' && buf[i-3] == 't')
2802                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2803                     if(!strcmp("c-shouts", chatPartner[p])) {
2804                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2805                         chattingPartner = p; break;
2806                     }
2807                   }
2808                   if(chattingPartner < 0)
2809                   for(p=0; p<MAX_CHAT; p++) {
2810                     if(!strcmp("shouts", chatPartner[p])) {
2811                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2812                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2813                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0; Colorize(ColorTell, FALSE);
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2825                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2826                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2827                     started = STARTED_COMMENT;
2828                     parse_pos = 0; parse[0] = NULLCHAR;
2829                     savingComment = 3 + chattingPartner; // counts as TRUE
2830                     suppressKibitz = TRUE;
2831                     continue;
2832                 }
2833             } // [HGM] chat: end of patch
2834
2835             if (appData.zippyTalk || appData.zippyPlay) {
2836                 /* [DM] Backup address for color zippy lines */
2837                 backup = i;
2838 #if ZIPPY
2839        #ifdef WIN32
2840                if (loggedOn == TRUE)
2841                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2842                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2843        #else
2844                 if (ZippyControl(buf, &i) ||
2845                     ZippyConverse(buf, &i) ||
2846                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2847                       loggedOn = TRUE;
2848                       if (!appData.colorize) continue;
2849                 }
2850        #endif
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #ifdef ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhitePromotionQueen:
4579       case BlackPromotionQueen:
4580       case WhitePromotionRook:
4581       case BlackPromotionRook:
4582       case WhitePromotionBishop:
4583       case BlackPromotionBishop:
4584       case WhitePromotionKnight:
4585       case BlackPromotionKnight:
4586       case WhitePromotionKing:
4587       case BlackPromotionKing:
4588       case WhitePromotionChancellor:
4589       case BlackPromotionChancellor:
4590       case WhitePromotionArchbishop:
4591       case BlackPromotionArchbishop:
4592         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4593             sprintf(user_move, "%c%c%c%c=%c\n",
4594                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4595                 PieceToChar(WhiteFerz));
4596         else if(gameInfo.variant == VariantGreat)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteMan));
4600         else
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(PromoPiece(moveType)));
4604         break;
4605       case WhiteDrop:
4606       case BlackDrop:
4607         sprintf(user_move, "%c@%c%c\n",
4608                 ToUpper(PieceToChar((ChessSquare) fromX)),
4609                 AAA + toX, ONE + toY);
4610         break;
4611       case NormalMove:
4612       case WhiteCapturesEnPassant:
4613       case BlackCapturesEnPassant:
4614       case IllegalMove:  /* could be a variant we don't quite understand */
4615         sprintf(user_move, "%c%c%c%c\n",
4616                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4617         break;
4618     }
4619     SendToICS(user_move);
4620     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4621         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4622 }
4623
4624 void
4625 UploadGameEvent()
4626 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4627     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4628     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4629     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4630         DisplayError("You cannot do this while you are playing or observing", 0);
4631         return;
4632     }
4633     if(gameMode != IcsExamining) { // is this ever not the case?
4634         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4635
4636         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4637             sprintf(command, "match %s", ics_handle);
4638         } else { // on FICS we must first go to general examine mode
4639             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4640         }
4641         if(gameInfo.variant != VariantNormal) {
4642             // try figure out wild number, as xboard names are not always valid on ICS
4643             for(i=1; i<=36; i++) {
4644                 sprintf(buf, "wild/%d", i);
4645                 if(StringToVariant(buf) == gameInfo.variant) break;
4646             }
4647             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4648             else if(i == 22) sprintf(buf, "%s fr\n", command);
4649             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4650         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4651         SendToICS(ics_prefix);
4652         SendToICS(buf);
4653         if(startedFromSetupPosition || backwardMostMove != 0) {
4654           fen = PositionToFEN(backwardMostMove, NULL);
4655           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4656             sprintf(buf, "loadfen %s\n", fen);
4657             SendToICS(buf);
4658           } else { // FICS: everything has to set by separate bsetup commands
4659             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4660             sprintf(buf, "bsetup fen %s\n", fen);
4661             SendToICS(buf);
4662             if(!WhiteOnMove(backwardMostMove)) {
4663                 SendToICS("bsetup tomove black\n");
4664             }
4665             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4666             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4667             SendToICS(buf);
4668             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4669             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4670             SendToICS(buf);
4671             i = boards[backwardMostMove][EP_STATUS];
4672             if(i >= 0) { // set e.p.
4673                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4674                 SendToICS(buf);
4675             }
4676             bsetup++;
4677           }
4678         }
4679       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4680             SendToICS("bsetup done\n"); // switch to normal examining.
4681     }
4682     for(i = backwardMostMove; i<last; i++) {
4683         char buf[20];
4684         sprintf(buf, "%s\n", parseList[i]);
4685         SendToICS(buf);
4686     }
4687     SendToICS(ics_prefix);
4688     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4689 }
4690
4691 void
4692 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4693      int rf, ff, rt, ft;
4694      char promoChar;
4695      char move[7];
4696 {
4697     if (rf == DROP_RANK) {
4698         sprintf(move, "%c@%c%c\n",
4699                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4700     } else {
4701         if (promoChar == 'x' || promoChar == NULLCHAR) {
4702             sprintf(move, "%c%c%c%c\n",
4703                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4704         } else {
4705             sprintf(move, "%c%c%c%c%c\n",
4706                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4707         }
4708     }
4709 }
4710
4711 void
4712 ProcessICSInitScript(f)
4713      FILE *f;
4714 {
4715     char buf[MSG_SIZ];
4716
4717     while (fgets(buf, MSG_SIZ, f)) {
4718         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4719     }
4720
4721     fclose(f);
4722 }
4723
4724
4725 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4726 void
4727 AlphaRank(char *move, int n)
4728 {
4729 //    char *p = move, c; int x, y;
4730
4731     if (appData.debugMode) {
4732         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4733     }
4734
4735     if(move[1]=='*' && 
4736        move[2]>='0' && move[2]<='9' &&
4737        move[3]>='a' && move[3]<='x'    ) {
4738         move[1] = '@';
4739         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4740         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4741     } else
4742     if(move[0]>='0' && move[0]<='9' &&
4743        move[1]>='a' && move[1]<='x' &&
4744        move[2]>='0' && move[2]<='9' &&
4745        move[3]>='a' && move[3]<='x'    ) {
4746         /* input move, Shogi -> normal */
4747         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4748         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4749         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4750         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4751     } else
4752     if(move[1]=='@' &&
4753        move[3]>='0' && move[3]<='9' &&
4754        move[2]>='a' && move[2]<='x'    ) {
4755         move[1] = '*';
4756         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4757         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4758     } else
4759     if(
4760        move[0]>='a' && move[0]<='x' &&
4761        move[3]>='0' && move[3]<='9' &&
4762        move[2]>='a' && move[2]<='x'    ) {
4763          /* output move, normal -> Shogi */
4764         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4765         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4766         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4767         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4768         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4769     }
4770     if (appData.debugMode) {
4771         fprintf(debugFP, "   out = '%s'\n", move);
4772     }
4773 }
4774
4775 char yy_textstr[8000];
4776
4777 /* Parser for moves from gnuchess, ICS, or user typein box */
4778 Boolean
4779 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4780      char *move;
4781      int moveNum;
4782      ChessMove *moveType;
4783      int *fromX, *fromY, *toX, *toY;
4784      char *promoChar;
4785 {       
4786     if (appData.debugMode) {
4787         fprintf(debugFP, "move to parse: %s\n", move);
4788     }
4789     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4790
4791     switch (*moveType) {
4792       case WhitePromotionChancellor:
4793       case BlackPromotionChancellor:
4794       case WhitePromotionArchbishop:
4795       case BlackPromotionArchbishop:
4796       case WhitePromotionQueen:
4797       case BlackPromotionQueen:
4798       case WhitePromotionRook:
4799       case BlackPromotionRook:
4800       case WhitePromotionBishop:
4801       case BlackPromotionBishop:
4802       case WhitePromotionKnight:
4803       case BlackPromotionKnight:
4804       case WhitePromotionKing:
4805       case BlackPromotionKing:
4806       case NormalMove:
4807       case WhiteCapturesEnPassant:
4808       case BlackCapturesEnPassant:
4809       case WhiteKingSideCastle:
4810       case WhiteQueenSideCastle:
4811       case BlackKingSideCastle:
4812       case BlackQueenSideCastle:
4813       case WhiteKingSideCastleWild:
4814       case WhiteQueenSideCastleWild:
4815       case BlackKingSideCastleWild:
4816       case BlackQueenSideCastleWild:
4817       /* Code added by Tord: */
4818       case WhiteHSideCastleFR:
4819       case WhiteASideCastleFR:
4820       case BlackHSideCastleFR:
4821       case BlackASideCastleFR:
4822       /* End of code added by Tord */
4823       case IllegalMove:         /* bug or odd chess variant */
4824         *fromX = currentMoveString[0] - AAA;
4825         *fromY = currentMoveString[1] - ONE;
4826         *toX = currentMoveString[2] - AAA;
4827         *toY = currentMoveString[3] - ONE;
4828         *promoChar = currentMoveString[4];
4829         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4830             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4831     if (appData.debugMode) {
4832         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4833     }
4834             *fromX = *fromY = *toX = *toY = 0;
4835             return FALSE;
4836         }
4837         if (appData.testLegality) {
4838           return (*moveType != IllegalMove);
4839         } else {
4840           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4841                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4842         }
4843
4844       case WhiteDrop:
4845       case BlackDrop:
4846         *fromX = *moveType == WhiteDrop ?
4847           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4848           (int) CharToPiece(ToLower(currentMoveString[0]));
4849         *fromY = DROP_RANK;
4850         *toX = currentMoveString[2] - AAA;
4851         *toY = currentMoveString[3] - ONE;
4852         *promoChar = NULLCHAR;
4853         return TRUE;
4854
4855       case AmbiguousMove:
4856       case ImpossibleMove:
4857       case (ChessMove) 0:       /* end of file */
4858       case ElapsedTime:
4859       case Comment:
4860       case PGNTag:
4861       case NAG:
4862       case WhiteWins:
4863       case BlackWins:
4864       case GameIsDrawn:
4865       default:
4866     if (appData.debugMode) {
4867         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4868     }
4869         /* bug? */
4870         *fromX = *fromY = *toX = *toY = 0;
4871         *promoChar = NULLCHAR;
4872         return FALSE;
4873     }
4874 }
4875
4876
4877 void
4878 ParsePV(char *pv, Boolean storeComments)
4879 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4880   int fromX, fromY, toX, toY; char promoChar;
4881   ChessMove moveType;
4882   Boolean valid;
4883   int nr = 0;
4884
4885   endPV = forwardMostMove;
4886   do {
4887     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4888     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4889     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4890 if(appData.debugMode){
4891 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);
4892 }
4893     if(!valid && nr == 0 &&
4894        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4895         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4896         // Hande case where played move is different from leading PV move
4897         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4898         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4899         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4900         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4901           endPV += 2; // if position different, keep this
4902           moveList[endPV-1][0] = fromX + AAA;
4903           moveList[endPV-1][1] = fromY + ONE;
4904           moveList[endPV-1][2] = toX + AAA;
4905           moveList[endPV-1][3] = toY + ONE;
4906           parseList[endPV-1][0] = NULLCHAR;
4907           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4908         }
4909       }
4910     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4911     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4912     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4913     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4914         valid++; // allow comments in PV
4915         continue;
4916     }
4917     nr++;
4918     if(endPV+1 > framePtr) break; // no space, truncate
4919     if(!valid) break;
4920     endPV++;
4921     CopyBoard(boards[endPV], boards[endPV-1]);
4922     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4923     moveList[endPV-1][0] = fromX + AAA;
4924     moveList[endPV-1][1] = fromY + ONE;
4925     moveList[endPV-1][2] = toX + AAA;
4926     moveList[endPV-1][3] = toY + ONE;
4927     if(storeComments)
4928         CoordsToAlgebraic(boards[endPV - 1],
4929                              PosFlags(endPV - 1),
4930                              fromY, fromX, toY, toX, promoChar,
4931                              parseList[endPV - 1]);
4932     else
4933         parseList[endPV-1][0] = NULLCHAR;
4934   } while(valid);
4935   currentMove = endPV;
4936   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4937   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4938                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4939   DrawPosition(TRUE, boards[currentMove]);
4940 }
4941
4942 static int lastX, lastY;
4943
4944 Boolean
4945 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4946 {
4947         int startPV;
4948         char *p;
4949
4950         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4951         lastX = x; lastY = y;
4952         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4953         startPV = index;
4954         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4955         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4956         index = startPV;
4957         do{ while(buf[index] && buf[index] != '\n') index++;
4958         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4959         buf[index] = 0;
4960         ParsePV(buf+startPV, FALSE);
4961         *start = startPV; *end = index-1;
4962         return TRUE;
4963 }
4964
4965 Boolean
4966 LoadPV(int x, int y)
4967 { // called on right mouse click to load PV
4968   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4969   lastX = x; lastY = y;
4970   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4971   return TRUE;
4972 }
4973
4974 void
4975 UnLoadPV()
4976 {
4977   if(endPV < 0) return;
4978   endPV = -1;
4979   currentMove = forwardMostMove;
4980   ClearPremoveHighlights();
4981   DrawPosition(TRUE, boards[currentMove]);
4982 }
4983
4984 void
4985 MovePV(int x, int y, int h)
4986 { // step through PV based on mouse coordinates (called on mouse move)
4987   int margin = h>>3, step = 0;
4988
4989   if(endPV < 0) return;
4990   // we must somehow check if right button is still down (might be released off board!)
4991   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4992   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4993   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4994   if(!step) return;
4995   lastX = x; lastY = y;
4996   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4997   currentMove += step;
4998   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4999   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5000                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5001   DrawPosition(FALSE, boards[currentMove]);
5002 }
5003
5004
5005 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5006 // All positions will have equal probability, but the current method will not provide a unique
5007 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5008 #define DARK 1
5009 #define LITE 2
5010 #define ANY 3
5011
5012 int squaresLeft[4];
5013 int piecesLeft[(int)BlackPawn];
5014 int seed, nrOfShuffles;
5015
5016 void GetPositionNumber()
5017 {       // sets global variable seed
5018         int i;
5019
5020         seed = appData.defaultFrcPosition;
5021         if(seed < 0) { // randomize based on time for negative FRC position numbers
5022                 for(i=0; i<50; i++) seed += random();
5023                 seed = random() ^ random() >> 8 ^ random() << 8;
5024                 if(seed<0) seed = -seed;
5025         }
5026 }
5027
5028 int put(Board board, int pieceType, int rank, int n, int shade)
5029 // put the piece on the (n-1)-th empty squares of the given shade
5030 {
5031         int i;
5032
5033         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5034                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5035                         board[rank][i] = (ChessSquare) pieceType;
5036                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5037                         squaresLeft[ANY]--;
5038                         piecesLeft[pieceType]--; 
5039                         return i;
5040                 }
5041         }
5042         return -1;
5043 }
5044
5045
5046 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5047 // calculate where the next piece goes, (any empty square), and put it there
5048 {
5049         int i;
5050
5051         i = seed % squaresLeft[shade];
5052         nrOfShuffles *= squaresLeft[shade];
5053         seed /= squaresLeft[shade];
5054         put(board, pieceType, rank, i, shade);
5055 }
5056
5057 void AddTwoPieces(Board board, int pieceType, int rank)
5058 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5059 {
5060         int i, n=squaresLeft[ANY], j=n-1, k;
5061
5062         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5063         i = seed % k;  // pick one
5064         nrOfShuffles *= k;
5065         seed /= k;
5066         while(i >= j) i -= j--;
5067         j = n - 1 - j; i += j;
5068         put(board, pieceType, rank, j, ANY);
5069         put(board, pieceType, rank, i, ANY);
5070 }
5071
5072 void SetUpShuffle(Board board, int number)
5073 {
5074         int i, p, first=1;
5075
5076         GetPositionNumber(); nrOfShuffles = 1;
5077
5078         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5079         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5080         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5081
5082         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5083
5084         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5085             p = (int) board[0][i];
5086             if(p < (int) BlackPawn) piecesLeft[p] ++;
5087             board[0][i] = EmptySquare;
5088         }
5089
5090         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5091             // shuffles restricted to allow normal castling put KRR first
5092             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5093                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5094             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5095                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5096             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5097                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5098             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5099                 put(board, WhiteRook, 0, 0, ANY);
5100             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5101         }
5102
5103         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5104             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5105             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5106                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5107                 while(piecesLeft[p] >= 2) {
5108                     AddOnePiece(board, p, 0, LITE);
5109                     AddOnePiece(board, p, 0, DARK);
5110                 }
5111                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5112             }
5113
5114         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5115             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5116             // but we leave King and Rooks for last, to possibly obey FRC restriction
5117             if(p == (int)WhiteRook) continue;
5118             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5119             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5120         }
5121
5122         // now everything is placed, except perhaps King (Unicorn) and Rooks
5123
5124         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5125             // Last King gets castling rights
5126             while(piecesLeft[(int)WhiteUnicorn]) {
5127                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5128                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5129             }
5130
5131             while(piecesLeft[(int)WhiteKing]) {
5132                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5133                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5134             }
5135
5136
5137         } else {
5138             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5139             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5140         }
5141
5142         // Only Rooks can be left; simply place them all
5143         while(piecesLeft[(int)WhiteRook]) {
5144                 i = put(board, WhiteRook, 0, 0, ANY);
5145                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5146                         if(first) {
5147                                 first=0;
5148                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5149                         }
5150                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5151                 }
5152         }
5153         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5154             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5155         }
5156
5157         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5158 }
5159
5160 int SetCharTable( char *table, const char * map )
5161 /* [HGM] moved here from winboard.c because of its general usefulness */
5162 /*       Basically a safe strcpy that uses the last character as King */
5163 {
5164     int result = FALSE; int NrPieces;
5165
5166     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5167                     && NrPieces >= 12 && !(NrPieces&1)) {
5168         int i; /* [HGM] Accept even length from 12 to 34 */
5169
5170         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5171         for( i=0; i<NrPieces/2-1; i++ ) {
5172             table[i] = map[i];
5173             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5174         }
5175         table[(int) WhiteKing]  = map[NrPieces/2-1];
5176         table[(int) BlackKing]  = map[NrPieces-1];
5177
5178         result = TRUE;
5179     }
5180
5181     return result;
5182 }
5183
5184 void Prelude(Board board)
5185 {       // [HGM] superchess: random selection of exo-pieces
5186         int i, j, k; ChessSquare p; 
5187         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5188
5189         GetPositionNumber(); // use FRC position number
5190
5191         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5192             SetCharTable(pieceToChar, appData.pieceToCharTable);
5193             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5194                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5195         }
5196
5197         j = seed%4;                 seed /= 4; 
5198         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5199         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5200         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5201         j = seed%3 + (seed%3 >= j); seed /= 3; 
5202         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5203         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5204         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5205         j = seed%3;                 seed /= 3; 
5206         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5207         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5208         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5209         j = seed%2 + (seed%2 >= j); seed /= 2; 
5210         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5211         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5212         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5213         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5214         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5215         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5216         put(board, exoPieces[0],    0, 0, ANY);
5217         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5218 }
5219
5220 void
5221 InitPosition(redraw)
5222      int redraw;
5223 {
5224     ChessSquare (* pieces)[BOARD_FILES];
5225     int i, j, pawnRow, overrule,
5226     oldx = gameInfo.boardWidth,
5227     oldy = gameInfo.boardHeight,
5228     oldh = gameInfo.holdingsWidth,
5229     oldv = gameInfo.variant;
5230
5231     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5232
5233     /* [AS] Initialize pv info list [HGM] and game status */
5234     {
5235         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5236             pvInfoList[i].depth = 0;
5237             boards[i][EP_STATUS] = EP_NONE;
5238             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5239         }
5240
5241         initialRulePlies = 0; /* 50-move counter start */
5242
5243         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5244         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5245     }
5246
5247     
5248     /* [HGM] logic here is completely changed. In stead of full positions */
5249     /* the initialized data only consist of the two backranks. The switch */
5250     /* selects which one we will use, which is than copied to the Board   */
5251     /* initialPosition, which for the rest is initialized by Pawns and    */
5252     /* empty squares. This initial position is then copied to boards[0],  */
5253     /* possibly after shuffling, so that it remains available.            */
5254
5255     gameInfo.holdingsWidth = 0; /* default board sizes */
5256     gameInfo.boardWidth    = 8;
5257     gameInfo.boardHeight   = 8;
5258     gameInfo.holdingsSize  = 0;
5259     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5260     for(i=0; i<BOARD_FILES-2; i++)
5261       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5262     initialPosition[EP_STATUS] = EP_NONE;
5263     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5264
5265     switch (gameInfo.variant) {
5266     case VariantFischeRandom:
5267       shuffleOpenings = TRUE;
5268     default:
5269       pieces = FIDEArray;
5270       break;
5271     case VariantShatranj:
5272       pieces = ShatranjArray;
5273       nrCastlingRights = 0;
5274       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5275       break;
5276     case VariantMakruk:
5277       pieces = makrukArray;
5278       nrCastlingRights = 0;
5279       startedFromSetupPosition = TRUE;
5280       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5281       break;
5282     case VariantTwoKings:
5283       pieces = twoKingsArray;
5284       break;
5285     case VariantCapaRandom:
5286       shuffleOpenings = TRUE;
5287     case VariantCapablanca:
5288       pieces = CapablancaArray;
5289       gameInfo.boardWidth = 10;
5290       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5291       break;
5292     case VariantGothic:
5293       pieces = GothicArray;
5294       gameInfo.boardWidth = 10;
5295       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5296       break;
5297     case VariantJanus:
5298       pieces = JanusArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5301       nrCastlingRights = 6;
5302         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5303         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5304         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5305         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5306         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5307         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5308       break;
5309     case VariantFalcon:
5310       pieces = FalconArray;
5311       gameInfo.boardWidth = 10;
5312       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5313       break;
5314     case VariantXiangqi:
5315       pieces = XiangqiArray;
5316       gameInfo.boardWidth  = 9;
5317       gameInfo.boardHeight = 10;
5318       nrCastlingRights = 0;
5319       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5320       break;
5321     case VariantShogi:
5322       pieces = ShogiArray;
5323       gameInfo.boardWidth  = 9;
5324       gameInfo.boardHeight = 9;
5325       gameInfo.holdingsSize = 7;
5326       nrCastlingRights = 0;
5327       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5328       break;
5329     case VariantCourier:
5330       pieces = CourierArray;
5331       gameInfo.boardWidth  = 12;
5332       nrCastlingRights = 0;
5333       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5334       break;
5335     case VariantKnightmate:
5336       pieces = KnightmateArray;
5337       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5338       break;
5339     case VariantFairy:
5340       pieces = fairyArray;
5341       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5342       break;
5343     case VariantGreat:
5344       pieces = GreatArray;
5345       gameInfo.boardWidth = 10;
5346       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5347       gameInfo.holdingsSize = 8;
5348       break;
5349     case VariantSuper:
5350       pieces = FIDEArray;
5351       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5352       gameInfo.holdingsSize = 8;
5353       startedFromSetupPosition = TRUE;
5354       break;
5355     case VariantCrazyhouse:
5356     case VariantBughouse:
5357       pieces = FIDEArray;
5358       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5359       gameInfo.holdingsSize = 5;
5360       break;
5361     case VariantWildCastle:
5362       pieces = FIDEArray;
5363       /* !!?shuffle with kings guaranteed to be on d or e file */
5364       shuffleOpenings = 1;
5365       break;
5366     case VariantNoCastle:
5367       pieces = FIDEArray;
5368       nrCastlingRights = 0;
5369       /* !!?unconstrained back-rank shuffle */
5370       shuffleOpenings = 1;
5371       break;
5372     }
5373
5374     overrule = 0;
5375     if(appData.NrFiles >= 0) {
5376         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5377         gameInfo.boardWidth = appData.NrFiles;
5378     }
5379     if(appData.NrRanks >= 0) {
5380         gameInfo.boardHeight = appData.NrRanks;
5381     }
5382     if(appData.holdingsSize >= 0) {
5383         i = appData.holdingsSize;
5384         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5385         gameInfo.holdingsSize = i;
5386     }
5387     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5388     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5389         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5390
5391     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5392     if(pawnRow < 1) pawnRow = 1;
5393     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5394
5395     /* User pieceToChar list overrules defaults */
5396     if(appData.pieceToCharTable != NULL)
5397         SetCharTable(pieceToChar, appData.pieceToCharTable);
5398
5399     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5400
5401         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5402             s = (ChessSquare) 0; /* account holding counts in guard band */
5403         for( i=0; i<BOARD_HEIGHT; i++ )
5404             initialPosition[i][j] = s;
5405
5406         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5407         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5408         initialPosition[pawnRow][j] = WhitePawn;
5409         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5410         if(gameInfo.variant == VariantXiangqi) {
5411             if(j&1) {
5412                 initialPosition[pawnRow][j] = 
5413                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5414                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5415                    initialPosition[2][j] = WhiteCannon;
5416                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5417                 }
5418             }
5419         }
5420         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5421     }
5422     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5423
5424             j=BOARD_LEFT+1;
5425             initialPosition[1][j] = WhiteBishop;
5426             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5427             j=BOARD_RGHT-2;
5428             initialPosition[1][j] = WhiteRook;
5429             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5430     }
5431
5432     if( nrCastlingRights == -1) {
5433         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5434         /*       This sets default castling rights from none to normal corners   */
5435         /* Variants with other castling rights must set them themselves above    */
5436         nrCastlingRights = 6;
5437        
5438         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5439         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5440         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5441         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5442         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5443         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5444      }
5445
5446      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5447      if(gameInfo.variant == VariantGreat) { // promotion commoners
5448         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5449         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5450         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5451         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5452      }
5453   if (appData.debugMode) {
5454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5455   }
5456     if(shuffleOpenings) {
5457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5458         startedFromSetupPosition = TRUE;
5459     }
5460     if(startedFromPositionFile) {
5461       /* [HGM] loadPos: use PositionFile for every new game */
5462       CopyBoard(initialPosition, filePosition);
5463       for(i=0; i<nrCastlingRights; i++)
5464           initialRights[i] = filePosition[CASTLING][i];
5465       startedFromSetupPosition = TRUE;
5466     }
5467
5468     CopyBoard(boards[0], initialPosition);
5469
5470     if(oldx != gameInfo.boardWidth ||
5471        oldy != gameInfo.boardHeight ||
5472        oldh != gameInfo.holdingsWidth
5473 #ifdef GOTHIC
5474        || oldv == VariantGothic ||        // For licensing popups
5475        gameInfo.variant == VariantGothic
5476 #endif
5477 #ifdef FALCON
5478        || oldv == VariantFalcon ||
5479        gameInfo.variant == VariantFalcon
5480 #endif
5481                                          )
5482             InitDrawingSizes(-2 ,0);
5483
5484     if (redraw)
5485       DrawPosition(TRUE, boards[currentMove]);
5486 }
5487
5488 void
5489 SendBoard(cps, moveNum)
5490      ChessProgramState *cps;
5491      int moveNum;
5492 {
5493     char message[MSG_SIZ];
5494     
5495     if (cps->useSetboard) {
5496       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5497       sprintf(message, "setboard %s\n", fen);
5498       SendToProgram(message, cps);
5499       free(fen);
5500
5501     } else {
5502       ChessSquare *bp;
5503       int i, j;
5504       /* Kludge to set black to move, avoiding the troublesome and now
5505        * deprecated "black" command.
5506        */
5507       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5508
5509       SendToProgram("edit\n", cps);
5510       SendToProgram("#\n", cps);
5511       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5512         bp = &boards[moveNum][i][BOARD_LEFT];
5513         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5514           if ((int) *bp < (int) BlackPawn) {
5515             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5516                     AAA + j, ONE + i);
5517             if(message[0] == '+' || message[0] == '~') {
5518                 sprintf(message, "%c%c%c+\n",
5519                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5520                         AAA + j, ONE + i);
5521             }
5522             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5523                 message[1] = BOARD_RGHT   - 1 - j + '1';
5524                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5525             }
5526             SendToProgram(message, cps);
5527           }
5528         }
5529       }
5530     
5531       SendToProgram("c\n", cps);
5532       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5533         bp = &boards[moveNum][i][BOARD_LEFT];
5534         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5535           if (((int) *bp != (int) EmptySquare)
5536               && ((int) *bp >= (int) BlackPawn)) {
5537             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5538                     AAA + j, ONE + i);
5539             if(message[0] == '+' || message[0] == '~') {
5540                 sprintf(message, "%c%c%c+\n",
5541                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5542                         AAA + j, ONE + i);
5543             }
5544             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5545                 message[1] = BOARD_RGHT   - 1 - j + '1';
5546                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5547             }
5548             SendToProgram(message, cps);
5549           }
5550         }
5551       }
5552     
5553       SendToProgram(".\n", cps);
5554     }
5555     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5556 }
5557
5558 static int autoQueen; // [HGM] oneclick
5559
5560 int
5561 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5562 {
5563     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5564     /* [HGM] add Shogi promotions */
5565     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5566     ChessSquare piece;
5567     ChessMove moveType;
5568     Boolean premove;
5569
5570     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5571     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5572
5573     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5574       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5575         return FALSE;
5576
5577     piece = boards[currentMove][fromY][fromX];
5578     if(gameInfo.variant == VariantShogi) {
5579         promotionZoneSize = 3;
5580         highestPromotingPiece = (int)WhiteFerz;
5581     } else if(gameInfo.variant == VariantMakruk) {
5582         promotionZoneSize = 3;
5583     }
5584
5585     // next weed out all moves that do not touch the promotion zone at all
5586     if((int)piece >= BlackPawn) {
5587         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5588              return FALSE;
5589         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5590     } else {
5591         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5592            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5593     }
5594
5595     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5596
5597     // weed out mandatory Shogi promotions
5598     if(gameInfo.variant == VariantShogi) {
5599         if(piece >= BlackPawn) {
5600             if(toY == 0 && piece == BlackPawn ||
5601                toY == 0 && piece == BlackQueen ||
5602                toY <= 1 && piece == BlackKnight) {
5603                 *promoChoice = '+';
5604                 return FALSE;
5605             }
5606         } else {
5607             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5608                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5609                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5610                 *promoChoice = '+';
5611                 return FALSE;
5612             }
5613         }
5614     }
5615
5616     // weed out obviously illegal Pawn moves
5617     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5618         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5619         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5620         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5621         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5622         // note we are not allowed to test for valid (non-)capture, due to premove
5623     }
5624
5625     // we either have a choice what to promote to, or (in Shogi) whether to promote
5626     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5627         *promoChoice = PieceToChar(BlackFerz);  // no choice
5628         return FALSE;
5629     }
5630     if(autoQueen) { // predetermined
5631         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5632              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5633         else *promoChoice = PieceToChar(BlackQueen);
5634         return FALSE;
5635     }
5636
5637     // suppress promotion popup on illegal moves that are not premoves
5638     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5639               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5640     if(appData.testLegality && !premove) {
5641         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5642                         fromY, fromX, toY, toX, NULLCHAR);
5643         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5644            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5645             return FALSE;
5646     }
5647
5648     return TRUE;
5649 }
5650
5651 int
5652 InPalace(row, column)
5653      int row, column;
5654 {   /* [HGM] for Xiangqi */
5655     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5656          column < (BOARD_WIDTH + 4)/2 &&
5657          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5658     return FALSE;
5659 }
5660
5661 int
5662 PieceForSquare (x, y)
5663      int x;
5664      int y;
5665 {
5666   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5667      return -1;
5668   else
5669      return boards[currentMove][y][x];
5670 }
5671
5672 int
5673 OKToStartUserMove(x, y)
5674      int x, y;
5675 {
5676     ChessSquare from_piece;
5677     int white_piece;
5678
5679     if (matchMode) return FALSE;
5680     if (gameMode == EditPosition) return TRUE;
5681
5682     if (x >= 0 && y >= 0)
5683       from_piece = boards[currentMove][y][x];
5684     else
5685       from_piece = EmptySquare;
5686
5687     if (from_piece == EmptySquare) return FALSE;
5688
5689     white_piece = (int)from_piece >= (int)WhitePawn &&
5690       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5691
5692     switch (gameMode) {
5693       case PlayFromGameFile:
5694       case AnalyzeFile:
5695       case TwoMachinesPlay:
5696       case EndOfGame:
5697         return FALSE;
5698
5699       case IcsObserving:
5700       case IcsIdle:
5701         return FALSE;
5702
5703       case MachinePlaysWhite:
5704       case IcsPlayingBlack:
5705         if (appData.zippyPlay) return FALSE;
5706         if (white_piece) {
5707             DisplayMoveError(_("You are playing Black"));
5708             return FALSE;
5709         }
5710         break;
5711
5712       case MachinePlaysBlack:
5713       case IcsPlayingWhite:
5714         if (appData.zippyPlay) return FALSE;
5715         if (!white_piece) {
5716             DisplayMoveError(_("You are playing White"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case EditGame:
5722         if (!white_piece && WhiteOnMove(currentMove)) {
5723             DisplayMoveError(_("It is White's turn"));
5724             return FALSE;
5725         }           
5726         if (white_piece && !WhiteOnMove(currentMove)) {
5727             DisplayMoveError(_("It is Black's turn"));
5728             return FALSE;
5729         }           
5730         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5731             /* Editing correspondence game history */
5732             /* Could disallow this or prompt for confirmation */
5733             cmailOldMove = -1;
5734         }
5735         break;
5736
5737       case BeginningOfGame:
5738         if (appData.icsActive) return FALSE;
5739         if (!appData.noChessProgram) {
5740             if (!white_piece) {
5741                 DisplayMoveError(_("You are playing White"));
5742                 return FALSE;
5743             }
5744         }
5745         break;
5746         
5747       case Training:
5748         if (!white_piece && WhiteOnMove(currentMove)) {
5749             DisplayMoveError(_("It is White's turn"));
5750             return FALSE;
5751         }           
5752         if (white_piece && !WhiteOnMove(currentMove)) {
5753             DisplayMoveError(_("It is Black's turn"));
5754             return FALSE;
5755         }           
5756         break;
5757
5758       default:
5759       case IcsExamining:
5760         break;
5761     }
5762     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5763         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5764         && gameMode != AnalyzeFile && gameMode != Training) {
5765         DisplayMoveError(_("Displayed position is not current"));
5766         return FALSE;
5767     }
5768     return TRUE;
5769 }
5770
5771 Boolean
5772 OnlyMove(int *x, int *y, Boolean captures) {
5773     DisambiguateClosure cl;
5774     if (appData.zippyPlay) return FALSE;
5775     switch(gameMode) {
5776       case MachinePlaysBlack:
5777       case IcsPlayingWhite:
5778       case BeginningOfGame:
5779         if(!WhiteOnMove(currentMove)) return FALSE;
5780         break;
5781       case MachinePlaysWhite:
5782       case IcsPlayingBlack:
5783         if(WhiteOnMove(currentMove)) return FALSE;
5784         break;
5785       default:
5786         return FALSE;
5787     }
5788     cl.pieceIn = EmptySquare; 
5789     cl.rfIn = *y;
5790     cl.ffIn = *x;
5791     cl.rtIn = -1;
5792     cl.ftIn = -1;
5793     cl.promoCharIn = NULLCHAR;
5794     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5795     if( cl.kind == NormalMove ||
5796         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5797         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5798         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5799         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5800       fromX = cl.ff;
5801       fromY = cl.rf;
5802       *x = cl.ft;
5803       *y = cl.rt;
5804       return TRUE;
5805     }
5806     if(cl.kind != ImpossibleMove) return FALSE;
5807     cl.pieceIn = EmptySquare;
5808     cl.rfIn = -1;
5809     cl.ffIn = -1;
5810     cl.rtIn = *y;
5811     cl.ftIn = *x;
5812     cl.promoCharIn = NULLCHAR;
5813     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5814     if( cl.kind == NormalMove ||
5815         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5816         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5817         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5818         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5819       fromX = cl.ff;
5820       fromY = cl.rf;
5821       *x = cl.ft;
5822       *y = cl.rt;
5823       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5824       return TRUE;
5825     }
5826     return FALSE;
5827 }
5828
5829 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5830 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5831 int lastLoadGameUseList = FALSE;
5832 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5833 ChessMove lastLoadGameStart = (ChessMove) 0;
5834
5835 ChessMove
5836 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5837      int fromX, fromY, toX, toY;
5838      int promoChar;
5839      Boolean captureOwn;
5840 {
5841     ChessMove moveType;
5842     ChessSquare pdown, pup;
5843
5844     /* Check if the user is playing in turn.  This is complicated because we
5845        let the user "pick up" a piece before it is his turn.  So the piece he
5846        tried to pick up may have been captured by the time he puts it down!
5847        Therefore we use the color the user is supposed to be playing in this
5848        test, not the color of the piece that is currently on the starting
5849        square---except in EditGame mode, where the user is playing both
5850        sides; fortunately there the capture race can't happen.  (It can
5851        now happen in IcsExamining mode, but that's just too bad.  The user
5852        will get a somewhat confusing message in that case.)
5853        */
5854
5855     switch (gameMode) {
5856       case PlayFromGameFile:
5857       case AnalyzeFile:
5858       case TwoMachinesPlay:
5859       case EndOfGame:
5860       case IcsObserving:
5861       case IcsIdle:
5862         /* We switched into a game mode where moves are not accepted,
5863            perhaps while the mouse button was down. */
5864         return ImpossibleMove;
5865
5866       case MachinePlaysWhite:
5867         /* User is moving for Black */
5868         if (WhiteOnMove(currentMove)) {
5869             DisplayMoveError(_("It is White's turn"));
5870             return ImpossibleMove;
5871         }
5872         break;
5873
5874       case MachinePlaysBlack:
5875         /* User is moving for White */
5876         if (!WhiteOnMove(currentMove)) {
5877             DisplayMoveError(_("It is Black's turn"));
5878             return ImpossibleMove;
5879         }
5880         break;
5881
5882       case EditGame:
5883       case IcsExamining:
5884       case BeginningOfGame:
5885       case AnalyzeMode:
5886       case Training:
5887         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5888             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5889             /* User is moving for Black */
5890             if (WhiteOnMove(currentMove)) {
5891                 DisplayMoveError(_("It is White's turn"));
5892                 return ImpossibleMove;
5893             }
5894         } else {
5895             /* User is moving for White */
5896             if (!WhiteOnMove(currentMove)) {
5897                 DisplayMoveError(_("It is Black's turn"));
5898                 return ImpossibleMove;
5899             }
5900         }
5901         break;
5902
5903       case IcsPlayingBlack:
5904         /* User is moving for Black */
5905         if (WhiteOnMove(currentMove)) {
5906             if (!appData.premove) {
5907                 DisplayMoveError(_("It is White's turn"));
5908             } else if (toX >= 0 && toY >= 0) {
5909                 premoveToX = toX;
5910                 premoveToY = toY;
5911                 premoveFromX = fromX;
5912                 premoveFromY = fromY;
5913                 premovePromoChar = promoChar;
5914                 gotPremove = 1;
5915                 if (appData.debugMode) 
5916                     fprintf(debugFP, "Got premove: fromX %d,"
5917                             "fromY %d, toX %d, toY %d\n",
5918                             fromX, fromY, toX, toY);
5919             }
5920             return ImpossibleMove;
5921         }
5922         break;
5923
5924       case IcsPlayingWhite:
5925         /* User is moving for White */
5926         if (!WhiteOnMove(currentMove)) {
5927             if (!appData.premove) {
5928                 DisplayMoveError(_("It is Black's turn"));
5929             } else if (toX >= 0 && toY >= 0) {
5930                 premoveToX = toX;
5931                 premoveToY = toY;
5932                 premoveFromX = fromX;
5933                 premoveFromY = fromY;
5934                 premovePromoChar = promoChar;
5935                 gotPremove = 1;
5936                 if (appData.debugMode) 
5937                     fprintf(debugFP, "Got premove: fromX %d,"
5938                             "fromY %d, toX %d, toY %d\n",
5939                             fromX, fromY, toX, toY);
5940             }
5941             return ImpossibleMove;
5942         }
5943         break;
5944
5945       default:
5946         break;
5947
5948       case EditPosition:
5949         /* EditPosition, empty square, or different color piece;
5950            click-click move is possible */
5951         if (toX == -2 || toY == -2) {
5952             boards[0][fromY][fromX] = EmptySquare;
5953             return AmbiguousMove;
5954         } else if (toX >= 0 && toY >= 0) {
5955             boards[0][toY][toX] = boards[0][fromY][fromX];
5956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5957                 if(boards[0][fromY][0] != EmptySquare) {
5958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5960                 }
5961             } else
5962             if(fromX == BOARD_RGHT+1) {
5963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5966                 }
5967             } else
5968             boards[0][fromY][fromX] = EmptySquare;
5969             return AmbiguousMove;
5970         }
5971         return ImpossibleMove;
5972     }
5973
5974     if(toX < 0 || toY < 0) return ImpossibleMove;
5975     pdown = boards[currentMove][fromY][fromX];
5976     pup = boards[currentMove][toY][toX];
5977
5978     /* [HGM] If move started in holdings, it means a drop */
5979     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5980          if( pup != EmptySquare ) return ImpossibleMove;
5981          if(appData.testLegality) {
5982              /* it would be more logical if LegalityTest() also figured out
5983               * which drops are legal. For now we forbid pawns on back rank.
5984               * Shogi is on its own here...
5985               */
5986              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5987                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5988                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5989          }
5990          return WhiteDrop; /* Not needed to specify white or black yet */
5991     }
5992
5993     /* [HGM] always test for legality, to get promotion info */
5994     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5995                                          fromY, fromX, toY, toX, promoChar);
5996     /* [HGM] but possibly ignore an IllegalMove result */
5997     if (appData.testLegality) {
5998         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5999             DisplayMoveError(_("Illegal move"));
6000             return ImpossibleMove;
6001         }
6002     }
6003
6004     return moveType;
6005     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6006        function is made into one that returns an OK move type if FinishMove
6007        should be called. This to give the calling driver routine the
6008        opportunity to finish the userMove input with a promotion popup,
6009        without bothering the user with this for invalid or illegal moves */
6010
6011 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6012 }
6013
6014 /* Common tail of UserMoveEvent and DropMenuEvent */
6015 int
6016 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6017      ChessMove moveType;
6018      int fromX, fromY, toX, toY;
6019      /*char*/int promoChar;
6020 {
6021     char *bookHit = 0;
6022
6023     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6024         // [HGM] superchess: suppress promotions to non-available piece
6025         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6026         if(WhiteOnMove(currentMove)) {
6027             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6028         } else {
6029             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6030         }
6031     }
6032
6033     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6034        move type in caller when we know the move is a legal promotion */
6035     if(moveType == NormalMove && promoChar)
6036         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6037
6038     /* [HGM] convert drag-and-drop piece drops to standard form */
6039     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6040          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6041            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6042                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6043            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6044            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6045            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6046            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6047          fromY = DROP_RANK;
6048     }
6049
6050     /* [HGM] <popupFix> The following if has been moved here from
6051        UserMoveEvent(). Because it seemed to belong here (why not allow
6052        piece drops in training games?), and because it can only be
6053        performed after it is known to what we promote. */
6054     if (gameMode == Training) {
6055       /* compare the move played on the board to the next move in the
6056        * game. If they match, display the move and the opponent's response. 
6057        * If they don't match, display an error message.
6058        */
6059       int saveAnimate;
6060       Board testBoard;
6061       CopyBoard(testBoard, boards[currentMove]);
6062       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6063
6064       if (CompareBoards(testBoard, boards[currentMove+1])) {
6065         ForwardInner(currentMove+1);
6066
6067         /* Autoplay the opponent's response.
6068          * if appData.animate was TRUE when Training mode was entered,
6069          * the response will be animated.
6070          */
6071         saveAnimate = appData.animate;
6072         appData.animate = animateTraining;
6073         ForwardInner(currentMove+1);
6074         appData.animate = saveAnimate;
6075
6076         /* check for the end of the game */
6077         if (currentMove >= forwardMostMove) {
6078           gameMode = PlayFromGameFile;
6079           ModeHighlight();
6080           SetTrainingModeOff();
6081           DisplayInformation(_("End of game"));
6082         }
6083       } else {
6084         DisplayError(_("Incorrect move"), 0);
6085       }
6086       return 1;
6087     }
6088
6089   /* Ok, now we know that the move is good, so we can kill
6090      the previous line in Analysis Mode */
6091   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6092                                 && currentMove < forwardMostMove) {
6093     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6094   }
6095
6096   /* If we need the chess program but it's dead, restart it */
6097   ResurrectChessProgram();
6098
6099   /* A user move restarts a paused game*/
6100   if (pausing)
6101     PauseEvent();
6102
6103   thinkOutput[0] = NULLCHAR;
6104
6105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6106
6107   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6108
6109   if (gameMode == BeginningOfGame) {
6110     if (appData.noChessProgram) {
6111       gameMode = EditGame;
6112       SetGameInfo();
6113     } else {
6114       char buf[MSG_SIZ];
6115       gameMode = MachinePlaysBlack;
6116       StartClocks();
6117       SetGameInfo();
6118       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6119       DisplayTitle(buf);
6120       if (first.sendName) {
6121         sprintf(buf, "name %s\n", gameInfo.white);
6122         SendToProgram(buf, &first);
6123       }
6124       StartClocks();
6125     }
6126     ModeHighlight();
6127   }
6128
6129   /* Relay move to ICS or chess engine */
6130   if (appData.icsActive) {
6131     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6132         gameMode == IcsExamining) {
6133       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6134         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6135         SendToICS("draw ");
6136         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6137       }
6138       // also send plain move, in case ICS does not understand atomic claims
6139       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6140       ics_user_moved = 1;
6141     }
6142   } else {
6143     if (first.sendTime && (gameMode == BeginningOfGame ||
6144                            gameMode == MachinePlaysWhite ||
6145                            gameMode == MachinePlaysBlack)) {
6146       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6147     }
6148     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6149          // [HGM] book: if program might be playing, let it use book
6150         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6151         first.maybeThinking = TRUE;
6152     } else SendMoveToProgram(forwardMostMove-1, &first);
6153     if (currentMove == cmailOldMove + 1) {
6154       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6155     }
6156   }
6157
6158   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159
6160   switch (gameMode) {
6161   case EditGame:
6162     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6163     case MT_NONE:
6164     case MT_CHECK:
6165       break;
6166     case MT_CHECKMATE:
6167     case MT_STAINMATE:
6168       if (WhiteOnMove(currentMove)) {
6169         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6170       } else {
6171         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6172       }
6173       break;
6174     case MT_STALEMATE:
6175       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6176       break;
6177     }
6178     break;
6179     
6180   case MachinePlaysBlack:
6181   case MachinePlaysWhite:
6182     /* disable certain menu options while machine is thinking */
6183     SetMachineThinkingEnables();
6184     break;
6185
6186   default:
6187     break;
6188   }
6189
6190   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6191         
6192   if(bookHit) { // [HGM] book: simulate book reply
6193         static char bookMove[MSG_SIZ]; // a bit generous?
6194
6195         programStats.nodes = programStats.depth = programStats.time = 
6196         programStats.score = programStats.got_only_move = 0;
6197         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6198
6199         strcpy(bookMove, "move ");
6200         strcat(bookMove, bookHit);
6201         HandleMachineMove(bookMove, &first);
6202   }
6203   return 1;
6204 }
6205
6206 void
6207 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6208      int fromX, fromY, toX, toY;
6209      int promoChar;
6210 {
6211     /* [HGM] This routine was added to allow calling of its two logical
6212        parts from other modules in the old way. Before, UserMoveEvent()
6213        automatically called FinishMove() if the move was OK, and returned
6214        otherwise. I separated the two, in order to make it possible to
6215        slip a promotion popup in between. But that it always needs two
6216        calls, to the first part, (now called UserMoveTest() ), and to
6217        FinishMove if the first part succeeded. Calls that do not need
6218        to do anything in between, can call this routine the old way. 
6219     */
6220     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6221 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6222     if(moveType == AmbiguousMove)
6223         DrawPosition(FALSE, boards[currentMove]);
6224     else if(moveType != ImpossibleMove && moveType != Comment)
6225         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6226 }
6227
6228 void
6229 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6230      Board board;
6231      int flags;
6232      ChessMove kind;
6233      int rf, ff, rt, ft;
6234      VOIDSTAR closure;
6235 {
6236     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6237     Markers *m = (Markers *) closure;
6238     if(rf == fromY && ff == fromX)
6239         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6240                          || kind == WhiteCapturesEnPassant
6241                          || kind == BlackCapturesEnPassant);
6242     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6243 }
6244
6245 void
6246 MarkTargetSquares(int clear)
6247 {
6248   int x, y;
6249   if(!appData.markers || !appData.highlightDragging || 
6250      !appData.testLegality || gameMode == EditPosition) return;
6251   if(clear) {
6252     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6253   } else {
6254     int capt = 0;
6255     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6256     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6257       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6258       if(capt)
6259       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6260     }
6261   }
6262   DrawPosition(TRUE, NULL);
6263 }
6264
6265 void LeftClick(ClickType clickType, int xPix, int yPix)
6266 {
6267     int x, y;
6268     Boolean saveAnimate;
6269     static int second = 0, promotionChoice = 0, dragging = 0;
6270     char promoChoice = NULLCHAR;
6271
6272     if(appData.seekGraph && appData.icsActive && loggedOn &&
6273         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6274         SeekGraphClick(clickType, xPix, yPix, 0);
6275         return;
6276     }
6277
6278     if (clickType == Press) ErrorPopDown();
6279     MarkTargetSquares(1);
6280
6281     x = EventToSquare(xPix, BOARD_WIDTH);
6282     y = EventToSquare(yPix, BOARD_HEIGHT);
6283     if (!flipView && y >= 0) {
6284         y = BOARD_HEIGHT - 1 - y;
6285     }
6286     if (flipView && x >= 0) {
6287         x = BOARD_WIDTH - 1 - x;
6288     }
6289
6290     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6291         if(clickType == Release) return; // ignore upclick of click-click destination
6292         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6293         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6294         if(gameInfo.holdingsWidth && 
6295                 (WhiteOnMove(currentMove) 
6296                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6297                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6298             // click in right holdings, for determining promotion piece
6299             ChessSquare p = boards[currentMove][y][x];
6300             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6301             if(p != EmptySquare) {
6302                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6303                 fromX = fromY = -1;
6304                 return;
6305             }
6306         }
6307         DrawPosition(FALSE, boards[currentMove]);
6308         return;
6309     }
6310
6311     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6312     if(clickType == Press
6313             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6314               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6315               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6316         return;
6317
6318     autoQueen = appData.alwaysPromoteToQueen;
6319
6320     if (fromX == -1) {
6321       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6322         if (clickType == Press) {
6323             /* First square */
6324             if (OKToStartUserMove(x, y)) {
6325                 fromX = x;
6326                 fromY = y;
6327                 second = 0;
6328                 MarkTargetSquares(0);
6329                 DragPieceBegin(xPix, yPix); dragging = 1;
6330                 if (appData.highlightDragging) {
6331                     SetHighlights(x, y, -1, -1);
6332                 }
6333             }
6334         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6335             DragPieceEnd(xPix, yPix); dragging = 0;
6336             DrawPosition(FALSE, NULL);
6337         }
6338         return;
6339       }
6340     }
6341
6342     /* fromX != -1 */
6343     if (clickType == Press && gameMode != EditPosition) {
6344         ChessSquare fromP;
6345         ChessSquare toP;
6346         int frc;
6347
6348         // ignore off-board to clicks
6349         if(y < 0 || x < 0) return;
6350
6351         /* Check if clicking again on the same color piece */
6352         fromP = boards[currentMove][fromY][fromX];
6353         toP = boards[currentMove][y][x];
6354         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6355         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6356              WhitePawn <= toP && toP <= WhiteKing &&
6357              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6358              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6359             (BlackPawn <= fromP && fromP <= BlackKing && 
6360              BlackPawn <= toP && toP <= BlackKing &&
6361              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6362              !(fromP == BlackKing && toP == BlackRook && frc))) {
6363             /* Clicked again on same color piece -- changed his mind */
6364             second = (x == fromX && y == fromY);
6365            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6366             if (appData.highlightDragging) {
6367                 SetHighlights(x, y, -1, -1);
6368             } else {
6369                 ClearHighlights();
6370             }
6371             if (OKToStartUserMove(x, y)) {
6372                 fromX = x;
6373                 fromY = y; dragging = 1;
6374                 MarkTargetSquares(0);
6375                 DragPieceBegin(xPix, yPix);
6376             }
6377             return;
6378            }
6379         }
6380         // ignore clicks on holdings
6381         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6382     }
6383
6384     if (clickType == Release && x == fromX && y == fromY) {
6385         DragPieceEnd(xPix, yPix); dragging = 0;
6386         if (appData.animateDragging) {
6387             /* Undo animation damage if any */
6388             DrawPosition(FALSE, NULL);
6389         }
6390         if (second) {
6391             /* Second up/down in same square; just abort move */
6392             second = 0;
6393             fromX = fromY = -1;
6394             ClearHighlights();
6395             gotPremove = 0;
6396             ClearPremoveHighlights();
6397         } else {
6398             /* First upclick in same square; start click-click mode */
6399             SetHighlights(x, y, -1, -1);
6400         }
6401         return;
6402     }
6403
6404     /* we now have a different from- and (possibly off-board) to-square */
6405     /* Completed move */
6406     toX = x;
6407     toY = y;
6408     saveAnimate = appData.animate;
6409     if (clickType == Press) {
6410         /* Finish clickclick move */
6411         if (appData.animate || appData.highlightLastMove) {
6412             SetHighlights(fromX, fromY, toX, toY);
6413         } else {
6414             ClearHighlights();
6415         }
6416     } else {
6417         /* Finish drag move */
6418         if (appData.highlightLastMove) {
6419             SetHighlights(fromX, fromY, toX, toY);
6420         } else {
6421             ClearHighlights();
6422         }
6423         DragPieceEnd(xPix, yPix); dragging = 0;
6424         /* Don't animate move and drag both */
6425         appData.animate = FALSE;
6426     }
6427
6428     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6429     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6430         ChessSquare piece = boards[currentMove][fromY][fromX];
6431         if(gameMode == EditPosition && piece != EmptySquare &&
6432            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6433             int n;
6434              
6435             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6436                 n = PieceToNumber(piece - (int)BlackPawn);
6437                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6438                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6439                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6440             } else
6441             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6442                 n = PieceToNumber(piece);
6443                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6444                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6445                 boards[currentMove][n][BOARD_WIDTH-2]++;
6446             }
6447             boards[currentMove][fromY][fromX] = EmptySquare;
6448         }
6449         ClearHighlights();
6450         fromX = fromY = -1;
6451         DrawPosition(TRUE, boards[currentMove]);
6452         return;
6453     }
6454
6455     // off-board moves should not be highlighted
6456     if(x < 0 || x < 0) ClearHighlights();
6457
6458     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6459         SetHighlights(fromX, fromY, toX, toY);
6460         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6461             // [HGM] super: promotion to captured piece selected from holdings
6462             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6463             promotionChoice = TRUE;
6464             // kludge follows to temporarily execute move on display, without promoting yet
6465             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6466             boards[currentMove][toY][toX] = p;
6467             DrawPosition(FALSE, boards[currentMove]);
6468             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6469             boards[currentMove][toY][toX] = q;
6470             DisplayMessage("Click in holdings to choose piece", "");
6471             return;
6472         }
6473         PromotionPopUp();
6474     } else {
6475         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6476         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6477         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6478         fromX = fromY = -1;
6479     }
6480     appData.animate = saveAnimate;
6481     if (appData.animate || appData.animateDragging) {
6482         /* Undo animation damage if needed */
6483         DrawPosition(FALSE, NULL);
6484     }
6485 }
6486
6487 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6488 {   // front-end-free part taken out of PieceMenuPopup
6489     int whichMenu; int xSqr, ySqr;
6490
6491     if(seekGraphUp) { // [HGM] seekgraph
6492         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6493         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6494         return -2;
6495     }
6496
6497     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6498          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6499         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6500         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6501         if(action == Press)   {
6502             originalFlip = flipView;
6503             flipView = !flipView; // temporarily flip board to see game from partners perspective
6504             DrawPosition(TRUE, partnerBoard);
6505             DisplayMessage(partnerStatus, "");
6506             partnerUp = TRUE;
6507         } else if(action == Release) {
6508             flipView = originalFlip;
6509             DrawPosition(TRUE, boards[currentMove]);
6510             partnerUp = FALSE;
6511         }
6512         return -2;
6513     }
6514
6515     xSqr = EventToSquare(x, BOARD_WIDTH);
6516     ySqr = EventToSquare(y, BOARD_HEIGHT);
6517     if (action == Release) UnLoadPV(); // [HGM] pv
6518     if (action != Press) return -2; // return code to be ignored
6519     switch (gameMode) {
6520       case IcsExamining:
6521         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6522       case EditPosition:
6523         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6524         if (xSqr < 0 || ySqr < 0) return -1;\r
6525         whichMenu = 0; // edit-position menu
6526         break;
6527       case IcsObserving:
6528         if(!appData.icsEngineAnalyze) return -1;
6529       case IcsPlayingWhite:
6530       case IcsPlayingBlack:
6531         if(!appData.zippyPlay) goto noZip;
6532       case AnalyzeMode:
6533       case AnalyzeFile:
6534       case MachinePlaysWhite:
6535       case MachinePlaysBlack:
6536       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6537         if (!appData.dropMenu) {
6538           LoadPV(x, y);
6539           return 2; // flag front-end to grab mouse events
6540         }
6541         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6542            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6543       case EditGame:
6544       noZip:
6545         if (xSqr < 0 || ySqr < 0) return -1;
6546         if (!appData.dropMenu || appData.testLegality &&
6547             gameInfo.variant != VariantBughouse &&
6548             gameInfo.variant != VariantCrazyhouse) return -1;
6549         whichMenu = 1; // drop menu
6550         break;
6551       default:
6552         return -1;
6553     }
6554
6555     if (((*fromX = xSqr) < 0) ||
6556         ((*fromY = ySqr) < 0)) {
6557         *fromX = *fromY = -1;
6558         return -1;
6559     }
6560     if (flipView)
6561       *fromX = BOARD_WIDTH - 1 - *fromX;
6562     else
6563       *fromY = BOARD_HEIGHT - 1 - *fromY;
6564
6565     return whichMenu;
6566 }
6567
6568 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6569 {
6570 //    char * hint = lastHint;
6571     FrontEndProgramStats stats;
6572
6573     stats.which = cps == &first ? 0 : 1;
6574     stats.depth = cpstats->depth;
6575     stats.nodes = cpstats->nodes;
6576     stats.score = cpstats->score;
6577     stats.time = cpstats->time;
6578     stats.pv = cpstats->movelist;
6579     stats.hint = lastHint;
6580     stats.an_move_index = 0;
6581     stats.an_move_count = 0;
6582
6583     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6584         stats.hint = cpstats->move_name;
6585         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6586         stats.an_move_count = cpstats->nr_moves;
6587     }
6588
6589     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6590
6591     SetProgramStats( &stats );
6592 }
6593
6594 void
6595 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6596 {       // count all piece types
6597         int p, f, r;
6598         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6599         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6600         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6601                 p = board[r][f];
6602                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6603                 if(p == BlackPawn && r == 0) (*bStale)++; else pCnt[p]++; // count last-Rank Pawns (XQ) separately
6604                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6605                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6606                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6607                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6608         }
6609 }
6610
6611 int
6612 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6613 {
6614         VariantClass v = gameInfo.variant;
6615
6616         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6617         if(v == VariantShatranj) return TRUE; // always winnable through baring
6618         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6619         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6620
6621         if(v == VariantXiangqi) {
6622                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6623
6624                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6625                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6626                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6627                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6628                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6629                 if(stale) // we have at least one last-rank P plus perhaps C
6630                     return majors // KPKX
6631                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6632                 else // KCA*E*
6633                     return pCnt[WhiteFerz+side] // KCAK
6634                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6635                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6636                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6637
6638         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6639                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6640                 
6641                 if(nMine == 1) return FALSE; // bare King
6642                 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
6643                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6644                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6645                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6646                 if(pCnt[WhiteKnight+side])
6647                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6648                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6649                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6650                 if(nBishops)
6651                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6652                 if(pCnt[WhiteAlfil+side])
6653                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6654                 if(pCnt[WhiteWazir+side])
6655                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6656         }
6657
6658         return TRUE;
6659 }
6660
6661 int
6662 Adjudicate(ChessProgramState *cps)
6663 {       // [HGM] some adjudications useful with buggy engines
6664         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6665         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6666         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6667         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6668         int k, count = 0; static int bare = 1;
6669         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6670         Boolean canAdjudicate = !appData.icsActive;
6671
6672         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6673         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6674             if( appData.testLegality )
6675             {   /* [HGM] Some more adjudications for obstinate engines */
6676                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6677                 static int moveCount = 6;
6678                 ChessMove result;
6679                 char *reason = NULL;
6680
6681                 /* Count what is on board. */
6682                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6683
6684                 /* Some material-based adjudications that have to be made before stalemate test */
6685                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6686                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6687                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6688                      if(canAdjudicate && appData.checkMates) {
6689                          if(engineOpponent)
6690                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6691                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6692                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6693                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6694                          return 1;
6695                      }
6696                 }
6697
6698                 /* Bare King in Shatranj (loses) or Losers (wins) */
6699                 if( nrW == 1 || nrB == 1) {
6700                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6701                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6702                      if(canAdjudicate && appData.checkMates) {
6703                          if(engineOpponent)
6704                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6705                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6707                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6708                          return 1;
6709                      }
6710                   } else
6711                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6712                   {    /* bare King */
6713                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6714                         if(canAdjudicate && appData.checkMates) {
6715                             /* but only adjudicate if adjudication enabled */
6716                             if(engineOpponent)
6717                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6718                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6719                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6720                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6721                             return 1;
6722                         }
6723                   }
6724                 } else bare = 1;
6725
6726
6727             // don't wait for engine to announce game end if we can judge ourselves
6728             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6729               case MT_CHECK:
6730                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6731                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6732                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6733                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6734                             checkCnt++;
6735                         if(checkCnt >= 2) {
6736                             reason = "Xboard adjudication: 3rd check";
6737                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6738                             break;
6739                         }
6740                     }
6741                 }
6742               case MT_NONE:
6743               default:
6744                 break;
6745               case MT_STALEMATE:
6746               case MT_STAINMATE:
6747                 reason = "Xboard adjudication: Stalemate";
6748                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6749                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6750                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6751                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6752                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6753                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6754                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6755                                                                         EP_CHECKMATE : EP_WINS);
6756                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6757                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6758                 }
6759                 break;
6760               case MT_CHECKMATE:
6761                 reason = "Xboard adjudication: Checkmate";
6762                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6763                 break;
6764             }
6765
6766                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6767                     case EP_STALEMATE:
6768                         result = GameIsDrawn; break;
6769                     case EP_CHECKMATE:
6770                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6771                     case EP_WINS:
6772                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6773                     default:
6774                         result = (ChessMove) 0;
6775                 }
6776                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6777                     if(engineOpponent)
6778                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6779                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6780                     GameEnds( result, reason, GE_XBOARD );
6781                     return 1;
6782                 }
6783
6784                 /* Next absolutely insufficient mating material. */
6785                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6786                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6787                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6788
6789                      /* always flag draws, for judging claims */
6790                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6791
6792                      if(canAdjudicate && appData.materialDraws) {
6793                          /* but only adjudicate them if adjudication enabled */
6794                          if(engineOpponent) {
6795                            SendToProgram("force\n", engineOpponent); // suppress reply
6796                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6797                          }
6798                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6799                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6800                          return 1;
6801                      }
6802                 }
6803
6804                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6805                 if(nrW + nrB == 4 && 
6806                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6807                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6808                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6809                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6810                   ) ) {
6811                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6812                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6813                           if(engineOpponent) {
6814                             SendToProgram("force\n", engineOpponent); // suppress reply
6815                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6816                           }
6817                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6818                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6819                           return 1;
6820                      }
6821                 } else moveCount = 6;
6822             }
6823         }
6824           
6825         if (appData.debugMode) { int i;
6826             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6827                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6828                     appData.drawRepeats);
6829             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6830               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6831             
6832         }
6833
6834         // Repetition draws and 50-move rule can be applied independently of legality testing
6835
6836                 /* Check for rep-draws */
6837                 count = 0;
6838                 for(k = forwardMostMove-2;
6839                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6840                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6841                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6842                     k-=2)
6843                 {   int rights=0;
6844                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6845                         /* compare castling rights */
6846                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6847                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6848                                 rights++; /* King lost rights, while rook still had them */
6849                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6850                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6851                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6852                                    rights++; /* but at least one rook lost them */
6853                         }
6854                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6855                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6856                                 rights++; 
6857                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6858                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6859                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6860                                    rights++;
6861                         }
6862                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6863                             && appData.drawRepeats > 1) {
6864                              /* adjudicate after user-specified nr of repeats */
6865                              int result = GameIsDrawn;
6866                              char *details = "XBoard adjudication: repetition draw";
6867                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6868                                 // [HGM] xiangqi: check for forbidden perpetuals
6869                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6870                                 for(m=forwardMostMove; m>k; m-=2) {
6871                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6872                                         ourPerpetual = 0; // the current mover did not always check
6873                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6874                                         hisPerpetual = 0; // the opponent did not always check
6875                                 }
6876                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6877                                                                         ourPerpetual, hisPerpetual);
6878                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6879                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6880                                     details = "Xboard adjudication: perpetual checking";
6881                                 } else
6882                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6883                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6884                                 } else
6885                                 // Now check for perpetual chases
6886                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6887                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6888                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6889                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6890                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6891                                         details = "Xboard adjudication: perpetual chasing";
6892                                     } else
6893                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6894                                         break; // Abort repetition-checking loop.
6895                                 }
6896                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6897                              }
6898                              if(engineOpponent) {
6899                                SendToProgram("force\n", engineOpponent); // suppress reply
6900                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6901                              }
6902                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903                              GameEnds( result, details, GE_XBOARD );
6904                              return 1;
6905                         }
6906                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6907                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6908                     }
6909                 }
6910
6911                 /* Now we test for 50-move draws. Determine ply count */
6912                 count = forwardMostMove;
6913                 /* look for last irreversble move */
6914                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6915                     count--;
6916                 /* if we hit starting position, add initial plies */
6917                 if( count == backwardMostMove )
6918                     count -= initialRulePlies;
6919                 count = forwardMostMove - count; 
6920                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6921                         // adjust reversible move counter for checks in Xiangqi
6922                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6923                         if(i < backwardMostMove) i = backwardMostMove;
6924                         while(i <= forwardMostMove) {
6925                                 lastCheck = inCheck; // check evasion does not count
6926                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6927                                 if(inCheck || lastCheck) count--; // check does not count
6928                                 i++;
6929                         }
6930                 }
6931                 if( count >= 100)
6932                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6933                          /* this is used to judge if draw claims are legal */
6934                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6935                          if(engineOpponent) {
6936                            SendToProgram("force\n", engineOpponent); // suppress reply
6937                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6938                          }
6939                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6940                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6941                          return 1;
6942                 }
6943
6944                 /* if draw offer is pending, treat it as a draw claim
6945                  * when draw condition present, to allow engines a way to
6946                  * claim draws before making their move to avoid a race
6947                  * condition occurring after their move
6948                  */
6949                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6950                          char *p = NULL;
6951                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6952                              p = "Draw claim: 50-move rule";
6953                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6954                              p = "Draw claim: 3-fold repetition";
6955                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6956                              p = "Draw claim: insufficient mating material";
6957                          if( p != NULL && canAdjudicate) {
6958                              if(engineOpponent) {
6959                                SendToProgram("force\n", engineOpponent); // suppress reply
6960                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6961                              }
6962                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6963                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6964                              return 1;
6965                          }
6966                 }
6967
6968                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6969                     if(engineOpponent) {
6970                       SendToProgram("force\n", engineOpponent); // suppress reply
6971                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6972                     }
6973                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6974                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6975                     return 1;
6976                 }
6977         return 0;
6978 }
6979
6980 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6981 {   // [HGM] book: this routine intercepts moves to simulate book replies
6982     char *bookHit = NULL;
6983
6984     //first determine if the incoming move brings opponent into his book
6985     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6986         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6987     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6988     if(bookHit != NULL && !cps->bookSuspend) {
6989         // make sure opponent is not going to reply after receiving move to book position
6990         SendToProgram("force\n", cps);
6991         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6992     }
6993     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6994     // now arrange restart after book miss
6995     if(bookHit) {
6996         // after a book hit we never send 'go', and the code after the call to this routine
6997         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6998         char buf[MSG_SIZ];
6999         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7000         SendToProgram(buf, cps);
7001         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7002     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7003         SendToProgram("go\n", cps);
7004         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7005     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7006         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7007             SendToProgram("go\n", cps); 
7008         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7009     }
7010     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7011 }
7012
7013 char *savedMessage;
7014 ChessProgramState *savedState;
7015 void DeferredBookMove(void)
7016 {
7017         if(savedState->lastPing != savedState->lastPong)
7018                     ScheduleDelayedEvent(DeferredBookMove, 10);
7019         else
7020         HandleMachineMove(savedMessage, savedState);
7021 }
7022
7023 void
7024 HandleMachineMove(message, cps)
7025      char *message;
7026      ChessProgramState *cps;
7027 {
7028     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7029     char realname[MSG_SIZ];
7030     int fromX, fromY, toX, toY;
7031     ChessMove moveType;
7032     char promoChar;
7033     char *p;
7034     int machineWhite;
7035     char *bookHit;
7036
7037     cps->userError = 0;
7038
7039 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7040     /*
7041      * Kludge to ignore BEL characters
7042      */
7043     while (*message == '\007') message++;
7044
7045     /*
7046      * [HGM] engine debug message: ignore lines starting with '#' character
7047      */
7048     if(cps->debug && *message == '#') return;
7049
7050     /*
7051      * Look for book output
7052      */
7053     if (cps == &first && bookRequested) {
7054         if (message[0] == '\t' || message[0] == ' ') {
7055             /* Part of the book output is here; append it */
7056             strcat(bookOutput, message);
7057             strcat(bookOutput, "  \n");
7058             return;
7059         } else if (bookOutput[0] != NULLCHAR) {
7060             /* All of book output has arrived; display it */
7061             char *p = bookOutput;
7062             while (*p != NULLCHAR) {
7063                 if (*p == '\t') *p = ' ';
7064                 p++;
7065             }
7066             DisplayInformation(bookOutput);
7067             bookRequested = FALSE;
7068             /* Fall through to parse the current output */
7069         }
7070     }
7071
7072     /*
7073      * Look for machine move.
7074      */
7075     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7076         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7077     {
7078         /* This method is only useful on engines that support ping */
7079         if (cps->lastPing != cps->lastPong) {
7080           if (gameMode == BeginningOfGame) {
7081             /* Extra move from before last new; ignore */
7082             if (appData.debugMode) {
7083                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7084             }
7085           } else {
7086             if (appData.debugMode) {
7087                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7088                         cps->which, gameMode);
7089             }
7090
7091             SendToProgram("undo\n", cps);
7092           }
7093           return;
7094         }
7095
7096         switch (gameMode) {
7097           case BeginningOfGame:
7098             /* Extra move from before last reset; ignore */
7099             if (appData.debugMode) {
7100                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7101             }
7102             return;
7103
7104           case EndOfGame:
7105           case IcsIdle:
7106           default:
7107             /* Extra move after we tried to stop.  The mode test is
7108                not a reliable way of detecting this problem, but it's
7109                the best we can do on engines that don't support ping.
7110             */
7111             if (appData.debugMode) {
7112                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7113                         cps->which, gameMode);
7114             }
7115             SendToProgram("undo\n", cps);
7116             return;
7117
7118           case MachinePlaysWhite:
7119           case IcsPlayingWhite:
7120             machineWhite = TRUE;
7121             break;
7122
7123           case MachinePlaysBlack:
7124           case IcsPlayingBlack:
7125             machineWhite = FALSE;
7126             break;
7127
7128           case TwoMachinesPlay:
7129             machineWhite = (cps->twoMachinesColor[0] == 'w');
7130             break;
7131         }
7132         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7133             if (appData.debugMode) {
7134                 fprintf(debugFP,
7135                         "Ignoring move out of turn by %s, gameMode %d"
7136                         ", forwardMost %d\n",
7137                         cps->which, gameMode, forwardMostMove);
7138             }
7139             return;
7140         }
7141
7142     if (appData.debugMode) { int f = forwardMostMove;
7143         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7144                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7145                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7146     }
7147         if(cps->alphaRank) AlphaRank(machineMove, 4);
7148         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7149                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7150             /* Machine move could not be parsed; ignore it. */
7151             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7152                     machineMove, cps->which);
7153             DisplayError(buf1, 0);
7154             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7155                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7156             if (gameMode == TwoMachinesPlay) {
7157               GameEnds(machineWhite ? BlackWins : WhiteWins,
7158                        buf1, GE_XBOARD);
7159             }
7160             return;
7161         }
7162
7163         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7164         /* So we have to redo legality test with true e.p. status here,  */
7165         /* to make sure an illegal e.p. capture does not slip through,   */
7166         /* to cause a forfeit on a justified illegal-move complaint      */
7167         /* of the opponent.                                              */
7168         if( gameMode==TwoMachinesPlay && appData.testLegality
7169             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7170                                                               ) {
7171            ChessMove moveType;
7172            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7173                              fromY, fromX, toY, toX, promoChar);
7174             if (appData.debugMode) {
7175                 int i;
7176                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7177                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7178                 fprintf(debugFP, "castling rights\n");
7179             }
7180             if(moveType == IllegalMove) {
7181                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7182                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7183                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7184                            buf1, GE_XBOARD);
7185                 return;
7186            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7187            /* [HGM] Kludge to handle engines that send FRC-style castling
7188               when they shouldn't (like TSCP-Gothic) */
7189            switch(moveType) {
7190              case WhiteASideCastleFR:
7191              case BlackASideCastleFR:
7192                toX+=2;
7193                currentMoveString[2]++;
7194                break;
7195              case WhiteHSideCastleFR:
7196              case BlackHSideCastleFR:
7197                toX--;
7198                currentMoveString[2]--;
7199                break;
7200              default: ; // nothing to do, but suppresses warning of pedantic compilers
7201            }
7202         }
7203         hintRequested = FALSE;
7204         lastHint[0] = NULLCHAR;
7205         bookRequested = FALSE;
7206         /* Program may be pondering now */
7207         cps->maybeThinking = TRUE;
7208         if (cps->sendTime == 2) cps->sendTime = 1;
7209         if (cps->offeredDraw) cps->offeredDraw--;
7210
7211         /* currentMoveString is set as a side-effect of ParseOneMove */
7212         strcpy(machineMove, currentMoveString);
7213         strcat(machineMove, "\n");
7214         strcpy(moveList[forwardMostMove], machineMove);
7215
7216         /* [AS] Save move info*/
7217         pvInfoList[ forwardMostMove ].score = programStats.score;
7218         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7219         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7220
7221         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7222
7223         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7224         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7225             int count = 0;
7226
7227             while( count < adjudicateLossPlies ) {
7228                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7229
7230                 if( count & 1 ) {
7231                     score = -score; /* Flip score for winning side */
7232                 }
7233
7234                 if( score > adjudicateLossThreshold ) {
7235                     break;
7236                 }
7237
7238                 count++;
7239             }
7240
7241             if( count >= adjudicateLossPlies ) {
7242                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7243
7244                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7245                     "Xboard adjudication", 
7246                     GE_XBOARD );
7247
7248                 return;
7249             }
7250         }
7251
7252         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7253
7254 #if ZIPPY
7255         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7256             first.initDone) {
7257           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7258                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7259                 SendToICS("draw ");
7260                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7261           }
7262           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7263           ics_user_moved = 1;
7264           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7265                 char buf[3*MSG_SIZ];
7266
7267                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7268                         programStats.score / 100.,
7269                         programStats.depth,
7270                         programStats.time / 100.,
7271                         (unsigned int)programStats.nodes,
7272                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7273                         programStats.movelist);
7274                 SendToICS(buf);
7275 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7276           }
7277         }
7278 #endif
7279
7280         /* [AS] Clear stats for next move */
7281         ClearProgramStats();
7282         thinkOutput[0] = NULLCHAR;
7283         hiddenThinkOutputState = 0;
7284
7285         bookHit = NULL;
7286         if (gameMode == TwoMachinesPlay) {
7287             /* [HGM] relaying draw offers moved to after reception of move */
7288             /* and interpreting offer as claim if it brings draw condition */
7289             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7290                 SendToProgram("draw\n", cps->other);
7291             }
7292             if (cps->other->sendTime) {
7293                 SendTimeRemaining(cps->other,
7294                                   cps->other->twoMachinesColor[0] == 'w');
7295             }
7296             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7297             if (firstMove && !bookHit) {
7298                 firstMove = FALSE;
7299                 if (cps->other->useColors) {
7300                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7301                 }
7302                 SendToProgram("go\n", cps->other);
7303             }
7304             cps->other->maybeThinking = TRUE;
7305         }
7306
7307         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7308         
7309         if (!pausing && appData.ringBellAfterMoves) {
7310             RingBell();
7311         }
7312
7313         /* 
7314          * Reenable menu items that were disabled while
7315          * machine was thinking
7316          */
7317         if (gameMode != TwoMachinesPlay)
7318             SetUserThinkingEnables();
7319
7320         // [HGM] book: after book hit opponent has received move and is now in force mode
7321         // force the book reply into it, and then fake that it outputted this move by jumping
7322         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7323         if(bookHit) {
7324                 static char bookMove[MSG_SIZ]; // a bit generous?
7325
7326                 strcpy(bookMove, "move ");
7327                 strcat(bookMove, bookHit);
7328                 message = bookMove;
7329                 cps = cps->other;
7330                 programStats.nodes = programStats.depth = programStats.time = 
7331                 programStats.score = programStats.got_only_move = 0;
7332                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7333
7334                 if(cps->lastPing != cps->lastPong) {
7335                     savedMessage = message; // args for deferred call
7336                     savedState = cps;
7337                     ScheduleDelayedEvent(DeferredBookMove, 10);
7338                     return;
7339                 }
7340                 goto FakeBookMove;
7341         }
7342
7343         return;
7344     }
7345
7346     /* Set special modes for chess engines.  Later something general
7347      *  could be added here; for now there is just one kludge feature,
7348      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7349      *  when "xboard" is given as an interactive command.
7350      */
7351     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7352         cps->useSigint = FALSE;
7353         cps->useSigterm = FALSE;
7354     }
7355     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7356       ParseFeatures(message+8, cps);
7357       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7358     }
7359
7360     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7361      * want this, I was asked to put it in, and obliged.
7362      */
7363     if (!strncmp(message, "setboard ", 9)) {
7364         Board initial_position;
7365
7366         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7367
7368         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7369             DisplayError(_("Bad FEN received from engine"), 0);
7370             return ;
7371         } else {
7372            Reset(TRUE, FALSE);
7373            CopyBoard(boards[0], initial_position);
7374            initialRulePlies = FENrulePlies;
7375            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7376            else gameMode = MachinePlaysBlack;                 
7377            DrawPosition(FALSE, boards[currentMove]);
7378         }
7379         return;
7380     }
7381
7382     /*
7383      * Look for communication commands
7384      */
7385     if (!strncmp(message, "telluser ", 9)) {
7386         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7387         DisplayNote(message + 9);
7388         return;
7389     }
7390     if (!strncmp(message, "tellusererror ", 14)) {
7391         cps->userError = 1;
7392         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7393         DisplayError(message + 14, 0);
7394         return;
7395     }
7396     if (!strncmp(message, "tellopponent ", 13)) {
7397       if (appData.icsActive) {
7398         if (loggedOn) {
7399           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7400           SendToICS(buf1);
7401         }
7402       } else {
7403         DisplayNote(message + 13);
7404       }
7405       return;
7406     }
7407     if (!strncmp(message, "tellothers ", 11)) {
7408       if (appData.icsActive) {
7409         if (loggedOn) {
7410           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7411           SendToICS(buf1);
7412         }
7413       }
7414       return;
7415     }
7416     if (!strncmp(message, "tellall ", 8)) {
7417       if (appData.icsActive) {
7418         if (loggedOn) {
7419           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7420           SendToICS(buf1);
7421         }
7422       } else {
7423         DisplayNote(message + 8);
7424       }
7425       return;
7426     }
7427     if (strncmp(message, "warning", 7) == 0) {
7428         /* Undocumented feature, use tellusererror in new code */
7429         DisplayError(message, 0);
7430         return;
7431     }
7432     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7433         strcpy(realname, cps->tidy);
7434         strcat(realname, " query");
7435         AskQuestion(realname, buf2, buf1, cps->pr);
7436         return;
7437     }
7438     /* Commands from the engine directly to ICS.  We don't allow these to be 
7439      *  sent until we are logged on. Crafty kibitzes have been known to 
7440      *  interfere with the login process.
7441      */
7442     if (loggedOn) {
7443         if (!strncmp(message, "tellics ", 8)) {
7444             SendToICS(message + 8);
7445             SendToICS("\n");
7446             return;
7447         }
7448         if (!strncmp(message, "tellicsnoalias ", 15)) {
7449             SendToICS(ics_prefix);
7450             SendToICS(message + 15);
7451             SendToICS("\n");
7452             return;
7453         }
7454         /* The following are for backward compatibility only */
7455         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7456             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7457             SendToICS(ics_prefix);
7458             SendToICS(message);
7459             SendToICS("\n");
7460             return;
7461         }
7462     }
7463     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7464         return;
7465     }
7466     /*
7467      * If the move is illegal, cancel it and redraw the board.
7468      * Also deal with other error cases.  Matching is rather loose
7469      * here to accommodate engines written before the spec.
7470      */
7471     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7472         strncmp(message, "Error", 5) == 0) {
7473         if (StrStr(message, "name") || 
7474             StrStr(message, "rating") || StrStr(message, "?") ||
7475             StrStr(message, "result") || StrStr(message, "board") ||
7476             StrStr(message, "bk") || StrStr(message, "computer") ||
7477             StrStr(message, "variant") || StrStr(message, "hint") ||
7478             StrStr(message, "random") || StrStr(message, "depth") ||
7479             StrStr(message, "accepted")) {
7480             return;
7481         }
7482         if (StrStr(message, "protover")) {
7483           /* Program is responding to input, so it's apparently done
7484              initializing, and this error message indicates it is
7485              protocol version 1.  So we don't need to wait any longer
7486              for it to initialize and send feature commands. */
7487           FeatureDone(cps, 1);
7488           cps->protocolVersion = 1;
7489           return;
7490         }
7491         cps->maybeThinking = FALSE;
7492
7493         if (StrStr(message, "draw")) {
7494             /* Program doesn't have "draw" command */
7495             cps->sendDrawOffers = 0;
7496             return;
7497         }
7498         if (cps->sendTime != 1 &&
7499             (StrStr(message, "time") || StrStr(message, "otim"))) {
7500           /* Program apparently doesn't have "time" or "otim" command */
7501           cps->sendTime = 0;
7502           return;
7503         }
7504         if (StrStr(message, "analyze")) {
7505             cps->analysisSupport = FALSE;
7506             cps->analyzing = FALSE;
7507             Reset(FALSE, TRUE);
7508             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7509             DisplayError(buf2, 0);
7510             return;
7511         }
7512         if (StrStr(message, "(no matching move)st")) {
7513           /* Special kludge for GNU Chess 4 only */
7514           cps->stKludge = TRUE;
7515           SendTimeControl(cps, movesPerSession, timeControl,
7516                           timeIncrement, appData.searchDepth,
7517                           searchTime);
7518           return;
7519         }
7520         if (StrStr(message, "(no matching move)sd")) {
7521           /* Special kludge for GNU Chess 4 only */
7522           cps->sdKludge = TRUE;
7523           SendTimeControl(cps, movesPerSession, timeControl,
7524                           timeIncrement, appData.searchDepth,
7525                           searchTime);
7526           return;
7527         }
7528         if (!StrStr(message, "llegal")) {
7529             return;
7530         }
7531         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7532             gameMode == IcsIdle) return;
7533         if (forwardMostMove <= backwardMostMove) return;
7534         if (pausing) PauseEvent();
7535       if(appData.forceIllegal) {
7536             // [HGM] illegal: machine refused move; force position after move into it
7537           SendToProgram("force\n", cps);
7538           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7539                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7540                 // when black is to move, while there might be nothing on a2 or black
7541                 // might already have the move. So send the board as if white has the move.
7542                 // But first we must change the stm of the engine, as it refused the last move
7543                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7544                 if(WhiteOnMove(forwardMostMove)) {
7545                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7546                     SendBoard(cps, forwardMostMove); // kludgeless board
7547                 } else {
7548                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7549                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7550                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7551                 }
7552           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7553             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7554                  gameMode == TwoMachinesPlay)
7555               SendToProgram("go\n", cps);
7556             return;
7557       } else
7558         if (gameMode == PlayFromGameFile) {
7559             /* Stop reading this game file */
7560             gameMode = EditGame;
7561             ModeHighlight();
7562         }
7563         currentMove = forwardMostMove-1;
7564         DisplayMove(currentMove-1); /* before DisplayMoveError */
7565         SwitchClocks(forwardMostMove-1); // [HGM] race
7566         DisplayBothClocks();
7567         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7568                 parseList[currentMove], cps->which);
7569         DisplayMoveError(buf1);
7570         DrawPosition(FALSE, boards[currentMove]);
7571
7572         /* [HGM] illegal-move claim should forfeit game when Xboard */
7573         /* only passes fully legal moves                            */
7574         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7575             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7576                                 "False illegal-move claim", GE_XBOARD );
7577         }
7578         return;
7579     }
7580     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7581         /* Program has a broken "time" command that
7582            outputs a string not ending in newline.
7583            Don't use it. */
7584         cps->sendTime = 0;
7585     }
7586     
7587     /*
7588      * If chess program startup fails, exit with an error message.
7589      * Attempts to recover here are futile.
7590      */
7591     if ((StrStr(message, "unknown host") != NULL)
7592         || (StrStr(message, "No remote directory") != NULL)
7593         || (StrStr(message, "not found") != NULL)
7594         || (StrStr(message, "No such file") != NULL)
7595         || (StrStr(message, "can't alloc") != NULL)
7596         || (StrStr(message, "Permission denied") != NULL)) {
7597
7598         cps->maybeThinking = FALSE;
7599         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7600                 cps->which, cps->program, cps->host, message);
7601         RemoveInputSource(cps->isr);
7602         DisplayFatalError(buf1, 0, 1);
7603         return;
7604     }
7605     
7606     /* 
7607      * Look for hint output
7608      */
7609     if (sscanf(message, "Hint: %s", buf1) == 1) {
7610         if (cps == &first && hintRequested) {
7611             hintRequested = FALSE;
7612             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7613                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7614                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7615                                     PosFlags(forwardMostMove),
7616                                     fromY, fromX, toY, toX, promoChar, buf1);
7617                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7618                 DisplayInformation(buf2);
7619             } else {
7620                 /* Hint move could not be parsed!? */
7621               snprintf(buf2, sizeof(buf2),
7622                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7623                         buf1, cps->which);
7624                 DisplayError(buf2, 0);
7625             }
7626         } else {
7627             strcpy(lastHint, buf1);
7628         }
7629         return;
7630     }
7631
7632     /*
7633      * Ignore other messages if game is not in progress
7634      */
7635     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7636         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7637
7638     /*
7639      * look for win, lose, draw, or draw offer
7640      */
7641     if (strncmp(message, "1-0", 3) == 0) {
7642         char *p, *q, *r = "";
7643         p = strchr(message, '{');
7644         if (p) {
7645             q = strchr(p, '}');
7646             if (q) {
7647                 *q = NULLCHAR;
7648                 r = p + 1;
7649             }
7650         }
7651         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7652         return;
7653     } else if (strncmp(message, "0-1", 3) == 0) {
7654         char *p, *q, *r = "";
7655         p = strchr(message, '{');
7656         if (p) {
7657             q = strchr(p, '}');
7658             if (q) {
7659                 *q = NULLCHAR;
7660                 r = p + 1;
7661             }
7662         }
7663         /* Kludge for Arasan 4.1 bug */
7664         if (strcmp(r, "Black resigns") == 0) {
7665             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7666             return;
7667         }
7668         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7669         return;
7670     } else if (strncmp(message, "1/2", 3) == 0) {
7671         char *p, *q, *r = "";
7672         p = strchr(message, '{');
7673         if (p) {
7674             q = strchr(p, '}');
7675             if (q) {
7676                 *q = NULLCHAR;
7677                 r = p + 1;
7678             }
7679         }
7680             
7681         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7682         return;
7683
7684     } else if (strncmp(message, "White resign", 12) == 0) {
7685         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7686         return;
7687     } else if (strncmp(message, "Black resign", 12) == 0) {
7688         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7689         return;
7690     } else if (strncmp(message, "White matches", 13) == 0 ||
7691                strncmp(message, "Black matches", 13) == 0   ) {
7692         /* [HGM] ignore GNUShogi noises */
7693         return;
7694     } else if (strncmp(message, "White", 5) == 0 &&
7695                message[5] != '(' &&
7696                StrStr(message, "Black") == NULL) {
7697         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7698         return;
7699     } else if (strncmp(message, "Black", 5) == 0 &&
7700                message[5] != '(') {
7701         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7702         return;
7703     } else if (strcmp(message, "resign") == 0 ||
7704                strcmp(message, "computer resigns") == 0) {
7705         switch (gameMode) {
7706           case MachinePlaysBlack:
7707           case IcsPlayingBlack:
7708             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7709             break;
7710           case MachinePlaysWhite:
7711           case IcsPlayingWhite:
7712             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7713             break;
7714           case TwoMachinesPlay:
7715             if (cps->twoMachinesColor[0] == 'w')
7716               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7717             else
7718               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7719             break;
7720           default:
7721             /* can't happen */
7722             break;
7723         }
7724         return;
7725     } else if (strncmp(message, "opponent mates", 14) == 0) {
7726         switch (gameMode) {
7727           case MachinePlaysBlack:
7728           case IcsPlayingBlack:
7729             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7730             break;
7731           case MachinePlaysWhite:
7732           case IcsPlayingWhite:
7733             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7734             break;
7735           case TwoMachinesPlay:
7736             if (cps->twoMachinesColor[0] == 'w')
7737               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7738             else
7739               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7740             break;
7741           default:
7742             /* can't happen */
7743             break;
7744         }
7745         return;
7746     } else if (strncmp(message, "computer mates", 14) == 0) {
7747         switch (gameMode) {
7748           case MachinePlaysBlack:
7749           case IcsPlayingBlack:
7750             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7751             break;
7752           case MachinePlaysWhite:
7753           case IcsPlayingWhite:
7754             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7755             break;
7756           case TwoMachinesPlay:
7757             if (cps->twoMachinesColor[0] == 'w')
7758               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7759             else
7760               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7761             break;
7762           default:
7763             /* can't happen */
7764             break;
7765         }
7766         return;
7767     } else if (strncmp(message, "checkmate", 9) == 0) {
7768         if (WhiteOnMove(forwardMostMove)) {
7769             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7770         } else {
7771             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7772         }
7773         return;
7774     } else if (strstr(message, "Draw") != NULL ||
7775                strstr(message, "game is a draw") != NULL) {
7776         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7777         return;
7778     } else if (strstr(message, "offer") != NULL &&
7779                strstr(message, "draw") != NULL) {
7780 #if ZIPPY
7781         if (appData.zippyPlay && first.initDone) {
7782             /* Relay offer to ICS */
7783             SendToICS(ics_prefix);
7784             SendToICS("draw\n");
7785         }
7786 #endif
7787         cps->offeredDraw = 2; /* valid until this engine moves twice */
7788         if (gameMode == TwoMachinesPlay) {
7789             if (cps->other->offeredDraw) {
7790                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7791             /* [HGM] in two-machine mode we delay relaying draw offer      */
7792             /* until after we also have move, to see if it is really claim */
7793             }
7794         } else if (gameMode == MachinePlaysWhite ||
7795                    gameMode == MachinePlaysBlack) {
7796           if (userOfferedDraw) {
7797             DisplayInformation(_("Machine accepts your draw offer"));
7798             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7799           } else {
7800             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7801           }
7802         }
7803     }
7804
7805     
7806     /*
7807      * Look for thinking output
7808      */
7809     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7810           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7811                                 ) {
7812         int plylev, mvleft, mvtot, curscore, time;
7813         char mvname[MOVE_LEN];
7814         u64 nodes; // [DM]
7815         char plyext;
7816         int ignore = FALSE;
7817         int prefixHint = FALSE;
7818         mvname[0] = NULLCHAR;
7819
7820         switch (gameMode) {
7821           case MachinePlaysBlack:
7822           case IcsPlayingBlack:
7823             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7824             break;
7825           case MachinePlaysWhite:
7826           case IcsPlayingWhite:
7827             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7828             break;
7829           case AnalyzeMode:
7830           case AnalyzeFile:
7831             break;
7832           case IcsObserving: /* [DM] icsEngineAnalyze */
7833             if (!appData.icsEngineAnalyze) ignore = TRUE;
7834             break;
7835           case TwoMachinesPlay:
7836             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7837                 ignore = TRUE;
7838             }
7839             break;
7840           default:
7841             ignore = TRUE;
7842             break;
7843         }
7844
7845         if (!ignore) {
7846             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7847             buf1[0] = NULLCHAR;
7848             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7849                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7850
7851                 if (plyext != ' ' && plyext != '\t') {
7852                     time *= 100;
7853                 }
7854
7855                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7856                 if( cps->scoreIsAbsolute && 
7857                     ( gameMode == MachinePlaysBlack ||
7858                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7859                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7860                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7861                      !WhiteOnMove(currentMove)
7862                     ) )
7863                 {
7864                     curscore = -curscore;
7865                 }
7866
7867
7868                 tempStats.depth = plylev;
7869                 tempStats.nodes = nodes;
7870                 tempStats.time = time;
7871                 tempStats.score = curscore;
7872                 tempStats.got_only_move = 0;
7873
7874                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7875                         int ticklen;
7876
7877                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7878                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7879                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7880                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7881                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7882                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7883                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7884                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7885                 }
7886
7887                 /* Buffer overflow protection */
7888                 if (buf1[0] != NULLCHAR) {
7889                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7890                         && appData.debugMode) {
7891                         fprintf(debugFP,
7892                                 "PV is too long; using the first %u bytes.\n",
7893                                 (unsigned) sizeof(tempStats.movelist) - 1);
7894                     }
7895
7896                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7897                 } else {
7898                     sprintf(tempStats.movelist, " no PV\n");
7899                 }
7900
7901                 if (tempStats.seen_stat) {
7902                     tempStats.ok_to_send = 1;
7903                 }
7904
7905                 if (strchr(tempStats.movelist, '(') != NULL) {
7906                     tempStats.line_is_book = 1;
7907                     tempStats.nr_moves = 0;
7908                     tempStats.moves_left = 0;
7909                 } else {
7910                     tempStats.line_is_book = 0;
7911                 }
7912
7913                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7914                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7915
7916                 SendProgramStatsToFrontend( cps, &tempStats );
7917
7918                 /* 
7919                     [AS] Protect the thinkOutput buffer from overflow... this
7920                     is only useful if buf1 hasn't overflowed first!
7921                 */
7922                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7923                         plylev, 
7924                         (gameMode == TwoMachinesPlay ?
7925                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7926                         ((double) curscore) / 100.0,
7927                         prefixHint ? lastHint : "",
7928                         prefixHint ? " " : "" );
7929
7930                 if( buf1[0] != NULLCHAR ) {
7931                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7932
7933                     if( strlen(buf1) > max_len ) {
7934                         if( appData.debugMode) {
7935                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7936                         }
7937                         buf1[max_len+1] = '\0';
7938                     }
7939
7940                     strcat( thinkOutput, buf1 );
7941                 }
7942
7943                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7944                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7945                     DisplayMove(currentMove - 1);
7946                 }
7947                 return;
7948
7949             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7950                 /* crafty (9.25+) says "(only move) <move>"
7951                  * if there is only 1 legal move
7952                  */
7953                 sscanf(p, "(only move) %s", buf1);
7954                 sprintf(thinkOutput, "%s (only move)", buf1);
7955                 sprintf(programStats.movelist, "%s (only move)", buf1);
7956                 programStats.depth = 1;
7957                 programStats.nr_moves = 1;
7958                 programStats.moves_left = 1;
7959                 programStats.nodes = 1;
7960                 programStats.time = 1;
7961                 programStats.got_only_move = 1;
7962
7963                 /* Not really, but we also use this member to
7964                    mean "line isn't going to change" (Crafty
7965                    isn't searching, so stats won't change) */
7966                 programStats.line_is_book = 1;
7967
7968                 SendProgramStatsToFrontend( cps, &programStats );
7969                 
7970                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7971                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7972                     DisplayMove(currentMove - 1);
7973                 }
7974                 return;
7975             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7976                               &time, &nodes, &plylev, &mvleft,
7977                               &mvtot, mvname) >= 5) {
7978                 /* The stat01: line is from Crafty (9.29+) in response
7979                    to the "." command */
7980                 programStats.seen_stat = 1;
7981                 cps->maybeThinking = TRUE;
7982
7983                 if (programStats.got_only_move || !appData.periodicUpdates)
7984                   return;
7985
7986                 programStats.depth = plylev;
7987                 programStats.time = time;
7988                 programStats.nodes = nodes;
7989                 programStats.moves_left = mvleft;
7990                 programStats.nr_moves = mvtot;
7991                 strcpy(programStats.move_name, mvname);
7992                 programStats.ok_to_send = 1;
7993                 programStats.movelist[0] = '\0';
7994
7995                 SendProgramStatsToFrontend( cps, &programStats );
7996
7997                 return;
7998
7999             } else if (strncmp(message,"++",2) == 0) {
8000                 /* Crafty 9.29+ outputs this */
8001                 programStats.got_fail = 2;
8002                 return;
8003
8004             } else if (strncmp(message,"--",2) == 0) {
8005                 /* Crafty 9.29+ outputs this */
8006                 programStats.got_fail = 1;
8007                 return;
8008
8009             } else if (thinkOutput[0] != NULLCHAR &&
8010                        strncmp(message, "    ", 4) == 0) {
8011                 unsigned message_len;
8012
8013                 p = message;
8014                 while (*p && *p == ' ') p++;
8015
8016                 message_len = strlen( p );
8017
8018                 /* [AS] Avoid buffer overflow */
8019                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8020                     strcat(thinkOutput, " ");
8021                     strcat(thinkOutput, p);
8022                 }
8023
8024                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8025                     strcat(programStats.movelist, " ");
8026                     strcat(programStats.movelist, p);
8027                 }
8028
8029                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8030                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8031                     DisplayMove(currentMove - 1);
8032                 }
8033                 return;
8034             }
8035         }
8036         else {
8037             buf1[0] = NULLCHAR;
8038
8039             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8040                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8041             {
8042                 ChessProgramStats cpstats;
8043
8044                 if (plyext != ' ' && plyext != '\t') {
8045                     time *= 100;
8046                 }
8047
8048                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8049                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8050                     curscore = -curscore;
8051                 }
8052
8053                 cpstats.depth = plylev;
8054                 cpstats.nodes = nodes;
8055                 cpstats.time = time;
8056                 cpstats.score = curscore;
8057                 cpstats.got_only_move = 0;
8058                 cpstats.movelist[0] = '\0';
8059
8060                 if (buf1[0] != NULLCHAR) {
8061                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8062                 }
8063
8064                 cpstats.ok_to_send = 0;
8065                 cpstats.line_is_book = 0;
8066                 cpstats.nr_moves = 0;
8067                 cpstats.moves_left = 0;
8068
8069                 SendProgramStatsToFrontend( cps, &cpstats );
8070             }
8071         }
8072     }
8073 }
8074
8075
8076 /* Parse a game score from the character string "game", and
8077    record it as the history of the current game.  The game
8078    score is NOT assumed to start from the standard position. 
8079    The display is not updated in any way.
8080    */
8081 void
8082 ParseGameHistory(game)
8083      char *game;
8084 {
8085     ChessMove moveType;
8086     int fromX, fromY, toX, toY, boardIndex;
8087     char promoChar;
8088     char *p, *q;
8089     char buf[MSG_SIZ];
8090
8091     if (appData.debugMode)
8092       fprintf(debugFP, "Parsing game history: %s\n", game);
8093
8094     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8095     gameInfo.site = StrSave(appData.icsHost);
8096     gameInfo.date = PGNDate();
8097     gameInfo.round = StrSave("-");
8098
8099     /* Parse out names of players */
8100     while (*game == ' ') game++;
8101     p = buf;
8102     while (*game != ' ') *p++ = *game++;
8103     *p = NULLCHAR;
8104     gameInfo.white = StrSave(buf);
8105     while (*game == ' ') game++;
8106     p = buf;
8107     while (*game != ' ' && *game != '\n') *p++ = *game++;
8108     *p = NULLCHAR;
8109     gameInfo.black = StrSave(buf);
8110
8111     /* Parse moves */
8112     boardIndex = blackPlaysFirst ? 1 : 0;
8113     yynewstr(game);
8114     for (;;) {
8115         yyboardindex = boardIndex;
8116         moveType = (ChessMove) yylex();
8117         switch (moveType) {
8118           case IllegalMove:             /* maybe suicide chess, etc. */
8119   if (appData.debugMode) {
8120     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8121     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8122     setbuf(debugFP, NULL);
8123   }
8124           case WhitePromotionChancellor:
8125           case BlackPromotionChancellor:
8126           case WhitePromotionArchbishop:
8127           case BlackPromotionArchbishop:
8128           case WhitePromotionQueen:
8129           case BlackPromotionQueen:
8130           case WhitePromotionRook:
8131           case BlackPromotionRook:
8132           case WhitePromotionBishop:
8133           case BlackPromotionBishop:
8134           case WhitePromotionKnight:
8135           case BlackPromotionKnight:
8136           case WhitePromotionKing:
8137           case BlackPromotionKing:
8138           case NormalMove:
8139           case WhiteCapturesEnPassant:
8140           case BlackCapturesEnPassant:
8141           case WhiteKingSideCastle:
8142           case WhiteQueenSideCastle:
8143           case BlackKingSideCastle:
8144           case BlackQueenSideCastle:
8145           case WhiteKingSideCastleWild:
8146           case WhiteQueenSideCastleWild:
8147           case BlackKingSideCastleWild:
8148           case BlackQueenSideCastleWild:
8149           /* PUSH Fabien */
8150           case WhiteHSideCastleFR:
8151           case WhiteASideCastleFR:
8152           case BlackHSideCastleFR:
8153           case BlackASideCastleFR:
8154           /* POP Fabien */
8155             fromX = currentMoveString[0] - AAA;
8156             fromY = currentMoveString[1] - ONE;
8157             toX = currentMoveString[2] - AAA;
8158             toY = currentMoveString[3] - ONE;
8159             promoChar = currentMoveString[4];
8160             break;
8161           case WhiteDrop:
8162           case BlackDrop:
8163             fromX = moveType == WhiteDrop ?
8164               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8165             (int) CharToPiece(ToLower(currentMoveString[0]));
8166             fromY = DROP_RANK;
8167             toX = currentMoveString[2] - AAA;
8168             toY = currentMoveString[3] - ONE;
8169             promoChar = NULLCHAR;
8170             break;
8171           case AmbiguousMove:
8172             /* bug? */
8173             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8174   if (appData.debugMode) {
8175     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8176     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8177     setbuf(debugFP, NULL);
8178   }
8179             DisplayError(buf, 0);
8180             return;
8181           case ImpossibleMove:
8182             /* bug? */
8183             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8184   if (appData.debugMode) {
8185     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8186     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8187     setbuf(debugFP, NULL);
8188   }
8189             DisplayError(buf, 0);
8190             return;
8191           case (ChessMove) 0:   /* end of file */
8192             if (boardIndex < backwardMostMove) {
8193                 /* Oops, gap.  How did that happen? */
8194                 DisplayError(_("Gap in move list"), 0);
8195                 return;
8196             }
8197             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8198             if (boardIndex > forwardMostMove) {
8199                 forwardMostMove = boardIndex;
8200             }
8201             return;
8202           case ElapsedTime:
8203             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8204                 strcat(parseList[boardIndex-1], " ");
8205                 strcat(parseList[boardIndex-1], yy_text);
8206             }
8207             continue;
8208           case Comment:
8209           case PGNTag:
8210           case NAG:
8211           default:
8212             /* ignore */
8213             continue;
8214           case WhiteWins:
8215           case BlackWins:
8216           case GameIsDrawn:
8217           case GameUnfinished:
8218             if (gameMode == IcsExamining) {
8219                 if (boardIndex < backwardMostMove) {
8220                     /* Oops, gap.  How did that happen? */
8221                     return;
8222                 }
8223                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8224                 return;
8225             }
8226             gameInfo.result = moveType;
8227             p = strchr(yy_text, '{');
8228             if (p == NULL) p = strchr(yy_text, '(');
8229             if (p == NULL) {
8230                 p = yy_text;
8231                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8232             } else {
8233                 q = strchr(p, *p == '{' ? '}' : ')');
8234                 if (q != NULL) *q = NULLCHAR;
8235                 p++;
8236             }
8237             gameInfo.resultDetails = StrSave(p);
8238             continue;
8239         }
8240         if (boardIndex >= forwardMostMove &&
8241             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8242             backwardMostMove = blackPlaysFirst ? 1 : 0;
8243             return;
8244         }
8245         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8246                                  fromY, fromX, toY, toX, promoChar,
8247                                  parseList[boardIndex]);
8248         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8249         /* currentMoveString is set as a side-effect of yylex */
8250         strcpy(moveList[boardIndex], currentMoveString);
8251         strcat(moveList[boardIndex], "\n");
8252         boardIndex++;
8253         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8254         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8255           case MT_NONE:
8256           case MT_STALEMATE:
8257           default:
8258             break;
8259           case MT_CHECK:
8260             if(gameInfo.variant != VariantShogi)
8261                 strcat(parseList[boardIndex - 1], "+");
8262             break;
8263           case MT_CHECKMATE:
8264           case MT_STAINMATE:
8265             strcat(parseList[boardIndex - 1], "#");
8266             break;
8267         }
8268     }
8269 }
8270
8271
8272 /* Apply a move to the given board  */
8273 void
8274 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8275      int fromX, fromY, toX, toY;
8276      int promoChar;
8277      Board board;
8278 {
8279   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8280   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8281
8282     /* [HGM] compute & store e.p. status and castling rights for new position */
8283     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8284     { int i;
8285
8286       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8287       oldEP = (signed char)board[EP_STATUS];
8288       board[EP_STATUS] = EP_NONE;
8289
8290       if( board[toY][toX] != EmptySquare ) 
8291            board[EP_STATUS] = EP_CAPTURE;  
8292
8293       if( board[fromY][fromX] == WhitePawn ) {
8294            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8295                board[EP_STATUS] = EP_PAWN_MOVE;
8296            if( toY-fromY==2) {
8297                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8298                         gameInfo.variant != VariantBerolina || toX < fromX)
8299                       board[EP_STATUS] = toX | berolina;
8300                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8301                         gameInfo.variant != VariantBerolina || toX > fromX) 
8302                       board[EP_STATUS] = toX;
8303            }
8304       } else 
8305       if( board[fromY][fromX] == BlackPawn ) {
8306            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8307                board[EP_STATUS] = EP_PAWN_MOVE; 
8308            if( toY-fromY== -2) {
8309                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8310                         gameInfo.variant != VariantBerolina || toX < fromX)
8311                       board[EP_STATUS] = toX | berolina;
8312                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8313                         gameInfo.variant != VariantBerolina || toX > fromX) 
8314                       board[EP_STATUS] = toX;
8315            }
8316        }
8317
8318        for(i=0; i<nrCastlingRights; i++) {
8319            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8320               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8321              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8322        }
8323
8324     }
8325
8326   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8327   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8328        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8329          
8330   if (fromX == toX && fromY == toY) return;
8331
8332   if (fromY == DROP_RANK) {
8333         /* must be first */
8334         piece = board[toY][toX] = (ChessSquare) fromX;
8335   } else {
8336      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8337      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8338      if(gameInfo.variant == VariantKnightmate)
8339          king += (int) WhiteUnicorn - (int) WhiteKing;
8340
8341     /* Code added by Tord: */
8342     /* FRC castling assumed when king captures friendly rook. */
8343     if (board[fromY][fromX] == WhiteKing &&
8344              board[toY][toX] == WhiteRook) {
8345       board[fromY][fromX] = EmptySquare;
8346       board[toY][toX] = EmptySquare;
8347       if(toX > fromX) {
8348         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8349       } else {
8350         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8351       }
8352     } else if (board[fromY][fromX] == BlackKing &&
8353                board[toY][toX] == BlackRook) {
8354       board[fromY][fromX] = EmptySquare;
8355       board[toY][toX] = EmptySquare;
8356       if(toX > fromX) {
8357         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8358       } else {
8359         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8360       }
8361     /* End of code added by Tord */
8362
8363     } else if (board[fromY][fromX] == king
8364         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8365         && toY == fromY && toX > fromX+1) {
8366         board[fromY][fromX] = EmptySquare;
8367         board[toY][toX] = king;
8368         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8369         board[fromY][BOARD_RGHT-1] = EmptySquare;
8370     } else if (board[fromY][fromX] == king
8371         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8372                && toY == fromY && toX < fromX-1) {
8373         board[fromY][fromX] = EmptySquare;
8374         board[toY][toX] = king;
8375         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8376         board[fromY][BOARD_LEFT] = EmptySquare;
8377     } else if (board[fromY][fromX] == WhitePawn
8378                && toY >= BOARD_HEIGHT-promoRank
8379                && gameInfo.variant != VariantXiangqi
8380                ) {
8381         /* white pawn promotion */
8382         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8383         if (board[toY][toX] == EmptySquare) {
8384             board[toY][toX] = WhiteQueen;
8385         }
8386         if(gameInfo.variant==VariantBughouse ||
8387            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8388             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8389         board[fromY][fromX] = EmptySquare;
8390     } else if ((fromY == BOARD_HEIGHT-4)
8391                && (toX != fromX)
8392                && gameInfo.variant != VariantXiangqi
8393                && gameInfo.variant != VariantBerolina
8394                && (board[fromY][fromX] == WhitePawn)
8395                && (board[toY][toX] == EmptySquare)) {
8396         board[fromY][fromX] = EmptySquare;
8397         board[toY][toX] = WhitePawn;
8398         captured = board[toY - 1][toX];
8399         board[toY - 1][toX] = EmptySquare;
8400     } else if ((fromY == BOARD_HEIGHT-4)
8401                && (toX == fromX)
8402                && gameInfo.variant == VariantBerolina
8403                && (board[fromY][fromX] == WhitePawn)
8404                && (board[toY][toX] == EmptySquare)) {
8405         board[fromY][fromX] = EmptySquare;
8406         board[toY][toX] = WhitePawn;
8407         if(oldEP & EP_BEROLIN_A) {
8408                 captured = board[fromY][fromX-1];
8409                 board[fromY][fromX-1] = EmptySquare;
8410         }else{  captured = board[fromY][fromX+1];
8411                 board[fromY][fromX+1] = EmptySquare;
8412         }
8413     } else if (board[fromY][fromX] == king
8414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8415                && toY == fromY && toX > fromX+1) {
8416         board[fromY][fromX] = EmptySquare;
8417         board[toY][toX] = king;
8418         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8419         board[fromY][BOARD_RGHT-1] = EmptySquare;
8420     } else if (board[fromY][fromX] == king
8421         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8422                && toY == fromY && toX < fromX-1) {
8423         board[fromY][fromX] = EmptySquare;
8424         board[toY][toX] = king;
8425         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8426         board[fromY][BOARD_LEFT] = EmptySquare;
8427     } else if (fromY == 7 && fromX == 3
8428                && board[fromY][fromX] == BlackKing
8429                && toY == 7 && toX == 5) {
8430         board[fromY][fromX] = EmptySquare;
8431         board[toY][toX] = BlackKing;
8432         board[fromY][7] = EmptySquare;
8433         board[toY][4] = BlackRook;
8434     } else if (fromY == 7 && fromX == 3
8435                && board[fromY][fromX] == BlackKing
8436                && toY == 7 && toX == 1) {
8437         board[fromY][fromX] = EmptySquare;
8438         board[toY][toX] = BlackKing;
8439         board[fromY][0] = EmptySquare;
8440         board[toY][2] = BlackRook;
8441     } else if (board[fromY][fromX] == BlackPawn
8442                && toY < promoRank
8443                && gameInfo.variant != VariantXiangqi
8444                ) {
8445         /* black pawn promotion */
8446         board[toY][toX] = CharToPiece(ToLower(promoChar));
8447         if (board[toY][toX] == EmptySquare) {
8448             board[toY][toX] = BlackQueen;
8449         }
8450         if(gameInfo.variant==VariantBughouse ||
8451            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8452             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8453         board[fromY][fromX] = EmptySquare;
8454     } else if ((fromY == 3)
8455                && (toX != fromX)
8456                && gameInfo.variant != VariantXiangqi
8457                && gameInfo.variant != VariantBerolina
8458                && (board[fromY][fromX] == BlackPawn)
8459                && (board[toY][toX] == EmptySquare)) {
8460         board[fromY][fromX] = EmptySquare;
8461         board[toY][toX] = BlackPawn;
8462         captured = board[toY + 1][toX];
8463         board[toY + 1][toX] = EmptySquare;
8464     } else if ((fromY == 3)
8465                && (toX == fromX)
8466                && gameInfo.variant == VariantBerolina
8467                && (board[fromY][fromX] == BlackPawn)
8468                && (board[toY][toX] == EmptySquare)) {
8469         board[fromY][fromX] = EmptySquare;
8470         board[toY][toX] = BlackPawn;
8471         if(oldEP & EP_BEROLIN_A) {
8472                 captured = board[fromY][fromX-1];
8473                 board[fromY][fromX-1] = EmptySquare;
8474         }else{  captured = board[fromY][fromX+1];
8475                 board[fromY][fromX+1] = EmptySquare;
8476         }
8477     } else {
8478         board[toY][toX] = board[fromY][fromX];
8479         board[fromY][fromX] = EmptySquare;
8480     }
8481
8482     /* [HGM] now we promote for Shogi, if needed */
8483     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8484         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8485   }
8486
8487     if (gameInfo.holdingsWidth != 0) {
8488
8489       /* !!A lot more code needs to be written to support holdings  */
8490       /* [HGM] OK, so I have written it. Holdings are stored in the */
8491       /* penultimate board files, so they are automaticlly stored   */
8492       /* in the game history.                                       */
8493       if (fromY == DROP_RANK) {
8494         /* Delete from holdings, by decreasing count */
8495         /* and erasing image if necessary            */
8496         p = (int) fromX;
8497         if(p < (int) BlackPawn) { /* white drop */
8498              p -= (int)WhitePawn;
8499                  p = PieceToNumber((ChessSquare)p);
8500              if(p >= gameInfo.holdingsSize) p = 0;
8501              if(--board[p][BOARD_WIDTH-2] <= 0)
8502                   board[p][BOARD_WIDTH-1] = EmptySquare;
8503              if((int)board[p][BOARD_WIDTH-2] < 0)
8504                         board[p][BOARD_WIDTH-2] = 0;
8505         } else {                  /* black drop */
8506              p -= (int)BlackPawn;
8507                  p = PieceToNumber((ChessSquare)p);
8508              if(p >= gameInfo.holdingsSize) p = 0;
8509              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8510                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8511              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8512                         board[BOARD_HEIGHT-1-p][1] = 0;
8513         }
8514       }
8515       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8516           && gameInfo.variant != VariantBughouse        ) {
8517         /* [HGM] holdings: Add to holdings, if holdings exist */
8518         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8519                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8520                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8521         }
8522         p = (int) captured;
8523         if (p >= (int) BlackPawn) {
8524           p -= (int)BlackPawn;
8525           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8526                   /* in Shogi restore piece to its original  first */
8527                   captured = (ChessSquare) (DEMOTED captured);
8528                   p = DEMOTED p;
8529           }
8530           p = PieceToNumber((ChessSquare)p);
8531           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8532           board[p][BOARD_WIDTH-2]++;
8533           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8534         } else {
8535           p -= (int)WhitePawn;
8536           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8537                   captured = (ChessSquare) (DEMOTED captured);
8538                   p = DEMOTED p;
8539           }
8540           p = PieceToNumber((ChessSquare)p);
8541           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8542           board[BOARD_HEIGHT-1-p][1]++;
8543           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8544         }
8545       }
8546     } else if (gameInfo.variant == VariantAtomic) {
8547       if (captured != EmptySquare) {
8548         int y, x;
8549         for (y = toY-1; y <= toY+1; y++) {
8550           for (x = toX-1; x <= toX+1; x++) {
8551             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8552                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8553               board[y][x] = EmptySquare;
8554             }
8555           }
8556         }
8557         board[toY][toX] = EmptySquare;
8558       }
8559     }
8560     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8561         /* [HGM] Shogi promotions */
8562         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8563     }
8564
8565     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8566                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8567         // [HGM] superchess: take promotion piece out of holdings
8568         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8569         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8570             if(!--board[k][BOARD_WIDTH-2])
8571                 board[k][BOARD_WIDTH-1] = EmptySquare;
8572         } else {
8573             if(!--board[BOARD_HEIGHT-1-k][1])
8574                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8575         }
8576     }
8577
8578 }
8579
8580 /* Updates forwardMostMove */
8581 void
8582 MakeMove(fromX, fromY, toX, toY, promoChar)
8583      int fromX, fromY, toX, toY;
8584      int promoChar;
8585 {
8586 //    forwardMostMove++; // [HGM] bare: moved downstream
8587
8588     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8589         int timeLeft; static int lastLoadFlag=0; int king, piece;
8590         piece = boards[forwardMostMove][fromY][fromX];
8591         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8592         if(gameInfo.variant == VariantKnightmate)
8593             king += (int) WhiteUnicorn - (int) WhiteKing;
8594         if(forwardMostMove == 0) {
8595             if(blackPlaysFirst) 
8596                 fprintf(serverMoves, "%s;", second.tidy);
8597             fprintf(serverMoves, "%s;", first.tidy);
8598             if(!blackPlaysFirst) 
8599                 fprintf(serverMoves, "%s;", second.tidy);
8600         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8601         lastLoadFlag = loadFlag;
8602         // print base move
8603         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8604         // print castling suffix
8605         if( toY == fromY && piece == king ) {
8606             if(toX-fromX > 1)
8607                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8608             if(fromX-toX >1)
8609                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8610         }
8611         // e.p. suffix
8612         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8613              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8614              boards[forwardMostMove][toY][toX] == EmptySquare
8615              && fromX != toX && fromY != toY)
8616                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8617         // promotion suffix
8618         if(promoChar != NULLCHAR)
8619                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8620         if(!loadFlag) {
8621             fprintf(serverMoves, "/%d/%d",
8622                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8623             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8624             else                      timeLeft = blackTimeRemaining/1000;
8625             fprintf(serverMoves, "/%d", timeLeft);
8626         }
8627         fflush(serverMoves);
8628     }
8629
8630     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8631       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8632                         0, 1);
8633       return;
8634     }
8635     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8636     if (commentList[forwardMostMove+1] != NULL) {
8637         free(commentList[forwardMostMove+1]);
8638         commentList[forwardMostMove+1] = NULL;
8639     }
8640     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8642     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8643     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8644     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8645     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8646     gameInfo.result = GameUnfinished;
8647     if (gameInfo.resultDetails != NULL) {
8648         free(gameInfo.resultDetails);
8649         gameInfo.resultDetails = NULL;
8650     }
8651     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8652                               moveList[forwardMostMove - 1]);
8653     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8654                              PosFlags(forwardMostMove - 1),
8655                              fromY, fromX, toY, toX, promoChar,
8656                              parseList[forwardMostMove - 1]);
8657     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8658       case MT_NONE:
8659       case MT_STALEMATE:
8660       default:
8661         break;
8662       case MT_CHECK:
8663         if(gameInfo.variant != VariantShogi)
8664             strcat(parseList[forwardMostMove - 1], "+");
8665         break;
8666       case MT_CHECKMATE:
8667       case MT_STAINMATE:
8668         strcat(parseList[forwardMostMove - 1], "#");
8669         break;
8670     }
8671     if (appData.debugMode) {
8672         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8673     }
8674
8675 }
8676
8677 /* Updates currentMove if not pausing */
8678 void
8679 ShowMove(fromX, fromY, toX, toY)
8680 {
8681     int instant = (gameMode == PlayFromGameFile) ?
8682         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8683     if(appData.noGUI) return;
8684     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8685         if (!instant) {
8686             if (forwardMostMove == currentMove + 1) {
8687                 AnimateMove(boards[forwardMostMove - 1],
8688                             fromX, fromY, toX, toY);
8689             }
8690             if (appData.highlightLastMove) {
8691                 SetHighlights(fromX, fromY, toX, toY);
8692             }
8693         }
8694         currentMove = forwardMostMove;
8695     }
8696
8697     if (instant) return;
8698
8699     DisplayMove(currentMove - 1);
8700     DrawPosition(FALSE, boards[currentMove]);
8701     DisplayBothClocks();
8702     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8703 }
8704
8705 void SendEgtPath(ChessProgramState *cps)
8706 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8707         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8708
8709         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8710
8711         while(*p) {
8712             char c, *q = name+1, *r, *s;
8713
8714             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8715             while(*p && *p != ',') *q++ = *p++;
8716             *q++ = ':'; *q = 0;
8717             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8718                 strcmp(name, ",nalimov:") == 0 ) {
8719                 // take nalimov path from the menu-changeable option first, if it is defined
8720                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8721                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8722             } else
8723             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8724                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8725                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8726                 s = r = StrStr(s, ":") + 1; // beginning of path info
8727                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8728                 c = *r; *r = 0;             // temporarily null-terminate path info
8729                     *--q = 0;               // strip of trailig ':' from name
8730                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8731                 *r = c;
8732                 SendToProgram(buf,cps);     // send egtbpath command for this format
8733             }
8734             if(*p == ',') p++; // read away comma to position for next format name
8735         }
8736 }
8737
8738 void
8739 InitChessProgram(cps, setup)
8740      ChessProgramState *cps;
8741      int setup; /* [HGM] needed to setup FRC opening position */
8742 {
8743     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8744     if (appData.noChessProgram) return;
8745     hintRequested = FALSE;
8746     bookRequested = FALSE;
8747
8748     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8749     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8750     if(cps->memSize) { /* [HGM] memory */
8751         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8752         SendToProgram(buf, cps);
8753     }
8754     SendEgtPath(cps); /* [HGM] EGT */
8755     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8756         sprintf(buf, "cores %d\n", appData.smpCores);
8757         SendToProgram(buf, cps);
8758     }
8759
8760     SendToProgram(cps->initString, cps);
8761     if (gameInfo.variant != VariantNormal &&
8762         gameInfo.variant != VariantLoadable
8763         /* [HGM] also send variant if board size non-standard */
8764         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8765                                             ) {
8766       char *v = VariantName(gameInfo.variant);
8767       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8768         /* [HGM] in protocol 1 we have to assume all variants valid */
8769         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8770         DisplayFatalError(buf, 0, 1);
8771         return;
8772       }
8773
8774       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8775       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8776       if( gameInfo.variant == VariantXiangqi )
8777            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8778       if( gameInfo.variant == VariantShogi )
8779            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8780       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8781            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8782       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8783                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8784            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8785       if( gameInfo.variant == VariantCourier )
8786            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8787       if( gameInfo.variant == VariantSuper )
8788            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8789       if( gameInfo.variant == VariantGreat )
8790            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8791
8792       if(overruled) {
8793            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8794                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8795            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8796            if(StrStr(cps->variants, b) == NULL) { 
8797                // specific sized variant not known, check if general sizing allowed
8798                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8799                    if(StrStr(cps->variants, "boardsize") == NULL) {
8800                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8801                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8802                        DisplayFatalError(buf, 0, 1);
8803                        return;
8804                    }
8805                    /* [HGM] here we really should compare with the maximum supported board size */
8806                }
8807            }
8808       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8809       sprintf(buf, "variant %s\n", b);
8810       SendToProgram(buf, cps);
8811     }
8812     currentlyInitializedVariant = gameInfo.variant;
8813
8814     /* [HGM] send opening position in FRC to first engine */
8815     if(setup) {
8816           SendToProgram("force\n", cps);
8817           SendBoard(cps, 0);
8818           /* engine is now in force mode! Set flag to wake it up after first move. */
8819           setboardSpoiledMachineBlack = 1;
8820     }
8821
8822     if (cps->sendICS) {
8823       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8824       SendToProgram(buf, cps);
8825     }
8826     cps->maybeThinking = FALSE;
8827     cps->offeredDraw = 0;
8828     if (!appData.icsActive) {
8829         SendTimeControl(cps, movesPerSession, timeControl,
8830                         timeIncrement, appData.searchDepth,
8831                         searchTime);
8832     }
8833     if (appData.showThinking 
8834         // [HGM] thinking: four options require thinking output to be sent
8835         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8836                                 ) {
8837         SendToProgram("post\n", cps);
8838     }
8839     SendToProgram("hard\n", cps);
8840     if (!appData.ponderNextMove) {
8841         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8842            it without being sure what state we are in first.  "hard"
8843            is not a toggle, so that one is OK.
8844          */
8845         SendToProgram("easy\n", cps);
8846     }
8847     if (cps->usePing) {
8848       sprintf(buf, "ping %d\n", ++cps->lastPing);
8849       SendToProgram(buf, cps);
8850     }
8851     cps->initDone = TRUE;
8852 }   
8853
8854
8855 void
8856 StartChessProgram(cps)
8857      ChessProgramState *cps;
8858 {
8859     char buf[MSG_SIZ];
8860     int err;
8861
8862     if (appData.noChessProgram) return;
8863     cps->initDone = FALSE;
8864
8865     if (strcmp(cps->host, "localhost") == 0) {
8866         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8867     } else if (*appData.remoteShell == NULLCHAR) {
8868         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8869     } else {
8870         if (*appData.remoteUser == NULLCHAR) {
8871           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8872                     cps->program);
8873         } else {
8874           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8875                     cps->host, appData.remoteUser, cps->program);
8876         }
8877         err = StartChildProcess(buf, "", &cps->pr);
8878     }
8879     
8880     if (err != 0) {
8881         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8882         DisplayFatalError(buf, err, 1);
8883         cps->pr = NoProc;
8884         cps->isr = NULL;
8885         return;
8886     }
8887     
8888     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8889     if (cps->protocolVersion > 1) {
8890       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8891       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8892       cps->comboCnt = 0;  //                and values of combo boxes
8893       SendToProgram(buf, cps);
8894     } else {
8895       SendToProgram("xboard\n", cps);
8896     }
8897 }
8898
8899
8900 void
8901 TwoMachinesEventIfReady P((void))
8902 {
8903   if (first.lastPing != first.lastPong) {
8904     DisplayMessage("", _("Waiting for first chess program"));
8905     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8906     return;
8907   }
8908   if (second.lastPing != second.lastPong) {
8909     DisplayMessage("", _("Waiting for second chess program"));
8910     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8911     return;
8912   }
8913   ThawUI();
8914   TwoMachinesEvent();
8915 }
8916
8917 void
8918 NextMatchGame P((void))
8919 {
8920     int index; /* [HGM] autoinc: step load index during match */
8921     Reset(FALSE, TRUE);
8922     if (*appData.loadGameFile != NULLCHAR) {
8923         index = appData.loadGameIndex;
8924         if(index < 0) { // [HGM] autoinc
8925             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8926             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8927         } 
8928         LoadGameFromFile(appData.loadGameFile,
8929                          index,
8930                          appData.loadGameFile, FALSE);
8931     } else if (*appData.loadPositionFile != NULLCHAR) {
8932         index = appData.loadPositionIndex;
8933         if(index < 0) { // [HGM] autoinc
8934             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8935             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8936         } 
8937         LoadPositionFromFile(appData.loadPositionFile,
8938                              index,
8939                              appData.loadPositionFile);
8940     }
8941     TwoMachinesEventIfReady();
8942 }
8943
8944 void UserAdjudicationEvent( int result )
8945 {
8946     ChessMove gameResult = GameIsDrawn;
8947
8948     if( result > 0 ) {
8949         gameResult = WhiteWins;
8950     }
8951     else if( result < 0 ) {
8952         gameResult = BlackWins;
8953     }
8954
8955     if( gameMode == TwoMachinesPlay ) {
8956         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8957     }
8958 }
8959
8960
8961 // [HGM] save: calculate checksum of game to make games easily identifiable
8962 int StringCheckSum(char *s)
8963 {
8964         int i = 0;
8965         if(s==NULL) return 0;
8966         while(*s) i = i*259 + *s++;
8967         return i;
8968 }
8969
8970 int GameCheckSum()
8971 {
8972         int i, sum=0;
8973         for(i=backwardMostMove; i<forwardMostMove; i++) {
8974                 sum += pvInfoList[i].depth;
8975                 sum += StringCheckSum(parseList[i]);
8976                 sum += StringCheckSum(commentList[i]);
8977                 sum *= 261;
8978         }
8979         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8980         return sum + StringCheckSum(commentList[i]);
8981 } // end of save patch
8982
8983 void
8984 GameEnds(result, resultDetails, whosays)
8985      ChessMove result;
8986      char *resultDetails;
8987      int whosays;
8988 {
8989     GameMode nextGameMode;
8990     int isIcsGame;
8991     char buf[MSG_SIZ], popupRequested = 0;
8992
8993     if(endingGame) return; /* [HGM] crash: forbid recursion */
8994     endingGame = 1;
8995     if(twoBoards) { // [HGM] dual: switch back to one board
8996         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8997         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8998     }
8999     if (appData.debugMode) {
9000       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9001               result, resultDetails ? resultDetails : "(null)", whosays);
9002     }
9003
9004     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9005
9006     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9007         /* If we are playing on ICS, the server decides when the
9008            game is over, but the engine can offer to draw, claim 
9009            a draw, or resign. 
9010          */
9011 #if ZIPPY
9012         if (appData.zippyPlay && first.initDone) {
9013             if (result == GameIsDrawn) {
9014                 /* In case draw still needs to be claimed */
9015                 SendToICS(ics_prefix);
9016                 SendToICS("draw\n");
9017             } else if (StrCaseStr(resultDetails, "resign")) {
9018                 SendToICS(ics_prefix);
9019                 SendToICS("resign\n");
9020             }
9021         }
9022 #endif
9023         endingGame = 0; /* [HGM] crash */
9024         return;
9025     }
9026
9027     /* If we're loading the game from a file, stop */
9028     if (whosays == GE_FILE) {
9029       (void) StopLoadGameTimer();
9030       gameFileFP = NULL;
9031     }
9032
9033     /* Cancel draw offers */
9034     first.offeredDraw = second.offeredDraw = 0;
9035
9036     /* If this is an ICS game, only ICS can really say it's done;
9037        if not, anyone can. */
9038     isIcsGame = (gameMode == IcsPlayingWhite || 
9039                  gameMode == IcsPlayingBlack || 
9040                  gameMode == IcsObserving    || 
9041                  gameMode == IcsExamining);
9042
9043     if (!isIcsGame || whosays == GE_ICS) {
9044         /* OK -- not an ICS game, or ICS said it was done */
9045         StopClocks();
9046         if (!isIcsGame && !appData.noChessProgram) 
9047           SetUserThinkingEnables();
9048     
9049         /* [HGM] if a machine claims the game end we verify this claim */
9050         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9051             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9052                 char claimer;
9053                 ChessMove trueResult = (ChessMove) -1;
9054
9055                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9056                                             first.twoMachinesColor[0] :
9057                                             second.twoMachinesColor[0] ;
9058
9059                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9060                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9061                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9062                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9063                 } else
9064                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9065                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9066                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9067                 } else
9068                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9069                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9070                 }
9071
9072                 // now verify win claims, but not in drop games, as we don't understand those yet
9073                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9074                                                  || gameInfo.variant == VariantGreat) &&
9075                     (result == WhiteWins && claimer == 'w' ||
9076                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9077                       if (appData.debugMode) {
9078                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9079                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9080                       }
9081                       if(result != trueResult) {
9082                               sprintf(buf, "False win claim: '%s'", resultDetails);
9083                               result = claimer == 'w' ? BlackWins : WhiteWins;
9084                               resultDetails = buf;
9085                       }
9086                 } else
9087                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9088                     && (forwardMostMove <= backwardMostMove ||
9089                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9090                         (claimer=='b')==(forwardMostMove&1))
9091                                                                                   ) {
9092                       /* [HGM] verify: draws that were not flagged are false claims */
9093                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9094                       result = claimer == 'w' ? BlackWins : WhiteWins;
9095                       resultDetails = buf;
9096                 }
9097                 /* (Claiming a loss is accepted no questions asked!) */
9098             }
9099             /* [HGM] bare: don't allow bare King to win */
9100             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9101                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9102                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9103                && result != GameIsDrawn)
9104             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9105                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9106                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9107                         if(p >= 0 && p <= (int)WhiteKing) k++;
9108                 }
9109                 if (appData.debugMode) {
9110                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9111                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9112                 }
9113                 if(k <= 1) {
9114                         result = GameIsDrawn;
9115                         sprintf(buf, "%s but bare king", resultDetails);
9116                         resultDetails = buf;
9117                 }
9118             }
9119         }
9120
9121
9122         if(serverMoves != NULL && !loadFlag) { char c = '=';
9123             if(result==WhiteWins) c = '+';
9124             if(result==BlackWins) c = '-';
9125             if(resultDetails != NULL)
9126                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9127         }
9128         if (resultDetails != NULL) {
9129             gameInfo.result = result;
9130             gameInfo.resultDetails = StrSave(resultDetails);
9131
9132             /* display last move only if game was not loaded from file */
9133             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9134                 DisplayMove(currentMove - 1);
9135     
9136             if (forwardMostMove != 0) {
9137                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9138                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9139                                                                 ) {
9140                     if (*appData.saveGameFile != NULLCHAR) {
9141                         SaveGameToFile(appData.saveGameFile, TRUE);
9142                     } else if (appData.autoSaveGames) {
9143                         AutoSaveGame();
9144                     }
9145                     if (*appData.savePositionFile != NULLCHAR) {
9146                         SavePositionToFile(appData.savePositionFile);
9147                     }
9148                 }
9149             }
9150
9151             /* Tell program how game ended in case it is learning */
9152             /* [HGM] Moved this to after saving the PGN, just in case */
9153             /* engine died and we got here through time loss. In that */
9154             /* case we will get a fatal error writing the pipe, which */
9155             /* would otherwise lose us the PGN.                       */
9156             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9157             /* output during GameEnds should never be fatal anymore   */
9158             if (gameMode == MachinePlaysWhite ||
9159                 gameMode == MachinePlaysBlack ||
9160                 gameMode == TwoMachinesPlay ||
9161                 gameMode == IcsPlayingWhite ||
9162                 gameMode == IcsPlayingBlack ||
9163                 gameMode == BeginningOfGame) {
9164                 char buf[MSG_SIZ];
9165                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9166                         resultDetails);
9167                 if (first.pr != NoProc) {
9168                     SendToProgram(buf, &first);
9169                 }
9170                 if (second.pr != NoProc &&
9171                     gameMode == TwoMachinesPlay) {
9172                     SendToProgram(buf, &second);
9173                 }
9174             }
9175         }
9176
9177         if (appData.icsActive) {
9178             if (appData.quietPlay &&
9179                 (gameMode == IcsPlayingWhite ||
9180                  gameMode == IcsPlayingBlack)) {
9181                 SendToICS(ics_prefix);
9182                 SendToICS("set shout 1\n");
9183             }
9184             nextGameMode = IcsIdle;
9185             ics_user_moved = FALSE;
9186             /* clean up premove.  It's ugly when the game has ended and the
9187              * premove highlights are still on the board.
9188              */
9189             if (gotPremove) {
9190               gotPremove = FALSE;
9191               ClearPremoveHighlights();
9192               DrawPosition(FALSE, boards[currentMove]);
9193             }
9194             if (whosays == GE_ICS) {
9195                 switch (result) {
9196                 case WhiteWins:
9197                     if (gameMode == IcsPlayingWhite)
9198                         PlayIcsWinSound();
9199                     else if(gameMode == IcsPlayingBlack)
9200                         PlayIcsLossSound();
9201                     break;
9202                 case BlackWins:
9203                     if (gameMode == IcsPlayingBlack)
9204                         PlayIcsWinSound();
9205                     else if(gameMode == IcsPlayingWhite)
9206                         PlayIcsLossSound();
9207                     break;
9208                 case GameIsDrawn:
9209                     PlayIcsDrawSound();
9210                     break;
9211                 default:
9212                     PlayIcsUnfinishedSound();
9213                 }
9214             }
9215         } else if (gameMode == EditGame ||
9216                    gameMode == PlayFromGameFile || 
9217                    gameMode == AnalyzeMode || 
9218                    gameMode == AnalyzeFile) {
9219             nextGameMode = gameMode;
9220         } else {
9221             nextGameMode = EndOfGame;
9222         }
9223         pausing = FALSE;
9224         ModeHighlight();
9225     } else {
9226         nextGameMode = gameMode;
9227     }
9228
9229     if (appData.noChessProgram) {
9230         gameMode = nextGameMode;
9231         ModeHighlight();
9232         endingGame = 0; /* [HGM] crash */
9233         return;
9234     }
9235
9236     if (first.reuse) {
9237         /* Put first chess program into idle state */
9238         if (first.pr != NoProc &&
9239             (gameMode == MachinePlaysWhite ||
9240              gameMode == MachinePlaysBlack ||
9241              gameMode == TwoMachinesPlay ||
9242              gameMode == IcsPlayingWhite ||
9243              gameMode == IcsPlayingBlack ||
9244              gameMode == BeginningOfGame)) {
9245             SendToProgram("force\n", &first);
9246             if (first.usePing) {
9247               char buf[MSG_SIZ];
9248               sprintf(buf, "ping %d\n", ++first.lastPing);
9249               SendToProgram(buf, &first);
9250             }
9251         }
9252     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9253         /* Kill off first chess program */
9254         if (first.isr != NULL)
9255           RemoveInputSource(first.isr);
9256         first.isr = NULL;
9257     
9258         if (first.pr != NoProc) {
9259             ExitAnalyzeMode();
9260             DoSleep( appData.delayBeforeQuit );
9261             SendToProgram("quit\n", &first);
9262             DoSleep( appData.delayAfterQuit );
9263             DestroyChildProcess(first.pr, first.useSigterm);
9264         }
9265         first.pr = NoProc;
9266     }
9267     if (second.reuse) {
9268         /* Put second chess program into idle state */
9269         if (second.pr != NoProc &&
9270             gameMode == TwoMachinesPlay) {
9271             SendToProgram("force\n", &second);
9272             if (second.usePing) {
9273               char buf[MSG_SIZ];
9274               sprintf(buf, "ping %d\n", ++second.lastPing);
9275               SendToProgram(buf, &second);
9276             }
9277         }
9278     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9279         /* Kill off second chess program */
9280         if (second.isr != NULL)
9281           RemoveInputSource(second.isr);
9282         second.isr = NULL;
9283     
9284         if (second.pr != NoProc) {
9285             DoSleep( appData.delayBeforeQuit );
9286             SendToProgram("quit\n", &second);
9287             DoSleep( appData.delayAfterQuit );
9288             DestroyChildProcess(second.pr, second.useSigterm);
9289         }
9290         second.pr = NoProc;
9291     }
9292
9293     if (matchMode && gameMode == TwoMachinesPlay) {
9294         switch (result) {
9295         case WhiteWins:
9296           if (first.twoMachinesColor[0] == 'w') {
9297             first.matchWins++;
9298           } else {
9299             second.matchWins++;
9300           }
9301           break;
9302         case BlackWins:
9303           if (first.twoMachinesColor[0] == 'b') {
9304             first.matchWins++;
9305           } else {
9306             second.matchWins++;
9307           }
9308           break;
9309         default:
9310           break;
9311         }
9312         if (matchGame < appData.matchGames) {
9313             char *tmp;
9314             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9315                 tmp = first.twoMachinesColor;
9316                 first.twoMachinesColor = second.twoMachinesColor;
9317                 second.twoMachinesColor = tmp;
9318             }
9319             gameMode = nextGameMode;
9320             matchGame++;
9321             if(appData.matchPause>10000 || appData.matchPause<10)
9322                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9323             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9324             endingGame = 0; /* [HGM] crash */
9325             return;
9326         } else {
9327             char buf[MSG_SIZ];
9328             gameMode = nextGameMode;
9329             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9330                     first.tidy, second.tidy,
9331                     first.matchWins, second.matchWins,
9332                     appData.matchGames - (first.matchWins + second.matchWins));
9333             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9334         }
9335     }
9336     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9337         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9338       ExitAnalyzeMode();
9339     gameMode = nextGameMode;
9340     ModeHighlight();
9341     endingGame = 0;  /* [HGM] crash */
9342     if(popupRequested) DisplayFatalError(buf, 0, 0); // [HGM] crash: this call GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9343 }
9344
9345 /* Assumes program was just initialized (initString sent).
9346    Leaves program in force mode. */
9347 void
9348 FeedMovesToProgram(cps, upto) 
9349      ChessProgramState *cps;
9350      int upto;
9351 {
9352     int i;
9353     
9354     if (appData.debugMode)
9355       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9356               startedFromSetupPosition ? "position and " : "",
9357               backwardMostMove, upto, cps->which);
9358     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9359         // [HGM] variantswitch: make engine aware of new variant
9360         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9361                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9362         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9363         SendToProgram(buf, cps);
9364         currentlyInitializedVariant = gameInfo.variant;
9365     }
9366     SendToProgram("force\n", cps);
9367     if (startedFromSetupPosition) {
9368         SendBoard(cps, backwardMostMove);
9369     if (appData.debugMode) {
9370         fprintf(debugFP, "feedMoves\n");
9371     }
9372     }
9373     for (i = backwardMostMove; i < upto; i++) {
9374         SendMoveToProgram(i, cps);
9375     }
9376 }
9377
9378
9379 void
9380 ResurrectChessProgram()
9381 {
9382      /* The chess program may have exited.
9383         If so, restart it and feed it all the moves made so far. */
9384
9385     if (appData.noChessProgram || first.pr != NoProc) return;
9386     
9387     StartChessProgram(&first);
9388     InitChessProgram(&first, FALSE);
9389     FeedMovesToProgram(&first, currentMove);
9390
9391     if (!first.sendTime) {
9392         /* can't tell gnuchess what its clock should read,
9393            so we bow to its notion. */
9394         ResetClocks();
9395         timeRemaining[0][currentMove] = whiteTimeRemaining;
9396         timeRemaining[1][currentMove] = blackTimeRemaining;
9397     }
9398
9399     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9400                 appData.icsEngineAnalyze) && first.analysisSupport) {
9401       SendToProgram("analyze\n", &first);
9402       first.analyzing = TRUE;
9403     }
9404 }
9405
9406 /*
9407  * Button procedures
9408  */
9409 void
9410 Reset(redraw, init)
9411      int redraw, init;
9412 {
9413     int i;
9414
9415     if (appData.debugMode) {
9416         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9417                 redraw, init, gameMode);
9418     }
9419     CleanupTail(); // [HGM] vari: delete any stored variations
9420     pausing = pauseExamInvalid = FALSE;
9421     startedFromSetupPosition = blackPlaysFirst = FALSE;
9422     firstMove = TRUE;
9423     whiteFlag = blackFlag = FALSE;
9424     userOfferedDraw = FALSE;
9425     hintRequested = bookRequested = FALSE;
9426     first.maybeThinking = FALSE;
9427     second.maybeThinking = FALSE;
9428     first.bookSuspend = FALSE; // [HGM] book
9429     second.bookSuspend = FALSE;
9430     thinkOutput[0] = NULLCHAR;
9431     lastHint[0] = NULLCHAR;
9432     ClearGameInfo(&gameInfo);
9433     gameInfo.variant = StringToVariant(appData.variant);
9434     ics_user_moved = ics_clock_paused = FALSE;
9435     ics_getting_history = H_FALSE;
9436     ics_gamenum = -1;
9437     white_holding[0] = black_holding[0] = NULLCHAR;
9438     ClearProgramStats();
9439     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9440     
9441     ResetFrontEnd();
9442     ClearHighlights();
9443     flipView = appData.flipView;
9444     ClearPremoveHighlights();
9445     gotPremove = FALSE;
9446     alarmSounded = FALSE;
9447
9448     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9449     if(appData.serverMovesName != NULL) {
9450         /* [HGM] prepare to make moves file for broadcasting */
9451         clock_t t = clock();
9452         if(serverMoves != NULL) fclose(serverMoves);
9453         serverMoves = fopen(appData.serverMovesName, "r");
9454         if(serverMoves != NULL) {
9455             fclose(serverMoves);
9456             /* delay 15 sec before overwriting, so all clients can see end */
9457             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9458         }
9459         serverMoves = fopen(appData.serverMovesName, "w");
9460     }
9461
9462     ExitAnalyzeMode();
9463     gameMode = BeginningOfGame;
9464     ModeHighlight();
9465     if(appData.icsActive) gameInfo.variant = VariantNormal;
9466     currentMove = forwardMostMove = backwardMostMove = 0;
9467     InitPosition(redraw);
9468     for (i = 0; i < MAX_MOVES; i++) {
9469         if (commentList[i] != NULL) {
9470             free(commentList[i]);
9471             commentList[i] = NULL;
9472         }
9473     }
9474     ResetClocks();
9475     timeRemaining[0][0] = whiteTimeRemaining;
9476     timeRemaining[1][0] = blackTimeRemaining;
9477     if (first.pr == NULL) {
9478         StartChessProgram(&first);
9479     }
9480     if (init) {
9481             InitChessProgram(&first, startedFromSetupPosition);
9482     }
9483     DisplayTitle("");
9484     DisplayMessage("", "");
9485     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9486     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9487 }
9488
9489 void
9490 AutoPlayGameLoop()
9491 {
9492     for (;;) {
9493         if (!AutoPlayOneMove())
9494           return;
9495         if (matchMode || appData.timeDelay == 0)
9496           continue;
9497         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9498           return;
9499         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9500         break;
9501     }
9502 }
9503
9504
9505 int
9506 AutoPlayOneMove()
9507 {
9508     int fromX, fromY, toX, toY;
9509
9510     if (appData.debugMode) {
9511       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9512     }
9513
9514     if (gameMode != PlayFromGameFile)
9515       return FALSE;
9516
9517     if (currentMove >= forwardMostMove) {
9518       gameMode = EditGame;
9519       ModeHighlight();
9520
9521       /* [AS] Clear current move marker at the end of a game */
9522       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9523
9524       return FALSE;
9525     }
9526     
9527     toX = moveList[currentMove][2] - AAA;
9528     toY = moveList[currentMove][3] - ONE;
9529
9530     if (moveList[currentMove][1] == '@') {
9531         if (appData.highlightLastMove) {
9532             SetHighlights(-1, -1, toX, toY);
9533         }
9534     } else {
9535         fromX = moveList[currentMove][0] - AAA;
9536         fromY = moveList[currentMove][1] - ONE;
9537
9538         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9539
9540         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9541
9542         if (appData.highlightLastMove) {
9543             SetHighlights(fromX, fromY, toX, toY);
9544         }
9545     }
9546     DisplayMove(currentMove);
9547     SendMoveToProgram(currentMove++, &first);
9548     DisplayBothClocks();
9549     DrawPosition(FALSE, boards[currentMove]);
9550     // [HGM] PV info: always display, routine tests if empty
9551     DisplayComment(currentMove - 1, commentList[currentMove]);
9552     return TRUE;
9553 }
9554
9555
9556 int
9557 LoadGameOneMove(readAhead)
9558      ChessMove readAhead;
9559 {
9560     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9561     char promoChar = NULLCHAR;
9562     ChessMove moveType;
9563     char move[MSG_SIZ];
9564     char *p, *q;
9565     
9566     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9567         gameMode != AnalyzeMode && gameMode != Training) {
9568         gameFileFP = NULL;
9569         return FALSE;
9570     }
9571     
9572     yyboardindex = forwardMostMove;
9573     if (readAhead != (ChessMove)0) {
9574       moveType = readAhead;
9575     } else {
9576       if (gameFileFP == NULL)
9577           return FALSE;
9578       moveType = (ChessMove) yylex();
9579     }
9580     
9581     done = FALSE;
9582     switch (moveType) {
9583       case Comment:
9584         if (appData.debugMode) 
9585           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9586         p = yy_text;
9587
9588         /* append the comment but don't display it */
9589         AppendComment(currentMove, p, FALSE);
9590         return TRUE;
9591
9592       case WhiteCapturesEnPassant:
9593       case BlackCapturesEnPassant:
9594       case WhitePromotionChancellor:
9595       case BlackPromotionChancellor:
9596       case WhitePromotionArchbishop:
9597       case BlackPromotionArchbishop:
9598       case WhitePromotionCentaur:
9599       case BlackPromotionCentaur:
9600       case WhitePromotionQueen:
9601       case BlackPromotionQueen:
9602       case WhitePromotionRook:
9603       case BlackPromotionRook:
9604       case WhitePromotionBishop:
9605       case BlackPromotionBishop:
9606       case WhitePromotionKnight:
9607       case BlackPromotionKnight:
9608       case WhitePromotionKing:
9609       case BlackPromotionKing:
9610       case NormalMove:
9611       case WhiteKingSideCastle:
9612       case WhiteQueenSideCastle:
9613       case BlackKingSideCastle:
9614       case BlackQueenSideCastle:
9615       case WhiteKingSideCastleWild:
9616       case WhiteQueenSideCastleWild:
9617       case BlackKingSideCastleWild:
9618       case BlackQueenSideCastleWild:
9619       /* PUSH Fabien */
9620       case WhiteHSideCastleFR:
9621       case WhiteASideCastleFR:
9622       case BlackHSideCastleFR:
9623       case BlackASideCastleFR:
9624       /* POP Fabien */
9625         if (appData.debugMode)
9626           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9627         fromX = currentMoveString[0] - AAA;
9628         fromY = currentMoveString[1] - ONE;
9629         toX = currentMoveString[2] - AAA;
9630         toY = currentMoveString[3] - ONE;
9631         promoChar = currentMoveString[4];
9632         break;
9633
9634       case WhiteDrop:
9635       case BlackDrop:
9636         if (appData.debugMode)
9637           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9638         fromX = moveType == WhiteDrop ?
9639           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9640         (int) CharToPiece(ToLower(currentMoveString[0]));
9641         fromY = DROP_RANK;
9642         toX = currentMoveString[2] - AAA;
9643         toY = currentMoveString[3] - ONE;
9644         break;
9645
9646       case WhiteWins:
9647       case BlackWins:
9648       case GameIsDrawn:
9649       case GameUnfinished:
9650         if (appData.debugMode)
9651           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9652         p = strchr(yy_text, '{');
9653         if (p == NULL) p = strchr(yy_text, '(');
9654         if (p == NULL) {
9655             p = yy_text;
9656             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9657         } else {
9658             q = strchr(p, *p == '{' ? '}' : ')');
9659             if (q != NULL) *q = NULLCHAR;
9660             p++;
9661         }
9662         GameEnds(moveType, p, GE_FILE);
9663         done = TRUE;
9664         if (cmailMsgLoaded) {
9665             ClearHighlights();
9666             flipView = WhiteOnMove(currentMove);
9667             if (moveType == GameUnfinished) flipView = !flipView;
9668             if (appData.debugMode)
9669               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9670         }
9671         break;
9672
9673       case (ChessMove) 0:       /* end of file */
9674         if (appData.debugMode)
9675           fprintf(debugFP, "Parser hit end of file\n");
9676         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9677           case MT_NONE:
9678           case MT_CHECK:
9679             break;
9680           case MT_CHECKMATE:
9681           case MT_STAINMATE:
9682             if (WhiteOnMove(currentMove)) {
9683                 GameEnds(BlackWins, "Black mates", GE_FILE);
9684             } else {
9685                 GameEnds(WhiteWins, "White mates", GE_FILE);
9686             }
9687             break;
9688           case MT_STALEMATE:
9689             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9690             break;
9691         }
9692         done = TRUE;
9693         break;
9694
9695       case MoveNumberOne:
9696         if (lastLoadGameStart == GNUChessGame) {
9697             /* GNUChessGames have numbers, but they aren't move numbers */
9698             if (appData.debugMode)
9699               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9700                       yy_text, (int) moveType);
9701             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9702         }
9703         /* else fall thru */
9704
9705       case XBoardGame:
9706       case GNUChessGame:
9707       case PGNTag:
9708         /* Reached start of next game in file */
9709         if (appData.debugMode)
9710           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9711         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9712           case MT_NONE:
9713           case MT_CHECK:
9714             break;
9715           case MT_CHECKMATE:
9716           case MT_STAINMATE:
9717             if (WhiteOnMove(currentMove)) {
9718                 GameEnds(BlackWins, "Black mates", GE_FILE);
9719             } else {
9720                 GameEnds(WhiteWins, "White mates", GE_FILE);
9721             }
9722             break;
9723           case MT_STALEMATE:
9724             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9725             break;
9726         }
9727         done = TRUE;
9728         break;
9729
9730       case PositionDiagram:     /* should not happen; ignore */
9731       case ElapsedTime:         /* ignore */
9732       case NAG:                 /* ignore */
9733         if (appData.debugMode)
9734           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9735                   yy_text, (int) moveType);
9736         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9737
9738       case IllegalMove:
9739         if (appData.testLegality) {
9740             if (appData.debugMode)
9741               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9742             sprintf(move, _("Illegal move: %d.%s%s"),
9743                     (forwardMostMove / 2) + 1,
9744                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9745             DisplayError(move, 0);
9746             done = TRUE;
9747         } else {
9748             if (appData.debugMode)
9749               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9750                       yy_text, currentMoveString);
9751             fromX = currentMoveString[0] - AAA;
9752             fromY = currentMoveString[1] - ONE;
9753             toX = currentMoveString[2] - AAA;
9754             toY = currentMoveString[3] - ONE;
9755             promoChar = currentMoveString[4];
9756         }
9757         break;
9758
9759       case AmbiguousMove:
9760         if (appData.debugMode)
9761           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9762         sprintf(move, _("Ambiguous move: %d.%s%s"),
9763                 (forwardMostMove / 2) + 1,
9764                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9765         DisplayError(move, 0);
9766         done = TRUE;
9767         break;
9768
9769       default:
9770       case ImpossibleMove:
9771         if (appData.debugMode)
9772           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9773         sprintf(move, _("Illegal move: %d.%s%s"),
9774                 (forwardMostMove / 2) + 1,
9775                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9776         DisplayError(move, 0);
9777         done = TRUE;
9778         break;
9779     }
9780
9781     if (done) {
9782         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9783             DrawPosition(FALSE, boards[currentMove]);
9784             DisplayBothClocks();
9785             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9786               DisplayComment(currentMove - 1, commentList[currentMove]);
9787         }
9788         (void) StopLoadGameTimer();
9789         gameFileFP = NULL;
9790         cmailOldMove = forwardMostMove;
9791         return FALSE;
9792     } else {
9793         /* currentMoveString is set as a side-effect of yylex */
9794         strcat(currentMoveString, "\n");
9795         strcpy(moveList[forwardMostMove], currentMoveString);
9796         
9797         thinkOutput[0] = NULLCHAR;
9798         MakeMove(fromX, fromY, toX, toY, promoChar);
9799         currentMove = forwardMostMove;
9800         return TRUE;
9801     }
9802 }
9803
9804 /* Load the nth game from the given file */
9805 int
9806 LoadGameFromFile(filename, n, title, useList)
9807      char *filename;
9808      int n;
9809      char *title;
9810      /*Boolean*/ int useList;
9811 {
9812     FILE *f;
9813     char buf[MSG_SIZ];
9814
9815     if (strcmp(filename, "-") == 0) {
9816         f = stdin;
9817         title = "stdin";
9818     } else {
9819         f = fopen(filename, "rb");
9820         if (f == NULL) {
9821           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9822             DisplayError(buf, errno);
9823             return FALSE;
9824         }
9825     }
9826     if (fseek(f, 0, 0) == -1) {
9827         /* f is not seekable; probably a pipe */
9828         useList = FALSE;
9829     }
9830     if (useList && n == 0) {
9831         int error = GameListBuild(f);
9832         if (error) {
9833             DisplayError(_("Cannot build game list"), error);
9834         } else if (!ListEmpty(&gameList) &&
9835                    ((ListGame *) gameList.tailPred)->number > 1) {
9836             GameListPopUp(f, title);
9837             return TRUE;
9838         }
9839         GameListDestroy();
9840         n = 1;
9841     }
9842     if (n == 0) n = 1;
9843     return LoadGame(f, n, title, FALSE);
9844 }
9845
9846
9847 void
9848 MakeRegisteredMove()
9849 {
9850     int fromX, fromY, toX, toY;
9851     char promoChar;
9852     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9853         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9854           case CMAIL_MOVE:
9855           case CMAIL_DRAW:
9856             if (appData.debugMode)
9857               fprintf(debugFP, "Restoring %s for game %d\n",
9858                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9859     
9860             thinkOutput[0] = NULLCHAR;
9861             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9862             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9863             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9864             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9865             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9866             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9867             MakeMove(fromX, fromY, toX, toY, promoChar);
9868             ShowMove(fromX, fromY, toX, toY);
9869               
9870             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9871               case MT_NONE:
9872               case MT_CHECK:
9873                 break;
9874                 
9875               case MT_CHECKMATE:
9876               case MT_STAINMATE:
9877                 if (WhiteOnMove(currentMove)) {
9878                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9879                 } else {
9880                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9881                 }
9882                 break;
9883                 
9884               case MT_STALEMATE:
9885                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9886                 break;
9887             }
9888
9889             break;
9890             
9891           case CMAIL_RESIGN:
9892             if (WhiteOnMove(currentMove)) {
9893                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9894             } else {
9895                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9896             }
9897             break;
9898             
9899           case CMAIL_ACCEPT:
9900             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9901             break;
9902               
9903           default:
9904             break;
9905         }
9906     }
9907
9908     return;
9909 }
9910
9911 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9912 int
9913 CmailLoadGame(f, gameNumber, title, useList)
9914      FILE *f;
9915      int gameNumber;
9916      char *title;
9917      int useList;
9918 {
9919     int retVal;
9920
9921     if (gameNumber > nCmailGames) {
9922         DisplayError(_("No more games in this message"), 0);
9923         return FALSE;
9924     }
9925     if (f == lastLoadGameFP) {
9926         int offset = gameNumber - lastLoadGameNumber;
9927         if (offset == 0) {
9928             cmailMsg[0] = NULLCHAR;
9929             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9930                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9931                 nCmailMovesRegistered--;
9932             }
9933             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9934             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9935                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9936             }
9937         } else {
9938             if (! RegisterMove()) return FALSE;
9939         }
9940     }
9941
9942     retVal = LoadGame(f, gameNumber, title, useList);
9943
9944     /* Make move registered during previous look at this game, if any */
9945     MakeRegisteredMove();
9946
9947     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9948         commentList[currentMove]
9949           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9950         DisplayComment(currentMove - 1, commentList[currentMove]);
9951     }
9952
9953     return retVal;
9954 }
9955
9956 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9957 int
9958 ReloadGame(offset)
9959      int offset;
9960 {
9961     int gameNumber = lastLoadGameNumber + offset;
9962     if (lastLoadGameFP == NULL) {
9963         DisplayError(_("No game has been loaded yet"), 0);
9964         return FALSE;
9965     }
9966     if (gameNumber <= 0) {
9967         DisplayError(_("Can't back up any further"), 0);
9968         return FALSE;
9969     }
9970     if (cmailMsgLoaded) {
9971         return CmailLoadGame(lastLoadGameFP, gameNumber,
9972                              lastLoadGameTitle, lastLoadGameUseList);
9973     } else {
9974         return LoadGame(lastLoadGameFP, gameNumber,
9975                         lastLoadGameTitle, lastLoadGameUseList);
9976     }
9977 }
9978
9979
9980
9981 /* Load the nth game from open file f */
9982 int
9983 LoadGame(f, gameNumber, title, useList)
9984      FILE *f;
9985      int gameNumber;
9986      char *title;
9987      int useList;
9988 {
9989     ChessMove cm;
9990     char buf[MSG_SIZ];
9991     int gn = gameNumber;
9992     ListGame *lg = NULL;
9993     int numPGNTags = 0;
9994     int err;
9995     GameMode oldGameMode;
9996     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9997
9998     if (appData.debugMode) 
9999         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10000
10001     if (gameMode == Training )
10002         SetTrainingModeOff();
10003
10004     oldGameMode = gameMode;
10005     if (gameMode != BeginningOfGame) {
10006       Reset(FALSE, TRUE);
10007     }
10008
10009     gameFileFP = f;
10010     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10011         fclose(lastLoadGameFP);
10012     }
10013
10014     if (useList) {
10015         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10016         
10017         if (lg) {
10018             fseek(f, lg->offset, 0);
10019             GameListHighlight(gameNumber);
10020             gn = 1;
10021         }
10022         else {
10023             DisplayError(_("Game number out of range"), 0);
10024             return FALSE;
10025         }
10026     } else {
10027         GameListDestroy();
10028         if (fseek(f, 0, 0) == -1) {
10029             if (f == lastLoadGameFP ?
10030                 gameNumber == lastLoadGameNumber + 1 :
10031                 gameNumber == 1) {
10032                 gn = 1;
10033             } else {
10034                 DisplayError(_("Can't seek on game file"), 0);
10035                 return FALSE;
10036             }
10037         }
10038     }
10039     lastLoadGameFP = f;
10040     lastLoadGameNumber = gameNumber;
10041     strcpy(lastLoadGameTitle, title);
10042     lastLoadGameUseList = useList;
10043
10044     yynewfile(f);
10045
10046     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10047       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10048                 lg->gameInfo.black);
10049             DisplayTitle(buf);
10050     } else if (*title != NULLCHAR) {
10051         if (gameNumber > 1) {
10052             sprintf(buf, "%s %d", title, gameNumber);
10053             DisplayTitle(buf);
10054         } else {
10055             DisplayTitle(title);
10056         }
10057     }
10058
10059     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10060         gameMode = PlayFromGameFile;
10061         ModeHighlight();
10062     }
10063
10064     currentMove = forwardMostMove = backwardMostMove = 0;
10065     CopyBoard(boards[0], initialPosition);
10066     StopClocks();
10067
10068     /*
10069      * Skip the first gn-1 games in the file.
10070      * Also skip over anything that precedes an identifiable 
10071      * start of game marker, to avoid being confused by 
10072      * garbage at the start of the file.  Currently 
10073      * recognized start of game markers are the move number "1",
10074      * the pattern "gnuchess .* game", the pattern
10075      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10076      * A game that starts with one of the latter two patterns
10077      * will also have a move number 1, possibly
10078      * following a position diagram.
10079      * 5-4-02: Let's try being more lenient and allowing a game to
10080      * start with an unnumbered move.  Does that break anything?
10081      */
10082     cm = lastLoadGameStart = (ChessMove) 0;
10083     while (gn > 0) {
10084         yyboardindex = forwardMostMove;
10085         cm = (ChessMove) yylex();
10086         switch (cm) {
10087           case (ChessMove) 0:
10088             if (cmailMsgLoaded) {
10089                 nCmailGames = CMAIL_MAX_GAMES - gn;
10090             } else {
10091                 Reset(TRUE, TRUE);
10092                 DisplayError(_("Game not found in file"), 0);
10093             }
10094             return FALSE;
10095
10096           case GNUChessGame:
10097           case XBoardGame:
10098             gn--;
10099             lastLoadGameStart = cm;
10100             break;
10101             
10102           case MoveNumberOne:
10103             switch (lastLoadGameStart) {
10104               case GNUChessGame:
10105               case XBoardGame:
10106               case PGNTag:
10107                 break;
10108               case MoveNumberOne:
10109               case (ChessMove) 0:
10110                 gn--;           /* count this game */
10111                 lastLoadGameStart = cm;
10112                 break;
10113               default:
10114                 /* impossible */
10115                 break;
10116             }
10117             break;
10118
10119           case PGNTag:
10120             switch (lastLoadGameStart) {
10121               case GNUChessGame:
10122               case PGNTag:
10123               case MoveNumberOne:
10124               case (ChessMove) 0:
10125                 gn--;           /* count this game */
10126                 lastLoadGameStart = cm;
10127                 break;
10128               case XBoardGame:
10129                 lastLoadGameStart = cm; /* game counted already */
10130                 break;
10131               default:
10132                 /* impossible */
10133                 break;
10134             }
10135             if (gn > 0) {
10136                 do {
10137                     yyboardindex = forwardMostMove;
10138                     cm = (ChessMove) yylex();
10139                 } while (cm == PGNTag || cm == Comment);
10140             }
10141             break;
10142
10143           case WhiteWins:
10144           case BlackWins:
10145           case GameIsDrawn:
10146             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10147                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10148                     != CMAIL_OLD_RESULT) {
10149                     nCmailResults ++ ;
10150                     cmailResult[  CMAIL_MAX_GAMES
10151                                 - gn - 1] = CMAIL_OLD_RESULT;
10152                 }
10153             }
10154             break;
10155
10156           case NormalMove:
10157             /* Only a NormalMove can be at the start of a game
10158              * without a position diagram. */
10159             if (lastLoadGameStart == (ChessMove) 0) {
10160               gn--;
10161               lastLoadGameStart = MoveNumberOne;
10162             }
10163             break;
10164
10165           default:
10166             break;
10167         }
10168     }
10169     
10170     if (appData.debugMode)
10171       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10172
10173     if (cm == XBoardGame) {
10174         /* Skip any header junk before position diagram and/or move 1 */
10175         for (;;) {
10176             yyboardindex = forwardMostMove;
10177             cm = (ChessMove) yylex();
10178
10179             if (cm == (ChessMove) 0 ||
10180                 cm == GNUChessGame || cm == XBoardGame) {
10181                 /* Empty game; pretend end-of-file and handle later */
10182                 cm = (ChessMove) 0;
10183                 break;
10184             }
10185
10186             if (cm == MoveNumberOne || cm == PositionDiagram ||
10187                 cm == PGNTag || cm == Comment)
10188               break;
10189         }
10190     } else if (cm == GNUChessGame) {
10191         if (gameInfo.event != NULL) {
10192             free(gameInfo.event);
10193         }
10194         gameInfo.event = StrSave(yy_text);
10195     }   
10196
10197     startedFromSetupPosition = FALSE;
10198     while (cm == PGNTag) {
10199         if (appData.debugMode) 
10200           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10201         err = ParsePGNTag(yy_text, &gameInfo);
10202         if (!err) numPGNTags++;
10203
10204         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10205         if(gameInfo.variant != oldVariant) {
10206             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10207             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10208             InitPosition(TRUE);
10209             oldVariant = gameInfo.variant;
10210             if (appData.debugMode) 
10211               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10212         }
10213
10214
10215         if (gameInfo.fen != NULL) {
10216           Board initial_position;
10217           startedFromSetupPosition = TRUE;
10218           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10219             Reset(TRUE, TRUE);
10220             DisplayError(_("Bad FEN position in file"), 0);
10221             return FALSE;
10222           }
10223           CopyBoard(boards[0], initial_position);
10224           if (blackPlaysFirst) {
10225             currentMove = forwardMostMove = backwardMostMove = 1;
10226             CopyBoard(boards[1], initial_position);
10227             strcpy(moveList[0], "");
10228             strcpy(parseList[0], "");
10229             timeRemaining[0][1] = whiteTimeRemaining;
10230             timeRemaining[1][1] = blackTimeRemaining;
10231             if (commentList[0] != NULL) {
10232               commentList[1] = commentList[0];
10233               commentList[0] = NULL;
10234             }
10235           } else {
10236             currentMove = forwardMostMove = backwardMostMove = 0;
10237           }
10238           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10239           {   int i;
10240               initialRulePlies = FENrulePlies;
10241               for( i=0; i< nrCastlingRights; i++ )
10242                   initialRights[i] = initial_position[CASTLING][i];
10243           }
10244           yyboardindex = forwardMostMove;
10245           free(gameInfo.fen);
10246           gameInfo.fen = NULL;
10247         }
10248
10249         yyboardindex = forwardMostMove;
10250         cm = (ChessMove) yylex();
10251
10252         /* Handle comments interspersed among the tags */
10253         while (cm == Comment) {
10254             char *p;
10255             if (appData.debugMode) 
10256               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10257             p = yy_text;
10258             AppendComment(currentMove, p, FALSE);
10259             yyboardindex = forwardMostMove;
10260             cm = (ChessMove) yylex();
10261         }
10262     }
10263
10264     /* don't rely on existence of Event tag since if game was
10265      * pasted from clipboard the Event tag may not exist
10266      */
10267     if (numPGNTags > 0){
10268         char *tags;
10269         if (gameInfo.variant == VariantNormal) {
10270           VariantClass v = StringToVariant(gameInfo.event);
10271           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10272           if(v < VariantShogi) gameInfo.variant = v;
10273         }
10274         if (!matchMode) {
10275           if( appData.autoDisplayTags ) {
10276             tags = PGNTags(&gameInfo);
10277             TagsPopUp(tags, CmailMsg());
10278             free(tags);
10279           }
10280         }
10281     } else {
10282         /* Make something up, but don't display it now */
10283         SetGameInfo();
10284         TagsPopDown();
10285     }
10286
10287     if (cm == PositionDiagram) {
10288         int i, j;
10289         char *p;
10290         Board initial_position;
10291
10292         if (appData.debugMode)
10293           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10294
10295         if (!startedFromSetupPosition) {
10296             p = yy_text;
10297             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10298               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10299                 switch (*p) {
10300                   case '[':
10301                   case '-':
10302                   case ' ':
10303                   case '\t':
10304                   case '\n':
10305                   case '\r':
10306                     break;
10307                   default:
10308                     initial_position[i][j++] = CharToPiece(*p);
10309                     break;
10310                 }
10311             while (*p == ' ' || *p == '\t' ||
10312                    *p == '\n' || *p == '\r') p++;
10313         
10314             if (strncmp(p, "black", strlen("black"))==0)
10315               blackPlaysFirst = TRUE;
10316             else
10317               blackPlaysFirst = FALSE;
10318             startedFromSetupPosition = TRUE;
10319         
10320             CopyBoard(boards[0], initial_position);
10321             if (blackPlaysFirst) {
10322                 currentMove = forwardMostMove = backwardMostMove = 1;
10323                 CopyBoard(boards[1], initial_position);
10324                 strcpy(moveList[0], "");
10325                 strcpy(parseList[0], "");
10326                 timeRemaining[0][1] = whiteTimeRemaining;
10327                 timeRemaining[1][1] = blackTimeRemaining;
10328                 if (commentList[0] != NULL) {
10329                     commentList[1] = commentList[0];
10330                     commentList[0] = NULL;
10331                 }
10332             } else {
10333                 currentMove = forwardMostMove = backwardMostMove = 0;
10334             }
10335         }
10336         yyboardindex = forwardMostMove;
10337         cm = (ChessMove) yylex();
10338     }
10339
10340     if (first.pr == NoProc) {
10341         StartChessProgram(&first);
10342     }
10343     InitChessProgram(&first, FALSE);
10344     SendToProgram("force\n", &first);
10345     if (startedFromSetupPosition) {
10346         SendBoard(&first, forwardMostMove);
10347     if (appData.debugMode) {
10348         fprintf(debugFP, "Load Game\n");
10349     }
10350         DisplayBothClocks();
10351     }      
10352
10353     /* [HGM] server: flag to write setup moves in broadcast file as one */
10354     loadFlag = appData.suppressLoadMoves;
10355
10356     while (cm == Comment) {
10357         char *p;
10358         if (appData.debugMode) 
10359           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10360         p = yy_text;
10361         AppendComment(currentMove, p, FALSE);
10362         yyboardindex = forwardMostMove;
10363         cm = (ChessMove) yylex();
10364     }
10365
10366     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10367         cm == WhiteWins || cm == BlackWins ||
10368         cm == GameIsDrawn || cm == GameUnfinished) {
10369         DisplayMessage("", _("No moves in game"));
10370         if (cmailMsgLoaded) {
10371             if (appData.debugMode)
10372               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10373             ClearHighlights();
10374             flipView = FALSE;
10375         }
10376         DrawPosition(FALSE, boards[currentMove]);
10377         DisplayBothClocks();
10378         gameMode = EditGame;
10379         ModeHighlight();
10380         gameFileFP = NULL;
10381         cmailOldMove = 0;
10382         return TRUE;
10383     }
10384
10385     // [HGM] PV info: routine tests if comment empty
10386     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10387         DisplayComment(currentMove - 1, commentList[currentMove]);
10388     }
10389     if (!matchMode && appData.timeDelay != 0) 
10390       DrawPosition(FALSE, boards[currentMove]);
10391
10392     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10393       programStats.ok_to_send = 1;
10394     }
10395
10396     /* if the first token after the PGN tags is a move
10397      * and not move number 1, retrieve it from the parser 
10398      */
10399     if (cm != MoveNumberOne)
10400         LoadGameOneMove(cm);
10401
10402     /* load the remaining moves from the file */
10403     while (LoadGameOneMove((ChessMove)0)) {
10404       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10405       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10406     }
10407
10408     /* rewind to the start of the game */
10409     currentMove = backwardMostMove;
10410
10411     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10412
10413     if (oldGameMode == AnalyzeFile ||
10414         oldGameMode == AnalyzeMode) {
10415       AnalyzeFileEvent();
10416     }
10417
10418     if (matchMode || appData.timeDelay == 0) {
10419       ToEndEvent();
10420       gameMode = EditGame;
10421       ModeHighlight();
10422     } else if (appData.timeDelay > 0) {
10423       AutoPlayGameLoop();
10424     }
10425
10426     if (appData.debugMode) 
10427         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10428
10429     loadFlag = 0; /* [HGM] true game starts */
10430     return TRUE;
10431 }
10432
10433 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10434 int
10435 ReloadPosition(offset)
10436      int offset;
10437 {
10438     int positionNumber = lastLoadPositionNumber + offset;
10439     if (lastLoadPositionFP == NULL) {
10440         DisplayError(_("No position has been loaded yet"), 0);
10441         return FALSE;
10442     }
10443     if (positionNumber <= 0) {
10444         DisplayError(_("Can't back up any further"), 0);
10445         return FALSE;
10446     }
10447     return LoadPosition(lastLoadPositionFP, positionNumber,
10448                         lastLoadPositionTitle);
10449 }
10450
10451 /* Load the nth position from the given file */
10452 int
10453 LoadPositionFromFile(filename, n, title)
10454      char *filename;
10455      int n;
10456      char *title;
10457 {
10458     FILE *f;
10459     char buf[MSG_SIZ];
10460
10461     if (strcmp(filename, "-") == 0) {
10462         return LoadPosition(stdin, n, "stdin");
10463     } else {
10464         f = fopen(filename, "rb");
10465         if (f == NULL) {
10466             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10467             DisplayError(buf, errno);
10468             return FALSE;
10469         } else {
10470             return LoadPosition(f, n, title);
10471         }
10472     }
10473 }
10474
10475 /* Load the nth position from the given open file, and close it */
10476 int
10477 LoadPosition(f, positionNumber, title)
10478      FILE *f;
10479      int positionNumber;
10480      char *title;
10481 {
10482     char *p, line[MSG_SIZ];
10483     Board initial_position;
10484     int i, j, fenMode, pn;
10485     
10486     if (gameMode == Training )
10487         SetTrainingModeOff();
10488
10489     if (gameMode != BeginningOfGame) {
10490         Reset(FALSE, TRUE);
10491     }
10492     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10493         fclose(lastLoadPositionFP);
10494     }
10495     if (positionNumber == 0) positionNumber = 1;
10496     lastLoadPositionFP = f;
10497     lastLoadPositionNumber = positionNumber;
10498     strcpy(lastLoadPositionTitle, title);
10499     if (first.pr == NoProc) {
10500       StartChessProgram(&first);
10501       InitChessProgram(&first, FALSE);
10502     }    
10503     pn = positionNumber;
10504     if (positionNumber < 0) {
10505         /* Negative position number means to seek to that byte offset */
10506         if (fseek(f, -positionNumber, 0) == -1) {
10507             DisplayError(_("Can't seek on position file"), 0);
10508             return FALSE;
10509         };
10510         pn = 1;
10511     } else {
10512         if (fseek(f, 0, 0) == -1) {
10513             if (f == lastLoadPositionFP ?
10514                 positionNumber == lastLoadPositionNumber + 1 :
10515                 positionNumber == 1) {
10516                 pn = 1;
10517             } else {
10518                 DisplayError(_("Can't seek on position file"), 0);
10519                 return FALSE;
10520             }
10521         }
10522     }
10523     /* See if this file is FEN or old-style xboard */
10524     if (fgets(line, MSG_SIZ, f) == NULL) {
10525         DisplayError(_("Position not found in file"), 0);
10526         return FALSE;
10527     }
10528     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10529     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10530
10531     if (pn >= 2) {
10532         if (fenMode || line[0] == '#') pn--;
10533         while (pn > 0) {
10534             /* skip positions before number pn */
10535             if (fgets(line, MSG_SIZ, f) == NULL) {
10536                 Reset(TRUE, TRUE);
10537                 DisplayError(_("Position not found in file"), 0);
10538                 return FALSE;
10539             }
10540             if (fenMode || line[0] == '#') pn--;
10541         }
10542     }
10543
10544     if (fenMode) {
10545         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10546             DisplayError(_("Bad FEN position in file"), 0);
10547             return FALSE;
10548         }
10549     } else {
10550         (void) fgets(line, MSG_SIZ, f);
10551         (void) fgets(line, MSG_SIZ, f);
10552     
10553         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10554             (void) fgets(line, MSG_SIZ, f);
10555             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10556                 if (*p == ' ')
10557                   continue;
10558                 initial_position[i][j++] = CharToPiece(*p);
10559             }
10560         }
10561     
10562         blackPlaysFirst = FALSE;
10563         if (!feof(f)) {
10564             (void) fgets(line, MSG_SIZ, f);
10565             if (strncmp(line, "black", strlen("black"))==0)
10566               blackPlaysFirst = TRUE;
10567         }
10568     }
10569     startedFromSetupPosition = TRUE;
10570     
10571     SendToProgram("force\n", &first);
10572     CopyBoard(boards[0], initial_position);
10573     if (blackPlaysFirst) {
10574         currentMove = forwardMostMove = backwardMostMove = 1;
10575         strcpy(moveList[0], "");
10576         strcpy(parseList[0], "");
10577         CopyBoard(boards[1], initial_position);
10578         DisplayMessage("", _("Black to play"));
10579     } else {
10580         currentMove = forwardMostMove = backwardMostMove = 0;
10581         DisplayMessage("", _("White to play"));
10582     }
10583     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10584     SendBoard(&first, forwardMostMove);
10585     if (appData.debugMode) {
10586 int i, j;
10587   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10588   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10589         fprintf(debugFP, "Load Position\n");
10590     }
10591
10592     if (positionNumber > 1) {
10593         sprintf(line, "%s %d", title, positionNumber);
10594         DisplayTitle(line);
10595     } else {
10596         DisplayTitle(title);
10597     }
10598     gameMode = EditGame;
10599     ModeHighlight();
10600     ResetClocks();
10601     timeRemaining[0][1] = whiteTimeRemaining;
10602     timeRemaining[1][1] = blackTimeRemaining;
10603     DrawPosition(FALSE, boards[currentMove]);
10604    
10605     return TRUE;
10606 }
10607
10608
10609 void
10610 CopyPlayerNameIntoFileName(dest, src)
10611      char **dest, *src;
10612 {
10613     while (*src != NULLCHAR && *src != ',') {
10614         if (*src == ' ') {
10615             *(*dest)++ = '_';
10616             src++;
10617         } else {
10618             *(*dest)++ = *src++;
10619         }
10620     }
10621 }
10622
10623 char *DefaultFileName(ext)
10624      char *ext;
10625 {
10626     static char def[MSG_SIZ];
10627     char *p;
10628
10629     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10630         p = def;
10631         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10632         *p++ = '-';
10633         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10634         *p++ = '.';
10635         strcpy(p, ext);
10636     } else {
10637         def[0] = NULLCHAR;
10638     }
10639     return def;
10640 }
10641
10642 /* Save the current game to the given file */
10643 int
10644 SaveGameToFile(filename, append)
10645      char *filename;
10646      int append;
10647 {
10648     FILE *f;
10649     char buf[MSG_SIZ];
10650
10651     if (strcmp(filename, "-") == 0) {
10652         return SaveGame(stdout, 0, NULL);
10653     } else {
10654         f = fopen(filename, append ? "a" : "w");
10655         if (f == NULL) {
10656             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10657             DisplayError(buf, errno);
10658             return FALSE;
10659         } else {
10660             return SaveGame(f, 0, NULL);
10661         }
10662     }
10663 }
10664
10665 char *
10666 SavePart(str)
10667      char *str;
10668 {
10669     static char buf[MSG_SIZ];
10670     char *p;
10671     
10672     p = strchr(str, ' ');
10673     if (p == NULL) return str;
10674     strncpy(buf, str, p - str);
10675     buf[p - str] = NULLCHAR;
10676     return buf;
10677 }
10678
10679 #define PGN_MAX_LINE 75
10680
10681 #define PGN_SIDE_WHITE  0
10682 #define PGN_SIDE_BLACK  1
10683
10684 /* [AS] */
10685 static int FindFirstMoveOutOfBook( int side )
10686 {
10687     int result = -1;
10688
10689     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10690         int index = backwardMostMove;
10691         int has_book_hit = 0;
10692
10693         if( (index % 2) != side ) {
10694             index++;
10695         }
10696
10697         while( index < forwardMostMove ) {
10698             /* Check to see if engine is in book */
10699             int depth = pvInfoList[index].depth;
10700             int score = pvInfoList[index].score;
10701             int in_book = 0;
10702
10703             if( depth <= 2 ) {
10704                 in_book = 1;
10705             }
10706             else if( score == 0 && depth == 63 ) {
10707                 in_book = 1; /* Zappa */
10708             }
10709             else if( score == 2 && depth == 99 ) {
10710                 in_book = 1; /* Abrok */
10711             }
10712
10713             has_book_hit += in_book;
10714
10715             if( ! in_book ) {
10716                 result = index;
10717
10718                 break;
10719             }
10720
10721             index += 2;
10722         }
10723     }
10724
10725     return result;
10726 }
10727
10728 /* [AS] */
10729 void GetOutOfBookInfo( char * buf )
10730 {
10731     int oob[2];
10732     int i;
10733     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10734
10735     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10736     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10737
10738     *buf = '\0';
10739
10740     if( oob[0] >= 0 || oob[1] >= 0 ) {
10741         for( i=0; i<2; i++ ) {
10742             int idx = oob[i];
10743
10744             if( idx >= 0 ) {
10745                 if( i > 0 && oob[0] >= 0 ) {
10746                     strcat( buf, "   " );
10747                 }
10748
10749                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10750                 sprintf( buf+strlen(buf), "%s%.2f", 
10751                     pvInfoList[idx].score >= 0 ? "+" : "",
10752                     pvInfoList[idx].score / 100.0 );
10753             }
10754         }
10755     }
10756 }
10757
10758 /* Save game in PGN style and close the file */
10759 int
10760 SaveGamePGN(f)
10761      FILE *f;
10762 {
10763     int i, offset, linelen, newblock;
10764     time_t tm;
10765 //    char *movetext;
10766     char numtext[32];
10767     int movelen, numlen, blank;
10768     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10769
10770     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10771     
10772     tm = time((time_t *) NULL);
10773     
10774     PrintPGNTags(f, &gameInfo);
10775     
10776     if (backwardMostMove > 0 || startedFromSetupPosition) {
10777         char *fen = PositionToFEN(backwardMostMove, NULL);
10778         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10779         fprintf(f, "\n{--------------\n");
10780         PrintPosition(f, backwardMostMove);
10781         fprintf(f, "--------------}\n");
10782         free(fen);
10783     }
10784     else {
10785         /* [AS] Out of book annotation */
10786         if( appData.saveOutOfBookInfo ) {
10787             char buf[64];
10788
10789             GetOutOfBookInfo( buf );
10790
10791             if( buf[0] != '\0' ) {
10792                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10793             }
10794         }
10795
10796         fprintf(f, "\n");
10797     }
10798
10799     i = backwardMostMove;
10800     linelen = 0;
10801     newblock = TRUE;
10802
10803     while (i < forwardMostMove) {
10804         /* Print comments preceding this move */
10805         if (commentList[i] != NULL) {
10806             if (linelen > 0) fprintf(f, "\n");
10807             fprintf(f, "%s", commentList[i]);
10808             linelen = 0;
10809             newblock = TRUE;
10810         }
10811
10812         /* Format move number */
10813         if ((i % 2) == 0) {
10814             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10815         } else {
10816             if (newblock) {
10817                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10818             } else {
10819                 numtext[0] = NULLCHAR;
10820             }
10821         }
10822         numlen = strlen(numtext);
10823         newblock = FALSE;
10824
10825         /* Print move number */
10826         blank = linelen > 0 && numlen > 0;
10827         if (linelen + (blank ? 1 : 0) + numlen > 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", numtext);
10837         linelen += numlen;
10838
10839         /* Get move */
10840         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10841         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10842
10843         /* Print move */
10844         blank = linelen > 0 && movelen > 0;
10845         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10846             fprintf(f, "\n");
10847             linelen = 0;
10848             blank = 0;
10849         }
10850         if (blank) {
10851             fprintf(f, " ");
10852             linelen++;
10853         }
10854         fprintf(f, "%s", move_buffer);
10855         linelen += movelen;
10856
10857         /* [AS] Add PV info if present */
10858         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10859             /* [HGM] add time */
10860             char buf[MSG_SIZ]; int seconds;
10861
10862             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10863
10864             if( seconds <= 0) buf[0] = 0; else
10865             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10866                 seconds = (seconds + 4)/10; // round to full seconds
10867                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10868                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10869             }
10870
10871             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10872                 pvInfoList[i].score >= 0 ? "+" : "",
10873                 pvInfoList[i].score / 100.0,
10874                 pvInfoList[i].depth,
10875                 buf );
10876
10877             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10878
10879             /* Print score/depth */
10880             blank = linelen > 0 && movelen > 0;
10881             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10882                 fprintf(f, "\n");
10883                 linelen = 0;
10884                 blank = 0;
10885             }
10886             if (blank) {
10887                 fprintf(f, " ");
10888                 linelen++;
10889             }
10890             fprintf(f, "%s", move_buffer);
10891             linelen += movelen;
10892         }
10893
10894         i++;
10895     }
10896     
10897     /* Start a new line */
10898     if (linelen > 0) fprintf(f, "\n");
10899
10900     /* Print comments after last move */
10901     if (commentList[i] != NULL) {
10902         fprintf(f, "%s\n", commentList[i]);
10903     }
10904
10905     /* Print result */
10906     if (gameInfo.resultDetails != NULL &&
10907         gameInfo.resultDetails[0] != NULLCHAR) {
10908         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10909                 PGNResult(gameInfo.result));
10910     } else {
10911         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10912     }
10913
10914     fclose(f);
10915     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10916     return TRUE;
10917 }
10918
10919 /* Save game in old style and close the file */
10920 int
10921 SaveGameOldStyle(f)
10922      FILE *f;
10923 {
10924     int i, offset;
10925     time_t tm;
10926     
10927     tm = time((time_t *) NULL);
10928     
10929     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10930     PrintOpponents(f);
10931     
10932     if (backwardMostMove > 0 || startedFromSetupPosition) {
10933         fprintf(f, "\n[--------------\n");
10934         PrintPosition(f, backwardMostMove);
10935         fprintf(f, "--------------]\n");
10936     } else {
10937         fprintf(f, "\n");
10938     }
10939
10940     i = backwardMostMove;
10941     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10942
10943     while (i < forwardMostMove) {
10944         if (commentList[i] != NULL) {
10945             fprintf(f, "[%s]\n", commentList[i]);
10946         }
10947
10948         if ((i % 2) == 1) {
10949             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10950             i++;
10951         } else {
10952             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10953             i++;
10954             if (commentList[i] != NULL) {
10955                 fprintf(f, "\n");
10956                 continue;
10957             }
10958             if (i >= forwardMostMove) {
10959                 fprintf(f, "\n");
10960                 break;
10961             }
10962             fprintf(f, "%s\n", parseList[i]);
10963             i++;
10964         }
10965     }
10966     
10967     if (commentList[i] != NULL) {
10968         fprintf(f, "[%s]\n", commentList[i]);
10969     }
10970
10971     /* This isn't really the old style, but it's close enough */
10972     if (gameInfo.resultDetails != NULL &&
10973         gameInfo.resultDetails[0] != NULLCHAR) {
10974         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10975                 gameInfo.resultDetails);
10976     } else {
10977         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10978     }
10979
10980     fclose(f);
10981     return TRUE;
10982 }
10983
10984 /* Save the current game to open file f and close the file */
10985 int
10986 SaveGame(f, dummy, dummy2)
10987      FILE *f;
10988      int dummy;
10989      char *dummy2;
10990 {
10991     if (gameMode == EditPosition) EditPositionDone(TRUE);
10992     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10993     if (appData.oldSaveStyle)
10994       return SaveGameOldStyle(f);
10995     else
10996       return SaveGamePGN(f);
10997 }
10998
10999 /* Save the current position to the given file */
11000 int
11001 SavePositionToFile(filename)
11002      char *filename;
11003 {
11004     FILE *f;
11005     char buf[MSG_SIZ];
11006
11007     if (strcmp(filename, "-") == 0) {
11008         return SavePosition(stdout, 0, NULL);
11009     } else {
11010         f = fopen(filename, "a");
11011         if (f == NULL) {
11012             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11013             DisplayError(buf, errno);
11014             return FALSE;
11015         } else {
11016             SavePosition(f, 0, NULL);
11017             return TRUE;
11018         }
11019     }
11020 }
11021
11022 /* Save the current position to the given open file and close the file */
11023 int
11024 SavePosition(f, dummy, dummy2)
11025      FILE *f;
11026      int dummy;
11027      char *dummy2;
11028 {
11029     time_t tm;
11030     char *fen;
11031     
11032     if (gameMode == EditPosition) EditPositionDone(TRUE);
11033     if (appData.oldSaveStyle) {
11034         tm = time((time_t *) NULL);
11035     
11036         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11037         PrintOpponents(f);
11038         fprintf(f, "[--------------\n");
11039         PrintPosition(f, currentMove);
11040         fprintf(f, "--------------]\n");
11041     } else {
11042         fen = PositionToFEN(currentMove, NULL);
11043         fprintf(f, "%s\n", fen);
11044         free(fen);
11045     }
11046     fclose(f);
11047     return TRUE;
11048 }
11049
11050 void
11051 ReloadCmailMsgEvent(unregister)
11052      int unregister;
11053 {
11054 #if !WIN32
11055     static char *inFilename = NULL;
11056     static char *outFilename;
11057     int i;
11058     struct stat inbuf, outbuf;
11059     int status;
11060     
11061     /* Any registered moves are unregistered if unregister is set, */
11062     /* i.e. invoked by the signal handler */
11063     if (unregister) {
11064         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11065             cmailMoveRegistered[i] = FALSE;
11066             if (cmailCommentList[i] != NULL) {
11067                 free(cmailCommentList[i]);
11068                 cmailCommentList[i] = NULL;
11069             }
11070         }
11071         nCmailMovesRegistered = 0;
11072     }
11073
11074     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11075         cmailResult[i] = CMAIL_NOT_RESULT;
11076     }
11077     nCmailResults = 0;
11078
11079     if (inFilename == NULL) {
11080         /* Because the filenames are static they only get malloced once  */
11081         /* and they never get freed                                      */
11082         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11083         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11084
11085         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11086         sprintf(outFilename, "%s.out", appData.cmailGameName);
11087     }
11088     
11089     status = stat(outFilename, &outbuf);
11090     if (status < 0) {
11091         cmailMailedMove = FALSE;
11092     } else {
11093         status = stat(inFilename, &inbuf);
11094         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11095     }
11096     
11097     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11098        counts the games, notes how each one terminated, etc.
11099        
11100        It would be nice to remove this kludge and instead gather all
11101        the information while building the game list.  (And to keep it
11102        in the game list nodes instead of having a bunch of fixed-size
11103        parallel arrays.)  Note this will require getting each game's
11104        termination from the PGN tags, as the game list builder does
11105        not process the game moves.  --mann
11106        */
11107     cmailMsgLoaded = TRUE;
11108     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11109     
11110     /* Load first game in the file or popup game menu */
11111     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11112
11113 #endif /* !WIN32 */
11114     return;
11115 }
11116
11117 int
11118 RegisterMove()
11119 {
11120     FILE *f;
11121     char string[MSG_SIZ];
11122
11123     if (   cmailMailedMove
11124         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11125         return TRUE;            /* Allow free viewing  */
11126     }
11127
11128     /* Unregister move to ensure that we don't leave RegisterMove        */
11129     /* with the move registered when the conditions for registering no   */
11130     /* longer hold                                                       */
11131     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11132         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11133         nCmailMovesRegistered --;
11134
11135         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11136           {
11137               free(cmailCommentList[lastLoadGameNumber - 1]);
11138               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11139           }
11140     }
11141
11142     if (cmailOldMove == -1) {
11143         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11144         return FALSE;
11145     }
11146
11147     if (currentMove > cmailOldMove + 1) {
11148         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11149         return FALSE;
11150     }
11151
11152     if (currentMove < cmailOldMove) {
11153         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11154         return FALSE;
11155     }
11156
11157     if (forwardMostMove > currentMove) {
11158         /* Silently truncate extra moves */
11159         TruncateGame();
11160     }
11161
11162     if (   (currentMove == cmailOldMove + 1)
11163         || (   (currentMove == cmailOldMove)
11164             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11165                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11166         if (gameInfo.result != GameUnfinished) {
11167             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11168         }
11169
11170         if (commentList[currentMove] != NULL) {
11171             cmailCommentList[lastLoadGameNumber - 1]
11172               = StrSave(commentList[currentMove]);
11173         }
11174         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11175
11176         if (appData.debugMode)
11177           fprintf(debugFP, "Saving %s for game %d\n",
11178                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11179
11180         sprintf(string,
11181                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11182         
11183         f = fopen(string, "w");
11184         if (appData.oldSaveStyle) {
11185             SaveGameOldStyle(f); /* also closes the file */
11186             
11187             sprintf(string, "%s.pos.out", appData.cmailGameName);
11188             f = fopen(string, "w");
11189             SavePosition(f, 0, NULL); /* also closes the file */
11190         } else {
11191             fprintf(f, "{--------------\n");
11192             PrintPosition(f, currentMove);
11193             fprintf(f, "--------------}\n\n");
11194             
11195             SaveGame(f, 0, NULL); /* also closes the file*/
11196         }
11197         
11198         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11199         nCmailMovesRegistered ++;
11200     } else if (nCmailGames == 1) {
11201         DisplayError(_("You have not made a move yet"), 0);
11202         return FALSE;
11203     }
11204
11205     return TRUE;
11206 }
11207
11208 void
11209 MailMoveEvent()
11210 {
11211 #if !WIN32
11212     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11213     FILE *commandOutput;
11214     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11215     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11216     int nBuffers;
11217     int i;
11218     int archived;
11219     char *arcDir;
11220
11221     if (! cmailMsgLoaded) {
11222         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11223         return;
11224     }
11225
11226     if (nCmailGames == nCmailResults) {
11227         DisplayError(_("No unfinished games"), 0);
11228         return;
11229     }
11230
11231 #if CMAIL_PROHIBIT_REMAIL
11232     if (cmailMailedMove) {
11233         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);
11234         DisplayError(msg, 0);
11235         return;
11236     }
11237 #endif
11238
11239     if (! (cmailMailedMove || RegisterMove())) return;
11240     
11241     if (   cmailMailedMove
11242         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11243         sprintf(string, partCommandString,
11244                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11245         commandOutput = popen(string, "r");
11246
11247         if (commandOutput == NULL) {
11248             DisplayError(_("Failed to invoke cmail"), 0);
11249         } else {
11250             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11251                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11252             }
11253             if (nBuffers > 1) {
11254                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11255                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11256                 nBytes = MSG_SIZ - 1;
11257             } else {
11258                 (void) memcpy(msg, buffer, nBytes);
11259             }
11260             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11261
11262             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11263                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11264
11265                 archived = TRUE;
11266                 for (i = 0; i < nCmailGames; i ++) {
11267                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11268                         archived = FALSE;
11269                     }
11270                 }
11271                 if (   archived
11272                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11273                         != NULL)) {
11274                     sprintf(buffer, "%s/%s.%s.archive",
11275                             arcDir,
11276                             appData.cmailGameName,
11277                             gameInfo.date);
11278                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11279                     cmailMsgLoaded = FALSE;
11280                 }
11281             }
11282
11283             DisplayInformation(msg);
11284             pclose(commandOutput);
11285         }
11286     } else {
11287         if ((*cmailMsg) != '\0') {
11288             DisplayInformation(cmailMsg);
11289         }
11290     }
11291
11292     return;
11293 #endif /* !WIN32 */
11294 }
11295
11296 char *
11297 CmailMsg()
11298 {
11299 #if WIN32
11300     return NULL;
11301 #else
11302     int  prependComma = 0;
11303     char number[5];
11304     char string[MSG_SIZ];       /* Space for game-list */
11305     int  i;
11306     
11307     if (!cmailMsgLoaded) return "";
11308
11309     if (cmailMailedMove) {
11310         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11311     } else {
11312         /* Create a list of games left */
11313         sprintf(string, "[");
11314         for (i = 0; i < nCmailGames; i ++) {
11315             if (! (   cmailMoveRegistered[i]
11316                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11317                 if (prependComma) {
11318                     sprintf(number, ",%d", i + 1);
11319                 } else {
11320                     sprintf(number, "%d", i + 1);
11321                     prependComma = 1;
11322                 }
11323                 
11324                 strcat(string, number);
11325             }
11326         }
11327         strcat(string, "]");
11328
11329         if (nCmailMovesRegistered + nCmailResults == 0) {
11330             switch (nCmailGames) {
11331               case 1:
11332                 sprintf(cmailMsg,
11333                         _("Still need to make move for game\n"));
11334                 break;
11335                 
11336               case 2:
11337                 sprintf(cmailMsg,
11338                         _("Still need to make moves for both games\n"));
11339                 break;
11340                 
11341               default:
11342                 sprintf(cmailMsg,
11343                         _("Still need to make moves for all %d games\n"),
11344                         nCmailGames);
11345                 break;
11346             }
11347         } else {
11348             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11349               case 1:
11350                 sprintf(cmailMsg,
11351                         _("Still need to make a move for game %s\n"),
11352                         string);
11353                 break;
11354                 
11355               case 0:
11356                 if (nCmailResults == nCmailGames) {
11357                     sprintf(cmailMsg, _("No unfinished games\n"));
11358                 } else {
11359                     sprintf(cmailMsg, _("Ready to send mail\n"));
11360                 }
11361                 break;
11362                 
11363               default:
11364                 sprintf(cmailMsg,
11365                         _("Still need to make moves for games %s\n"),
11366                         string);
11367             }
11368         }
11369     }
11370     return cmailMsg;
11371 #endif /* WIN32 */
11372 }
11373
11374 void
11375 ResetGameEvent()
11376 {
11377     if (gameMode == Training)
11378       SetTrainingModeOff();
11379
11380     Reset(TRUE, TRUE);
11381     cmailMsgLoaded = FALSE;
11382     if (appData.icsActive) {
11383       SendToICS(ics_prefix);
11384       SendToICS("refresh\n");
11385     }
11386 }
11387
11388 void
11389 ExitEvent(status)
11390      int status;
11391 {
11392     exiting++;
11393     if (exiting > 2) {
11394       /* Give up on clean exit */
11395       exit(status);
11396     }
11397     if (exiting > 1) {
11398       /* Keep trying for clean exit */
11399       return;
11400     }
11401
11402     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11403
11404     if (telnetISR != NULL) {
11405       RemoveInputSource(telnetISR);
11406     }
11407     if (icsPR != NoProc) {
11408       DestroyChildProcess(icsPR, TRUE);
11409     }
11410
11411     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11412     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11413
11414     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11415     /* make sure this other one finishes before killing it!                  */
11416     if(endingGame) { int count = 0;
11417         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11418         while(endingGame && count++ < 10) DoSleep(1);
11419         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11420     }
11421
11422     /* Kill off chess programs */
11423     if (first.pr != NoProc) {
11424         ExitAnalyzeMode();
11425         
11426         DoSleep( appData.delayBeforeQuit );
11427         SendToProgram("quit\n", &first);
11428         DoSleep( appData.delayAfterQuit );
11429         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11430     }
11431     if (second.pr != NoProc) {
11432         DoSleep( appData.delayBeforeQuit );
11433         SendToProgram("quit\n", &second);
11434         DoSleep( appData.delayAfterQuit );
11435         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11436     }
11437     if (first.isr != NULL) {
11438         RemoveInputSource(first.isr);
11439     }
11440     if (second.isr != NULL) {
11441         RemoveInputSource(second.isr);
11442     }
11443
11444     ShutDownFrontEnd();
11445     exit(status);
11446 }
11447
11448 void
11449 PauseEvent()
11450 {
11451     if (appData.debugMode)
11452         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11453     if (pausing) {
11454         pausing = FALSE;
11455         ModeHighlight();
11456         if (gameMode == MachinePlaysWhite ||
11457             gameMode == MachinePlaysBlack) {
11458             StartClocks();
11459         } else {
11460             DisplayBothClocks();
11461         }
11462         if (gameMode == PlayFromGameFile) {
11463             if (appData.timeDelay >= 0) 
11464                 AutoPlayGameLoop();
11465         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11466             Reset(FALSE, TRUE);
11467             SendToICS(ics_prefix);
11468             SendToICS("refresh\n");
11469         } else if (currentMove < forwardMostMove) {
11470             ForwardInner(forwardMostMove);
11471         }
11472         pauseExamInvalid = FALSE;
11473     } else {
11474         switch (gameMode) {
11475           default:
11476             return;
11477           case IcsExamining:
11478             pauseExamForwardMostMove = forwardMostMove;
11479             pauseExamInvalid = FALSE;
11480             /* fall through */
11481           case IcsObserving:
11482           case IcsPlayingWhite:
11483           case IcsPlayingBlack:
11484             pausing = TRUE;
11485             ModeHighlight();
11486             return;
11487           case PlayFromGameFile:
11488             (void) StopLoadGameTimer();
11489             pausing = TRUE;
11490             ModeHighlight();
11491             break;
11492           case BeginningOfGame:
11493             if (appData.icsActive) return;
11494             /* else fall through */
11495           case MachinePlaysWhite:
11496           case MachinePlaysBlack:
11497           case TwoMachinesPlay:
11498             if (forwardMostMove == 0)
11499               return;           /* don't pause if no one has moved */
11500             if ((gameMode == MachinePlaysWhite &&
11501                  !WhiteOnMove(forwardMostMove)) ||
11502                 (gameMode == MachinePlaysBlack &&
11503                  WhiteOnMove(forwardMostMove))) {
11504                 StopClocks();
11505             }
11506             pausing = TRUE;
11507             ModeHighlight();
11508             break;
11509         }
11510     }
11511 }
11512
11513 void
11514 EditCommentEvent()
11515 {
11516     char title[MSG_SIZ];
11517
11518     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11519         strcpy(title, _("Edit comment"));
11520     } else {
11521         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11522                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11523                 parseList[currentMove - 1]);
11524     }
11525
11526     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11527 }
11528
11529
11530 void
11531 EditTagsEvent()
11532 {
11533     char *tags = PGNTags(&gameInfo);
11534     EditTagsPopUp(tags);
11535     free(tags);
11536 }
11537
11538 void
11539 AnalyzeModeEvent()
11540 {
11541     if (appData.noChessProgram || gameMode == AnalyzeMode)
11542       return;
11543
11544     if (gameMode != AnalyzeFile) {
11545         if (!appData.icsEngineAnalyze) {
11546                EditGameEvent();
11547                if (gameMode != EditGame) return;
11548         }
11549         ResurrectChessProgram();
11550         SendToProgram("analyze\n", &first);
11551         first.analyzing = TRUE;
11552         /*first.maybeThinking = TRUE;*/
11553         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11554         EngineOutputPopUp();
11555     }
11556     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11557     pausing = FALSE;
11558     ModeHighlight();
11559     SetGameInfo();
11560
11561     StartAnalysisClock();
11562     GetTimeMark(&lastNodeCountTime);
11563     lastNodeCount = 0;
11564 }
11565
11566 void
11567 AnalyzeFileEvent()
11568 {
11569     if (appData.noChessProgram || gameMode == AnalyzeFile)
11570       return;
11571
11572     if (gameMode != AnalyzeMode) {
11573         EditGameEvent();
11574         if (gameMode != EditGame) return;
11575         ResurrectChessProgram();
11576         SendToProgram("analyze\n", &first);
11577         first.analyzing = TRUE;
11578         /*first.maybeThinking = TRUE;*/
11579         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11580         EngineOutputPopUp();
11581     }
11582     gameMode = AnalyzeFile;
11583     pausing = FALSE;
11584     ModeHighlight();
11585     SetGameInfo();
11586
11587     StartAnalysisClock();
11588     GetTimeMark(&lastNodeCountTime);
11589     lastNodeCount = 0;
11590 }
11591
11592 void
11593 MachineWhiteEvent()
11594 {
11595     char buf[MSG_SIZ];
11596     char *bookHit = NULL;
11597
11598     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11599       return;
11600
11601
11602     if (gameMode == PlayFromGameFile || 
11603         gameMode == TwoMachinesPlay  || 
11604         gameMode == Training         || 
11605         gameMode == AnalyzeMode      || 
11606         gameMode == EndOfGame)
11607         EditGameEvent();
11608
11609     if (gameMode == EditPosition) 
11610         EditPositionDone(TRUE);
11611
11612     if (!WhiteOnMove(currentMove)) {
11613         DisplayError(_("It is not White's turn"), 0);
11614         return;
11615     }
11616   
11617     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11618       ExitAnalyzeMode();
11619
11620     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11621         gameMode == AnalyzeFile)
11622         TruncateGame();
11623
11624     ResurrectChessProgram();    /* in case it isn't running */
11625     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11626         gameMode = MachinePlaysWhite;
11627         ResetClocks();
11628     } else
11629     gameMode = MachinePlaysWhite;
11630     pausing = FALSE;
11631     ModeHighlight();
11632     SetGameInfo();
11633     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11634     DisplayTitle(buf);
11635     if (first.sendName) {
11636       sprintf(buf, "name %s\n", gameInfo.black);
11637       SendToProgram(buf, &first);
11638     }
11639     if (first.sendTime) {
11640       if (first.useColors) {
11641         SendToProgram("black\n", &first); /*gnu kludge*/
11642       }
11643       SendTimeRemaining(&first, TRUE);
11644     }
11645     if (first.useColors) {
11646       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11647     }
11648     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11649     SetMachineThinkingEnables();
11650     first.maybeThinking = TRUE;
11651     StartClocks();
11652     firstMove = FALSE;
11653
11654     if (appData.autoFlipView && !flipView) {
11655       flipView = !flipView;
11656       DrawPosition(FALSE, NULL);
11657       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11658     }
11659
11660     if(bookHit) { // [HGM] book: simulate book reply
11661         static char bookMove[MSG_SIZ]; // a bit generous?
11662
11663         programStats.nodes = programStats.depth = programStats.time = 
11664         programStats.score = programStats.got_only_move = 0;
11665         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11666
11667         strcpy(bookMove, "move ");
11668         strcat(bookMove, bookHit);
11669         HandleMachineMove(bookMove, &first);
11670     }
11671 }
11672
11673 void
11674 MachineBlackEvent()
11675 {
11676     char buf[MSG_SIZ];
11677    char *bookHit = NULL;
11678
11679     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11680         return;
11681
11682
11683     if (gameMode == PlayFromGameFile || 
11684         gameMode == TwoMachinesPlay  || 
11685         gameMode == Training         || 
11686         gameMode == AnalyzeMode      || 
11687         gameMode == EndOfGame)
11688         EditGameEvent();
11689
11690     if (gameMode == EditPosition) 
11691         EditPositionDone(TRUE);
11692
11693     if (WhiteOnMove(currentMove)) {
11694         DisplayError(_("It is not Black's turn"), 0);
11695         return;
11696     }
11697     
11698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11699       ExitAnalyzeMode();
11700
11701     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11702         gameMode == AnalyzeFile)
11703         TruncateGame();
11704
11705     ResurrectChessProgram();    /* in case it isn't running */
11706     gameMode = MachinePlaysBlack;
11707     pausing = FALSE;
11708     ModeHighlight();
11709     SetGameInfo();
11710     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11711     DisplayTitle(buf);
11712     if (first.sendName) {
11713       sprintf(buf, "name %s\n", gameInfo.white);
11714       SendToProgram(buf, &first);
11715     }
11716     if (first.sendTime) {
11717       if (first.useColors) {
11718         SendToProgram("white\n", &first); /*gnu kludge*/
11719       }
11720       SendTimeRemaining(&first, FALSE);
11721     }
11722     if (first.useColors) {
11723       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11724     }
11725     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11726     SetMachineThinkingEnables();
11727     first.maybeThinking = TRUE;
11728     StartClocks();
11729
11730     if (appData.autoFlipView && flipView) {
11731       flipView = !flipView;
11732       DrawPosition(FALSE, NULL);
11733       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11734     }
11735     if(bookHit) { // [HGM] book: simulate book reply
11736         static char bookMove[MSG_SIZ]; // a bit generous?
11737
11738         programStats.nodes = programStats.depth = programStats.time = 
11739         programStats.score = programStats.got_only_move = 0;
11740         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11741
11742         strcpy(bookMove, "move ");
11743         strcat(bookMove, bookHit);
11744         HandleMachineMove(bookMove, &first);
11745     }
11746 }
11747
11748
11749 void
11750 DisplayTwoMachinesTitle()
11751 {
11752     char buf[MSG_SIZ];
11753     if (appData.matchGames > 0) {
11754         if (first.twoMachinesColor[0] == 'w') {
11755             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11756                     gameInfo.white, gameInfo.black,
11757                     first.matchWins, second.matchWins,
11758                     matchGame - 1 - (first.matchWins + second.matchWins));
11759         } else {
11760             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11761                     gameInfo.white, gameInfo.black,
11762                     second.matchWins, first.matchWins,
11763                     matchGame - 1 - (first.matchWins + second.matchWins));
11764         }
11765     } else {
11766         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11767     }
11768     DisplayTitle(buf);
11769 }
11770
11771 void
11772 TwoMachinesEvent P((void))
11773 {
11774     int i;
11775     char buf[MSG_SIZ];
11776     ChessProgramState *onmove;
11777     char *bookHit = NULL;
11778     
11779     if (appData.noChessProgram) return;
11780
11781     switch (gameMode) {
11782       case TwoMachinesPlay:
11783         return;
11784       case MachinePlaysWhite:
11785       case MachinePlaysBlack:
11786         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11787             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11788             return;
11789         }
11790         /* fall through */
11791       case BeginningOfGame:
11792       case PlayFromGameFile:
11793       case EndOfGame:
11794         EditGameEvent();
11795         if (gameMode != EditGame) return;
11796         break;
11797       case EditPosition:
11798         EditPositionDone(TRUE);
11799         break;
11800       case AnalyzeMode:
11801       case AnalyzeFile:
11802         ExitAnalyzeMode();
11803         break;
11804       case EditGame:
11805       default:
11806         break;
11807     }
11808
11809 //    forwardMostMove = currentMove;
11810     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11811     ResurrectChessProgram();    /* in case first program isn't running */
11812
11813     if (second.pr == NULL) {
11814         StartChessProgram(&second);
11815         if (second.protocolVersion == 1) {
11816           TwoMachinesEventIfReady();
11817         } else {
11818           /* kludge: allow timeout for initial "feature" command */
11819           FreezeUI();
11820           DisplayMessage("", _("Starting second chess program"));
11821           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11822         }
11823         return;
11824     }
11825     DisplayMessage("", "");
11826     InitChessProgram(&second, FALSE);
11827     SendToProgram("force\n", &second);
11828     if (startedFromSetupPosition) {
11829         SendBoard(&second, backwardMostMove);
11830     if (appData.debugMode) {
11831         fprintf(debugFP, "Two Machines\n");
11832     }
11833     }
11834     for (i = backwardMostMove; i < forwardMostMove; i++) {
11835         SendMoveToProgram(i, &second);
11836     }
11837
11838     gameMode = TwoMachinesPlay;
11839     pausing = FALSE;
11840     ModeHighlight();
11841     SetGameInfo();
11842     DisplayTwoMachinesTitle();
11843     firstMove = TRUE;
11844     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11845         onmove = &first;
11846     } else {
11847         onmove = &second;
11848     }
11849
11850     SendToProgram(first.computerString, &first);
11851     if (first.sendName) {
11852       sprintf(buf, "name %s\n", second.tidy);
11853       SendToProgram(buf, &first);
11854     }
11855     SendToProgram(second.computerString, &second);
11856     if (second.sendName) {
11857       sprintf(buf, "name %s\n", first.tidy);
11858       SendToProgram(buf, &second);
11859     }
11860
11861     ResetClocks();
11862     if (!first.sendTime || !second.sendTime) {
11863         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11864         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11865     }
11866     if (onmove->sendTime) {
11867       if (onmove->useColors) {
11868         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11869       }
11870       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11871     }
11872     if (onmove->useColors) {
11873       SendToProgram(onmove->twoMachinesColor, onmove);
11874     }
11875     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11876 //    SendToProgram("go\n", onmove);
11877     onmove->maybeThinking = TRUE;
11878     SetMachineThinkingEnables();
11879
11880     StartClocks();
11881
11882     if(bookHit) { // [HGM] book: simulate book reply
11883         static char bookMove[MSG_SIZ]; // a bit generous?
11884
11885         programStats.nodes = programStats.depth = programStats.time = 
11886         programStats.score = programStats.got_only_move = 0;
11887         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11888
11889         strcpy(bookMove, "move ");
11890         strcat(bookMove, bookHit);
11891         savedMessage = bookMove; // args for deferred call
11892         savedState = onmove;
11893         ScheduleDelayedEvent(DeferredBookMove, 1);
11894     }
11895 }
11896
11897 void
11898 TrainingEvent()
11899 {
11900     if (gameMode == Training) {
11901       SetTrainingModeOff();
11902       gameMode = PlayFromGameFile;
11903       DisplayMessage("", _("Training mode off"));
11904     } else {
11905       gameMode = Training;
11906       animateTraining = appData.animate;
11907
11908       /* make sure we are not already at the end of the game */
11909       if (currentMove < forwardMostMove) {
11910         SetTrainingModeOn();
11911         DisplayMessage("", _("Training mode on"));
11912       } else {
11913         gameMode = PlayFromGameFile;
11914         DisplayError(_("Already at end of game"), 0);
11915       }
11916     }
11917     ModeHighlight();
11918 }
11919
11920 void
11921 IcsClientEvent()
11922 {
11923     if (!appData.icsActive) return;
11924     switch (gameMode) {
11925       case IcsPlayingWhite:
11926       case IcsPlayingBlack:
11927       case IcsObserving:
11928       case IcsIdle:
11929       case BeginningOfGame:
11930       case IcsExamining:
11931         return;
11932
11933       case EditGame:
11934         break;
11935
11936       case EditPosition:
11937         EditPositionDone(TRUE);
11938         break;
11939
11940       case AnalyzeMode:
11941       case AnalyzeFile:
11942         ExitAnalyzeMode();
11943         break;
11944         
11945       default:
11946         EditGameEvent();
11947         break;
11948     }
11949
11950     gameMode = IcsIdle;
11951     ModeHighlight();
11952     return;
11953 }
11954
11955
11956 void
11957 EditGameEvent()
11958 {
11959     int i;
11960
11961     switch (gameMode) {
11962       case Training:
11963         SetTrainingModeOff();
11964         break;
11965       case MachinePlaysWhite:
11966       case MachinePlaysBlack:
11967       case BeginningOfGame:
11968         SendToProgram("force\n", &first);
11969         SetUserThinkingEnables();
11970         break;
11971       case PlayFromGameFile:
11972         (void) StopLoadGameTimer();
11973         if (gameFileFP != NULL) {
11974             gameFileFP = NULL;
11975         }
11976         break;
11977       case EditPosition:
11978         EditPositionDone(TRUE);
11979         break;
11980       case AnalyzeMode:
11981       case AnalyzeFile:
11982         ExitAnalyzeMode();
11983         SendToProgram("force\n", &first);
11984         break;
11985       case TwoMachinesPlay:
11986         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11987         ResurrectChessProgram();
11988         SetUserThinkingEnables();
11989         break;
11990       case EndOfGame:
11991         ResurrectChessProgram();
11992         break;
11993       case IcsPlayingBlack:
11994       case IcsPlayingWhite:
11995         DisplayError(_("Warning: You are still playing a game"), 0);
11996         break;
11997       case IcsObserving:
11998         DisplayError(_("Warning: You are still observing a game"), 0);
11999         break;
12000       case IcsExamining:
12001         DisplayError(_("Warning: You are still examining a game"), 0);
12002         break;
12003       case IcsIdle:
12004         break;
12005       case EditGame:
12006       default:
12007         return;
12008     }
12009     
12010     pausing = FALSE;
12011     StopClocks();
12012     first.offeredDraw = second.offeredDraw = 0;
12013
12014     if (gameMode == PlayFromGameFile) {
12015         whiteTimeRemaining = timeRemaining[0][currentMove];
12016         blackTimeRemaining = timeRemaining[1][currentMove];
12017         DisplayTitle("");
12018     }
12019
12020     if (gameMode == MachinePlaysWhite ||
12021         gameMode == MachinePlaysBlack ||
12022         gameMode == TwoMachinesPlay ||
12023         gameMode == EndOfGame) {
12024         i = forwardMostMove;
12025         while (i > currentMove) {
12026             SendToProgram("undo\n", &first);
12027             i--;
12028         }
12029         whiteTimeRemaining = timeRemaining[0][currentMove];
12030         blackTimeRemaining = timeRemaining[1][currentMove];
12031         DisplayBothClocks();
12032         if (whiteFlag || blackFlag) {
12033             whiteFlag = blackFlag = 0;
12034         }
12035         DisplayTitle("");
12036     }           
12037     
12038     gameMode = EditGame;
12039     ModeHighlight();
12040     SetGameInfo();
12041 }
12042
12043
12044 void
12045 EditPositionEvent()
12046 {
12047     if (gameMode == EditPosition) {
12048         EditGameEvent();
12049         return;
12050     }
12051     
12052     EditGameEvent();
12053     if (gameMode != EditGame) return;
12054     
12055     gameMode = EditPosition;
12056     ModeHighlight();
12057     SetGameInfo();
12058     if (currentMove > 0)
12059       CopyBoard(boards[0], boards[currentMove]);
12060     
12061     blackPlaysFirst = !WhiteOnMove(currentMove);
12062     ResetClocks();
12063     currentMove = forwardMostMove = backwardMostMove = 0;
12064     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12065     DisplayMove(-1);
12066 }
12067
12068 void
12069 ExitAnalyzeMode()
12070 {
12071     /* [DM] icsEngineAnalyze - possible call from other functions */
12072     if (appData.icsEngineAnalyze) {
12073         appData.icsEngineAnalyze = FALSE;
12074
12075         DisplayMessage("",_("Close ICS engine analyze..."));
12076     }
12077     if (first.analysisSupport && first.analyzing) {
12078       SendToProgram("exit\n", &first);
12079       first.analyzing = FALSE;
12080     }
12081     thinkOutput[0] = NULLCHAR;
12082 }
12083
12084 void
12085 EditPositionDone(Boolean fakeRights)
12086 {
12087     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12088
12089     startedFromSetupPosition = TRUE;
12090     InitChessProgram(&first, FALSE);
12091     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12092       boards[0][EP_STATUS] = EP_NONE;
12093       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12094     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12095         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12096         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12097       } else boards[0][CASTLING][2] = NoRights;
12098     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12099         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12100         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12101       } else boards[0][CASTLING][5] = NoRights;
12102     }
12103     SendToProgram("force\n", &first);
12104     if (blackPlaysFirst) {
12105         strcpy(moveList[0], "");
12106         strcpy(parseList[0], "");
12107         currentMove = forwardMostMove = backwardMostMove = 1;
12108         CopyBoard(boards[1], boards[0]);
12109     } else {
12110         currentMove = forwardMostMove = backwardMostMove = 0;
12111     }
12112     SendBoard(&first, forwardMostMove);
12113     if (appData.debugMode) {
12114         fprintf(debugFP, "EditPosDone\n");
12115     }
12116     DisplayTitle("");
12117     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12118     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12119     gameMode = EditGame;
12120     ModeHighlight();
12121     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12122     ClearHighlights(); /* [AS] */
12123 }
12124
12125 /* Pause for `ms' milliseconds */
12126 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12127 void
12128 TimeDelay(ms)
12129      long ms;
12130 {
12131     TimeMark m1, m2;
12132
12133     GetTimeMark(&m1);
12134     do {
12135         GetTimeMark(&m2);
12136     } while (SubtractTimeMarks(&m2, &m1) < ms);
12137 }
12138
12139 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12140 void
12141 SendMultiLineToICS(buf)
12142      char *buf;
12143 {
12144     char temp[MSG_SIZ+1], *p;
12145     int len;
12146
12147     len = strlen(buf);
12148     if (len > MSG_SIZ)
12149       len = MSG_SIZ;
12150   
12151     strncpy(temp, buf, len);
12152     temp[len] = 0;
12153
12154     p = temp;
12155     while (*p) {
12156         if (*p == '\n' || *p == '\r')
12157           *p = ' ';
12158         ++p;
12159     }
12160
12161     strcat(temp, "\n");
12162     SendToICS(temp);
12163     SendToPlayer(temp, strlen(temp));
12164 }
12165
12166 void
12167 SetWhiteToPlayEvent()
12168 {
12169     if (gameMode == EditPosition) {
12170         blackPlaysFirst = FALSE;
12171         DisplayBothClocks();    /* works because currentMove is 0 */
12172     } else if (gameMode == IcsExamining) {
12173         SendToICS(ics_prefix);
12174         SendToICS("tomove white\n");
12175     }
12176 }
12177
12178 void
12179 SetBlackToPlayEvent()
12180 {
12181     if (gameMode == EditPosition) {
12182         blackPlaysFirst = TRUE;
12183         currentMove = 1;        /* kludge */
12184         DisplayBothClocks();
12185         currentMove = 0;
12186     } else if (gameMode == IcsExamining) {
12187         SendToICS(ics_prefix);
12188         SendToICS("tomove black\n");
12189     }
12190 }
12191
12192 void
12193 EditPositionMenuEvent(selection, x, y)
12194      ChessSquare selection;
12195      int x, y;
12196 {
12197     char buf[MSG_SIZ];
12198     ChessSquare piece = boards[0][y][x];
12199
12200     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12201
12202     switch (selection) {
12203       case ClearBoard:
12204         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12205             SendToICS(ics_prefix);
12206             SendToICS("bsetup clear\n");
12207         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12208             SendToICS(ics_prefix);
12209             SendToICS("clearboard\n");
12210         } else {
12211             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12212                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12213                 for (y = 0; y < BOARD_HEIGHT; y++) {
12214                     if (gameMode == IcsExamining) {
12215                         if (boards[currentMove][y][x] != EmptySquare) {
12216                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12217                                     AAA + x, ONE + y);
12218                             SendToICS(buf);
12219                         }
12220                     } else {
12221                         boards[0][y][x] = p;
12222                     }
12223                 }
12224             }
12225         }
12226         if (gameMode == EditPosition) {
12227             DrawPosition(FALSE, boards[0]);
12228         }
12229         break;
12230
12231       case WhitePlay:
12232         SetWhiteToPlayEvent();
12233         break;
12234
12235       case BlackPlay:
12236         SetBlackToPlayEvent();
12237         break;
12238
12239       case EmptySquare:
12240         if (gameMode == IcsExamining) {
12241             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12242             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12243             SendToICS(buf);
12244         } else {
12245             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12246                 if(x == BOARD_LEFT-2) {
12247                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12248                     boards[0][y][1] = 0;
12249                 } else
12250                 if(x == BOARD_RGHT+1) {
12251                     if(y >= gameInfo.holdingsSize) break;
12252                     boards[0][y][BOARD_WIDTH-2] = 0;
12253                 } else break;
12254             }
12255             boards[0][y][x] = EmptySquare;
12256             DrawPosition(FALSE, boards[0]);
12257         }
12258         break;
12259
12260       case PromotePiece:
12261         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12262            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12263             selection = (ChessSquare) (PROMOTED piece);
12264         } else if(piece == EmptySquare) selection = WhiteSilver;
12265         else selection = (ChessSquare)((int)piece - 1);
12266         goto defaultlabel;
12267
12268       case DemotePiece:
12269         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12270            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12271             selection = (ChessSquare) (DEMOTED piece);
12272         } else if(piece == EmptySquare) selection = BlackSilver;
12273         else selection = (ChessSquare)((int)piece + 1);       
12274         goto defaultlabel;
12275
12276       case WhiteQueen:
12277       case BlackQueen:
12278         if(gameInfo.variant == VariantShatranj ||
12279            gameInfo.variant == VariantXiangqi  ||
12280            gameInfo.variant == VariantCourier  ||
12281            gameInfo.variant == VariantMakruk     )
12282             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12283         goto defaultlabel;
12284
12285       case WhiteKing:
12286       case BlackKing:
12287         if(gameInfo.variant == VariantXiangqi)
12288             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12289         if(gameInfo.variant == VariantKnightmate)
12290             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12291       default:
12292         defaultlabel:
12293         if (gameMode == IcsExamining) {
12294             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12295             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12296                     PieceToChar(selection), AAA + x, ONE + y);
12297             SendToICS(buf);
12298         } else {
12299             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12300                 int n;
12301                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12302                     n = PieceToNumber(selection - BlackPawn);
12303                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12304                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12305                     boards[0][BOARD_HEIGHT-1-n][1]++;
12306                 } else
12307                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12308                     n = PieceToNumber(selection);
12309                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12310                     boards[0][n][BOARD_WIDTH-1] = selection;
12311                     boards[0][n][BOARD_WIDTH-2]++;
12312                 }
12313             } else
12314             boards[0][y][x] = selection;
12315             DrawPosition(TRUE, boards[0]);
12316         }
12317         break;
12318     }
12319 }
12320
12321
12322 void
12323 DropMenuEvent(selection, x, y)
12324      ChessSquare selection;
12325      int x, y;
12326 {
12327     ChessMove moveType;
12328
12329     switch (gameMode) {
12330       case IcsPlayingWhite:
12331       case MachinePlaysBlack:
12332         if (!WhiteOnMove(currentMove)) {
12333             DisplayMoveError(_("It is Black's turn"));
12334             return;
12335         }
12336         moveType = WhiteDrop;
12337         break;
12338       case IcsPlayingBlack:
12339       case MachinePlaysWhite:
12340         if (WhiteOnMove(currentMove)) {
12341             DisplayMoveError(_("It is White's turn"));
12342             return;
12343         }
12344         moveType = BlackDrop;
12345         break;
12346       case EditGame:
12347         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12348         break;
12349       default:
12350         return;
12351     }
12352
12353     if (moveType == BlackDrop && selection < BlackPawn) {
12354       selection = (ChessSquare) ((int) selection
12355                                  + (int) BlackPawn - (int) WhitePawn);
12356     }
12357     if (boards[currentMove][y][x] != EmptySquare) {
12358         DisplayMoveError(_("That square is occupied"));
12359         return;
12360     }
12361
12362     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12363 }
12364
12365 void
12366 AcceptEvent()
12367 {
12368     /* Accept a pending offer of any kind from opponent */
12369     
12370     if (appData.icsActive) {
12371         SendToICS(ics_prefix);
12372         SendToICS("accept\n");
12373     } else if (cmailMsgLoaded) {
12374         if (currentMove == cmailOldMove &&
12375             commentList[cmailOldMove] != NULL &&
12376             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12377                    "Black offers a draw" : "White offers a draw")) {
12378             TruncateGame();
12379             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12380             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12381         } else {
12382             DisplayError(_("There is no pending offer on this move"), 0);
12383             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12384         }
12385     } else {
12386         /* Not used for offers from chess program */
12387     }
12388 }
12389
12390 void
12391 DeclineEvent()
12392 {
12393     /* Decline a pending offer of any kind from opponent */
12394     
12395     if (appData.icsActive) {
12396         SendToICS(ics_prefix);
12397         SendToICS("decline\n");
12398     } else if (cmailMsgLoaded) {
12399         if (currentMove == cmailOldMove &&
12400             commentList[cmailOldMove] != NULL &&
12401             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12402                    "Black offers a draw" : "White offers a draw")) {
12403 #ifdef NOTDEF
12404             AppendComment(cmailOldMove, "Draw declined", TRUE);
12405             DisplayComment(cmailOldMove - 1, "Draw declined");
12406 #endif /*NOTDEF*/
12407         } else {
12408             DisplayError(_("There is no pending offer on this move"), 0);
12409         }
12410     } else {
12411         /* Not used for offers from chess program */
12412     }
12413 }
12414
12415 void
12416 RematchEvent()
12417 {
12418     /* Issue ICS rematch command */
12419     if (appData.icsActive) {
12420         SendToICS(ics_prefix);
12421         SendToICS("rematch\n");
12422     }
12423 }
12424
12425 void
12426 CallFlagEvent()
12427 {
12428     /* Call your opponent's flag (claim a win on time) */
12429     if (appData.icsActive) {
12430         SendToICS(ics_prefix);
12431         SendToICS("flag\n");
12432     } else {
12433         switch (gameMode) {
12434           default:
12435             return;
12436           case MachinePlaysWhite:
12437             if (whiteFlag) {
12438                 if (blackFlag)
12439                   GameEnds(GameIsDrawn, "Both players ran out of time",
12440                            GE_PLAYER);
12441                 else
12442                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12443             } else {
12444                 DisplayError(_("Your opponent is not out of time"), 0);
12445             }
12446             break;
12447           case MachinePlaysBlack:
12448             if (blackFlag) {
12449                 if (whiteFlag)
12450                   GameEnds(GameIsDrawn, "Both players ran out of time",
12451                            GE_PLAYER);
12452                 else
12453                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12454             } else {
12455                 DisplayError(_("Your opponent is not out of time"), 0);
12456             }
12457             break;
12458         }
12459     }
12460 }
12461
12462 void
12463 DrawEvent()
12464 {
12465     /* Offer draw or accept pending draw offer from opponent */
12466     
12467     if (appData.icsActive) {
12468         /* Note: tournament rules require draw offers to be
12469            made after you make your move but before you punch
12470            your clock.  Currently ICS doesn't let you do that;
12471            instead, you immediately punch your clock after making
12472            a move, but you can offer a draw at any time. */
12473         
12474         SendToICS(ics_prefix);
12475         SendToICS("draw\n");
12476         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12477     } else if (cmailMsgLoaded) {
12478         if (currentMove == cmailOldMove &&
12479             commentList[cmailOldMove] != NULL &&
12480             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12481                    "Black offers a draw" : "White offers a draw")) {
12482             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12483             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12484         } else if (currentMove == cmailOldMove + 1) {
12485             char *offer = WhiteOnMove(cmailOldMove) ?
12486               "White offers a draw" : "Black offers a draw";
12487             AppendComment(currentMove, offer, TRUE);
12488             DisplayComment(currentMove - 1, offer);
12489             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12490         } else {
12491             DisplayError(_("You must make your move before offering a draw"), 0);
12492             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12493         }
12494     } else if (first.offeredDraw) {
12495         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12496     } else {
12497         if (first.sendDrawOffers) {
12498             SendToProgram("draw\n", &first);
12499             userOfferedDraw = TRUE;
12500         }
12501     }
12502 }
12503
12504 void
12505 AdjournEvent()
12506 {
12507     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12508     
12509     if (appData.icsActive) {
12510         SendToICS(ics_prefix);
12511         SendToICS("adjourn\n");
12512     } else {
12513         /* Currently GNU Chess doesn't offer or accept Adjourns */
12514     }
12515 }
12516
12517
12518 void
12519 AbortEvent()
12520 {
12521     /* Offer Abort or accept pending Abort offer from opponent */
12522     
12523     if (appData.icsActive) {
12524         SendToICS(ics_prefix);
12525         SendToICS("abort\n");
12526     } else {
12527         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12528     }
12529 }
12530
12531 void
12532 ResignEvent()
12533 {
12534     /* Resign.  You can do this even if it's not your turn. */
12535     
12536     if (appData.icsActive) {
12537         SendToICS(ics_prefix);
12538         SendToICS("resign\n");
12539     } else {
12540         switch (gameMode) {
12541           case MachinePlaysWhite:
12542             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12543             break;
12544           case MachinePlaysBlack:
12545             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12546             break;
12547           case EditGame:
12548             if (cmailMsgLoaded) {
12549                 TruncateGame();
12550                 if (WhiteOnMove(cmailOldMove)) {
12551                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12552                 } else {
12553                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12554                 }
12555                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12556             }
12557             break;
12558           default:
12559             break;
12560         }
12561     }
12562 }
12563
12564
12565 void
12566 StopObservingEvent()
12567 {
12568     /* Stop observing current games */
12569     SendToICS(ics_prefix);
12570     SendToICS("unobserve\n");
12571 }
12572
12573 void
12574 StopExaminingEvent()
12575 {
12576     /* Stop observing current game */
12577     SendToICS(ics_prefix);
12578     SendToICS("unexamine\n");
12579 }
12580
12581 void
12582 ForwardInner(target)
12583      int target;
12584 {
12585     int limit;
12586
12587     if (appData.debugMode)
12588         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12589                 target, currentMove, forwardMostMove);
12590
12591     if (gameMode == EditPosition)
12592       return;
12593
12594     if (gameMode == PlayFromGameFile && !pausing)
12595       PauseEvent();
12596     
12597     if (gameMode == IcsExamining && pausing)
12598       limit = pauseExamForwardMostMove;
12599     else
12600       limit = forwardMostMove;
12601     
12602     if (target > limit) target = limit;
12603
12604     if (target > 0 && moveList[target - 1][0]) {
12605         int fromX, fromY, toX, toY;
12606         toX = moveList[target - 1][2] - AAA;
12607         toY = moveList[target - 1][3] - ONE;
12608         if (moveList[target - 1][1] == '@') {
12609             if (appData.highlightLastMove) {
12610                 SetHighlights(-1, -1, toX, toY);
12611             }
12612         } else {
12613             fromX = moveList[target - 1][0] - AAA;
12614             fromY = moveList[target - 1][1] - ONE;
12615             if (target == currentMove + 1) {
12616                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12617             }
12618             if (appData.highlightLastMove) {
12619                 SetHighlights(fromX, fromY, toX, toY);
12620             }
12621         }
12622     }
12623     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12624         gameMode == Training || gameMode == PlayFromGameFile || 
12625         gameMode == AnalyzeFile) {
12626         while (currentMove < target) {
12627             SendMoveToProgram(currentMove++, &first);
12628         }
12629     } else {
12630         currentMove = target;
12631     }
12632     
12633     if (gameMode == EditGame || gameMode == EndOfGame) {
12634         whiteTimeRemaining = timeRemaining[0][currentMove];
12635         blackTimeRemaining = timeRemaining[1][currentMove];
12636     }
12637     DisplayBothClocks();
12638     DisplayMove(currentMove - 1);
12639     DrawPosition(FALSE, boards[currentMove]);
12640     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12641     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12642         DisplayComment(currentMove - 1, commentList[currentMove]);
12643     }
12644 }
12645
12646
12647 void
12648 ForwardEvent()
12649 {
12650     if (gameMode == IcsExamining && !pausing) {
12651         SendToICS(ics_prefix);
12652         SendToICS("forward\n");
12653     } else {
12654         ForwardInner(currentMove + 1);
12655     }
12656 }
12657
12658 void
12659 ToEndEvent()
12660 {
12661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12662         /* to optimze, we temporarily turn off analysis mode while we feed
12663          * the remaining moves to the engine. Otherwise we get analysis output
12664          * after each move.
12665          */ 
12666         if (first.analysisSupport) {
12667           SendToProgram("exit\nforce\n", &first);
12668           first.analyzing = FALSE;
12669         }
12670     }
12671         
12672     if (gameMode == IcsExamining && !pausing) {
12673         SendToICS(ics_prefix);
12674         SendToICS("forward 999999\n");
12675     } else {
12676         ForwardInner(forwardMostMove);
12677     }
12678
12679     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12680         /* we have fed all the moves, so reactivate analysis mode */
12681         SendToProgram("analyze\n", &first);
12682         first.analyzing = TRUE;
12683         /*first.maybeThinking = TRUE;*/
12684         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12685     }
12686 }
12687
12688 void
12689 BackwardInner(target)
12690      int target;
12691 {
12692     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12693
12694     if (appData.debugMode)
12695         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12696                 target, currentMove, forwardMostMove);
12697
12698     if (gameMode == EditPosition) return;
12699     if (currentMove <= backwardMostMove) {
12700         ClearHighlights();
12701         DrawPosition(full_redraw, boards[currentMove]);
12702         return;
12703     }
12704     if (gameMode == PlayFromGameFile && !pausing)
12705       PauseEvent();
12706     
12707     if (moveList[target][0]) {
12708         int fromX, fromY, toX, toY;
12709         toX = moveList[target][2] - AAA;
12710         toY = moveList[target][3] - ONE;
12711         if (moveList[target][1] == '@') {
12712             if (appData.highlightLastMove) {
12713                 SetHighlights(-1, -1, toX, toY);
12714             }
12715         } else {
12716             fromX = moveList[target][0] - AAA;
12717             fromY = moveList[target][1] - ONE;
12718             if (target == currentMove - 1) {
12719                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12720             }
12721             if (appData.highlightLastMove) {
12722                 SetHighlights(fromX, fromY, toX, toY);
12723             }
12724         }
12725     }
12726     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12727         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12728         while (currentMove > target) {
12729             SendToProgram("undo\n", &first);
12730             currentMove--;
12731         }
12732     } else {
12733         currentMove = target;
12734     }
12735     
12736     if (gameMode == EditGame || gameMode == EndOfGame) {
12737         whiteTimeRemaining = timeRemaining[0][currentMove];
12738         blackTimeRemaining = timeRemaining[1][currentMove];
12739     }
12740     DisplayBothClocks();
12741     DisplayMove(currentMove - 1);
12742     DrawPosition(full_redraw, boards[currentMove]);
12743     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12744     // [HGM] PV info: routine tests if comment empty
12745     DisplayComment(currentMove - 1, commentList[currentMove]);
12746 }
12747
12748 void
12749 BackwardEvent()
12750 {
12751     if (gameMode == IcsExamining && !pausing) {
12752         SendToICS(ics_prefix);
12753         SendToICS("backward\n");
12754     } else {
12755         BackwardInner(currentMove - 1);
12756     }
12757 }
12758
12759 void
12760 ToStartEvent()
12761 {
12762     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12763         /* to optimize, we temporarily turn off analysis mode while we undo
12764          * all the moves. Otherwise we get analysis output after each undo.
12765          */ 
12766         if (first.analysisSupport) {
12767           SendToProgram("exit\nforce\n", &first);
12768           first.analyzing = FALSE;
12769         }
12770     }
12771
12772     if (gameMode == IcsExamining && !pausing) {
12773         SendToICS(ics_prefix);
12774         SendToICS("backward 999999\n");
12775     } else {
12776         BackwardInner(backwardMostMove);
12777     }
12778
12779     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12780         /* we have fed all the moves, so reactivate analysis mode */
12781         SendToProgram("analyze\n", &first);
12782         first.analyzing = TRUE;
12783         /*first.maybeThinking = TRUE;*/
12784         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12785     }
12786 }
12787
12788 void
12789 ToNrEvent(int to)
12790 {
12791   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12792   if (to >= forwardMostMove) to = forwardMostMove;
12793   if (to <= backwardMostMove) to = backwardMostMove;
12794   if (to < currentMove) {
12795     BackwardInner(to);
12796   } else {
12797     ForwardInner(to);
12798   }
12799 }
12800
12801 void
12802 RevertEvent(Boolean annotate)
12803 {
12804     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12805         return;
12806     }
12807     if (gameMode != IcsExamining) {
12808         DisplayError(_("You are not examining a game"), 0);
12809         return;
12810     }
12811     if (pausing) {
12812         DisplayError(_("You can't revert while pausing"), 0);
12813         return;
12814     }
12815     SendToICS(ics_prefix);
12816     SendToICS("revert\n");
12817 }
12818
12819 void
12820 RetractMoveEvent()
12821 {
12822     switch (gameMode) {
12823       case MachinePlaysWhite:
12824       case MachinePlaysBlack:
12825         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12826             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12827             return;
12828         }
12829         if (forwardMostMove < 2) return;
12830         currentMove = forwardMostMove = forwardMostMove - 2;
12831         whiteTimeRemaining = timeRemaining[0][currentMove];
12832         blackTimeRemaining = timeRemaining[1][currentMove];
12833         DisplayBothClocks();
12834         DisplayMove(currentMove - 1);
12835         ClearHighlights();/*!! could figure this out*/
12836         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12837         SendToProgram("remove\n", &first);
12838         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12839         break;
12840
12841       case BeginningOfGame:
12842       default:
12843         break;
12844
12845       case IcsPlayingWhite:
12846       case IcsPlayingBlack:
12847         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12848             SendToICS(ics_prefix);
12849             SendToICS("takeback 2\n");
12850         } else {
12851             SendToICS(ics_prefix);
12852             SendToICS("takeback 1\n");
12853         }
12854         break;
12855     }
12856 }
12857
12858 void
12859 MoveNowEvent()
12860 {
12861     ChessProgramState *cps;
12862
12863     switch (gameMode) {
12864       case MachinePlaysWhite:
12865         if (!WhiteOnMove(forwardMostMove)) {
12866             DisplayError(_("It is your turn"), 0);
12867             return;
12868         }
12869         cps = &first;
12870         break;
12871       case MachinePlaysBlack:
12872         if (WhiteOnMove(forwardMostMove)) {
12873             DisplayError(_("It is your turn"), 0);
12874             return;
12875         }
12876         cps = &first;
12877         break;
12878       case TwoMachinesPlay:
12879         if (WhiteOnMove(forwardMostMove) ==
12880             (first.twoMachinesColor[0] == 'w')) {
12881             cps = &first;
12882         } else {
12883             cps = &second;
12884         }
12885         break;
12886       case BeginningOfGame:
12887       default:
12888         return;
12889     }
12890     SendToProgram("?\n", cps);
12891 }
12892
12893 void
12894 TruncateGameEvent()
12895 {
12896     EditGameEvent();
12897     if (gameMode != EditGame) return;
12898     TruncateGame();
12899 }
12900
12901 void
12902 TruncateGame()
12903 {
12904     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12905     if (forwardMostMove > currentMove) {
12906         if (gameInfo.resultDetails != NULL) {
12907             free(gameInfo.resultDetails);
12908             gameInfo.resultDetails = NULL;
12909             gameInfo.result = GameUnfinished;
12910         }
12911         forwardMostMove = currentMove;
12912         HistorySet(parseList, backwardMostMove, forwardMostMove,
12913                    currentMove-1);
12914     }
12915 }
12916
12917 void
12918 HintEvent()
12919 {
12920     if (appData.noChessProgram) return;
12921     switch (gameMode) {
12922       case MachinePlaysWhite:
12923         if (WhiteOnMove(forwardMostMove)) {
12924             DisplayError(_("Wait until your turn"), 0);
12925             return;
12926         }
12927         break;
12928       case BeginningOfGame:
12929       case MachinePlaysBlack:
12930         if (!WhiteOnMove(forwardMostMove)) {
12931             DisplayError(_("Wait until your turn"), 0);
12932             return;
12933         }
12934         break;
12935       default:
12936         DisplayError(_("No hint available"), 0);
12937         return;
12938     }
12939     SendToProgram("hint\n", &first);
12940     hintRequested = TRUE;
12941 }
12942
12943 void
12944 BookEvent()
12945 {
12946     if (appData.noChessProgram) return;
12947     switch (gameMode) {
12948       case MachinePlaysWhite:
12949         if (WhiteOnMove(forwardMostMove)) {
12950             DisplayError(_("Wait until your turn"), 0);
12951             return;
12952         }
12953         break;
12954       case BeginningOfGame:
12955       case MachinePlaysBlack:
12956         if (!WhiteOnMove(forwardMostMove)) {
12957             DisplayError(_("Wait until your turn"), 0);
12958             return;
12959         }
12960         break;
12961       case EditPosition:
12962         EditPositionDone(TRUE);
12963         break;
12964       case TwoMachinesPlay:
12965         return;
12966       default:
12967         break;
12968     }
12969     SendToProgram("bk\n", &first);
12970     bookOutput[0] = NULLCHAR;
12971     bookRequested = TRUE;
12972 }
12973
12974 void
12975 AboutGameEvent()
12976 {
12977     char *tags = PGNTags(&gameInfo);
12978     TagsPopUp(tags, CmailMsg());
12979     free(tags);
12980 }
12981
12982 /* end button procedures */
12983
12984 void
12985 PrintPosition(fp, move)
12986      FILE *fp;
12987      int move;
12988 {
12989     int i, j;
12990     
12991     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12992         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12993             char c = PieceToChar(boards[move][i][j]);
12994             fputc(c == 'x' ? '.' : c, fp);
12995             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12996         }
12997     }
12998     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12999       fprintf(fp, "white to play\n");
13000     else
13001       fprintf(fp, "black to play\n");
13002 }
13003
13004 void
13005 PrintOpponents(fp)
13006      FILE *fp;
13007 {
13008     if (gameInfo.white != NULL) {
13009         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13010     } else {
13011         fprintf(fp, "\n");
13012     }
13013 }
13014
13015 /* Find last component of program's own name, using some heuristics */
13016 void
13017 TidyProgramName(prog, host, buf)
13018      char *prog, *host, buf[MSG_SIZ];
13019 {
13020     char *p, *q;
13021     int local = (strcmp(host, "localhost") == 0);
13022     while (!local && (p = strchr(prog, ';')) != NULL) {
13023         p++;
13024         while (*p == ' ') p++;
13025         prog = p;
13026     }
13027     if (*prog == '"' || *prog == '\'') {
13028         q = strchr(prog + 1, *prog);
13029     } else {
13030         q = strchr(prog, ' ');
13031     }
13032     if (q == NULL) q = prog + strlen(prog);
13033     p = q;
13034     while (p >= prog && *p != '/' && *p != '\\') p--;
13035     p++;
13036     if(p == prog && *p == '"') p++;
13037     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13038     memcpy(buf, p, q - p);
13039     buf[q - p] = NULLCHAR;
13040     if (!local) {
13041         strcat(buf, "@");
13042         strcat(buf, host);
13043     }
13044 }
13045
13046 char *
13047 TimeControlTagValue()
13048 {
13049     char buf[MSG_SIZ];
13050     if (!appData.clockMode) {
13051         strcpy(buf, "-");
13052     } else if (movesPerSession > 0) {
13053         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13054     } else if (timeIncrement == 0) {
13055         sprintf(buf, "%ld", timeControl/1000);
13056     } else {
13057         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13058     }
13059     return StrSave(buf);
13060 }
13061
13062 void
13063 SetGameInfo()
13064 {
13065     /* This routine is used only for certain modes */
13066     VariantClass v = gameInfo.variant;
13067     ChessMove r = GameUnfinished;
13068     char *p = NULL;
13069
13070     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13071         r = gameInfo.result; 
13072         p = gameInfo.resultDetails; 
13073         gameInfo.resultDetails = NULL;
13074     }
13075     ClearGameInfo(&gameInfo);
13076     gameInfo.variant = v;
13077
13078     switch (gameMode) {
13079       case MachinePlaysWhite:
13080         gameInfo.event = StrSave( appData.pgnEventHeader );
13081         gameInfo.site = StrSave(HostName());
13082         gameInfo.date = PGNDate();
13083         gameInfo.round = StrSave("-");
13084         gameInfo.white = StrSave(first.tidy);
13085         gameInfo.black = StrSave(UserName());
13086         gameInfo.timeControl = TimeControlTagValue();
13087         break;
13088
13089       case MachinePlaysBlack:
13090         gameInfo.event = StrSave( appData.pgnEventHeader );
13091         gameInfo.site = StrSave(HostName());
13092         gameInfo.date = PGNDate();
13093         gameInfo.round = StrSave("-");
13094         gameInfo.white = StrSave(UserName());
13095         gameInfo.black = StrSave(first.tidy);
13096         gameInfo.timeControl = TimeControlTagValue();
13097         break;
13098
13099       case TwoMachinesPlay:
13100         gameInfo.event = StrSave( appData.pgnEventHeader );
13101         gameInfo.site = StrSave(HostName());
13102         gameInfo.date = PGNDate();
13103         if (matchGame > 0) {
13104             char buf[MSG_SIZ];
13105             sprintf(buf, "%d", matchGame);
13106             gameInfo.round = StrSave(buf);
13107         } else {
13108             gameInfo.round = StrSave("-");
13109         }
13110         if (first.twoMachinesColor[0] == 'w') {
13111             gameInfo.white = StrSave(first.tidy);
13112             gameInfo.black = StrSave(second.tidy);
13113         } else {
13114             gameInfo.white = StrSave(second.tidy);
13115             gameInfo.black = StrSave(first.tidy);
13116         }
13117         gameInfo.timeControl = TimeControlTagValue();
13118         break;
13119
13120       case EditGame:
13121         gameInfo.event = StrSave("Edited game");
13122         gameInfo.site = StrSave(HostName());
13123         gameInfo.date = PGNDate();
13124         gameInfo.round = StrSave("-");
13125         gameInfo.white = StrSave("-");
13126         gameInfo.black = StrSave("-");
13127         gameInfo.result = r;
13128         gameInfo.resultDetails = p;
13129         break;
13130
13131       case EditPosition:
13132         gameInfo.event = StrSave("Edited position");
13133         gameInfo.site = StrSave(HostName());
13134         gameInfo.date = PGNDate();
13135         gameInfo.round = StrSave("-");
13136         gameInfo.white = StrSave("-");
13137         gameInfo.black = StrSave("-");
13138         break;
13139
13140       case IcsPlayingWhite:
13141       case IcsPlayingBlack:
13142       case IcsObserving:
13143       case IcsExamining:
13144         break;
13145
13146       case PlayFromGameFile:
13147         gameInfo.event = StrSave("Game from non-PGN file");
13148         gameInfo.site = StrSave(HostName());
13149         gameInfo.date = PGNDate();
13150         gameInfo.round = StrSave("-");
13151         gameInfo.white = StrSave("?");
13152         gameInfo.black = StrSave("?");
13153         break;
13154
13155       default:
13156         break;
13157     }
13158 }
13159
13160 void
13161 ReplaceComment(index, text)
13162      int index;
13163      char *text;
13164 {
13165     int len;
13166
13167     while (*text == '\n') text++;
13168     len = strlen(text);
13169     while (len > 0 && text[len - 1] == '\n') len--;
13170
13171     if (commentList[index] != NULL)
13172       free(commentList[index]);
13173
13174     if (len == 0) {
13175         commentList[index] = NULL;
13176         return;
13177     }
13178   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13179       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13180       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13181     commentList[index] = (char *) malloc(len + 2);
13182     strncpy(commentList[index], text, len);
13183     commentList[index][len] = '\n';
13184     commentList[index][len + 1] = NULLCHAR;
13185   } else { 
13186     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13187     char *p;
13188     commentList[index] = (char *) malloc(len + 6);
13189     strcpy(commentList[index], "{\n");
13190     strncpy(commentList[index]+2, text, len);
13191     commentList[index][len+2] = NULLCHAR;
13192     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13193     strcat(commentList[index], "\n}\n");
13194   }
13195 }
13196
13197 void
13198 CrushCRs(text)
13199      char *text;
13200 {
13201   char *p = text;
13202   char *q = text;
13203   char ch;
13204
13205   do {
13206     ch = *p++;
13207     if (ch == '\r') continue;
13208     *q++ = ch;
13209   } while (ch != '\0');
13210 }
13211
13212 void
13213 AppendComment(index, text, addBraces)
13214      int index;
13215      char *text;
13216      Boolean addBraces; // [HGM] braces: tells if we should add {}
13217 {
13218     int oldlen, len;
13219     char *old;
13220
13221 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13222     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13223
13224     CrushCRs(text);
13225     while (*text == '\n') text++;
13226     len = strlen(text);
13227     while (len > 0 && text[len - 1] == '\n') len--;
13228
13229     if (len == 0) return;
13230
13231     if (commentList[index] != NULL) {
13232         old = commentList[index];
13233         oldlen = strlen(old);
13234         while(commentList[index][oldlen-1] ==  '\n')
13235           commentList[index][--oldlen] = NULLCHAR;
13236         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13237         strcpy(commentList[index], old);
13238         free(old);
13239         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13240         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13241           if(addBraces) addBraces = FALSE; else { text++; len--; }
13242           while (*text == '\n') { text++; len--; }
13243           commentList[index][--oldlen] = NULLCHAR;
13244       }
13245         if(addBraces) strcat(commentList[index], "\n{\n");
13246         else          strcat(commentList[index], "\n");
13247         strcat(commentList[index], text);
13248         if(addBraces) strcat(commentList[index], "\n}\n");
13249         else          strcat(commentList[index], "\n");
13250     } else {
13251         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13252         if(addBraces)
13253              strcpy(commentList[index], "{\n");
13254         else commentList[index][0] = NULLCHAR;
13255         strcat(commentList[index], text);
13256         strcat(commentList[index], "\n");
13257         if(addBraces) strcat(commentList[index], "}\n");
13258     }
13259 }
13260
13261 static char * FindStr( char * text, char * sub_text )
13262 {
13263     char * result = strstr( text, sub_text );
13264
13265     if( result != NULL ) {
13266         result += strlen( sub_text );
13267     }
13268
13269     return result;
13270 }
13271
13272 /* [AS] Try to extract PV info from PGN comment */
13273 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13274 char *GetInfoFromComment( int index, char * text )
13275 {
13276     char * sep = text;
13277
13278     if( text != NULL && index > 0 ) {
13279         int score = 0;
13280         int depth = 0;
13281         int time = -1, sec = 0, deci;
13282         char * s_eval = FindStr( text, "[%eval " );
13283         char * s_emt = FindStr( text, "[%emt " );
13284
13285         if( s_eval != NULL || s_emt != NULL ) {
13286             /* New style */
13287             char delim;
13288
13289             if( s_eval != NULL ) {
13290                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13291                     return text;
13292                 }
13293
13294                 if( delim != ']' ) {
13295                     return text;
13296                 }
13297             }
13298
13299             if( s_emt != NULL ) {
13300             }
13301                 return text;
13302         }
13303         else {
13304             /* We expect something like: [+|-]nnn.nn/dd */
13305             int score_lo = 0;
13306
13307             if(*text != '{') return text; // [HGM] braces: must be normal comment
13308
13309             sep = strchr( text, '/' );
13310             if( sep == NULL || sep < (text+4) ) {
13311                 return text;
13312             }
13313
13314             time = -1; sec = -1; deci = -1;
13315             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13316                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13317                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13318                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13319                 return text;
13320             }
13321
13322             if( score_lo < 0 || score_lo >= 100 ) {
13323                 return text;
13324             }
13325
13326             if(sec >= 0) time = 600*time + 10*sec; else
13327             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13328
13329             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13330
13331             /* [HGM] PV time: now locate end of PV info */
13332             while( *++sep >= '0' && *sep <= '9'); // strip depth
13333             if(time >= 0)
13334             while( *++sep >= '0' && *sep <= '9'); // strip time
13335             if(sec >= 0)
13336             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13337             if(deci >= 0)
13338             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13339             while(*sep == ' ') sep++;
13340         }
13341
13342         if( depth <= 0 ) {
13343             return text;
13344         }
13345
13346         if( time < 0 ) {
13347             time = -1;
13348         }
13349
13350         pvInfoList[index-1].depth = depth;
13351         pvInfoList[index-1].score = score;
13352         pvInfoList[index-1].time  = 10*time; // centi-sec
13353         if(*sep == '}') *sep = 0; else *--sep = '{';
13354     }
13355     return sep;
13356 }
13357
13358 void
13359 SendToProgram(message, cps)
13360      char *message;
13361      ChessProgramState *cps;
13362 {
13363     int count, outCount, error;
13364     char buf[MSG_SIZ];
13365
13366     if (cps->pr == NULL) return;
13367     Attention(cps);
13368     
13369     if (appData.debugMode) {
13370         TimeMark now;
13371         GetTimeMark(&now);
13372         fprintf(debugFP, "%ld >%-6s: %s", 
13373                 SubtractTimeMarks(&now, &programStartTime),
13374                 cps->which, message);
13375     }
13376     
13377     count = strlen(message);
13378     outCount = OutputToProcess(cps->pr, message, count, &error);
13379     if (outCount < count && !exiting 
13380                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13381         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13382         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13383             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13384                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13385                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13386             } else {
13387                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13388             }
13389             gameInfo.resultDetails = StrSave(buf);
13390         }
13391         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13392     }
13393 }
13394
13395 void
13396 ReceiveFromProgram(isr, closure, message, count, error)
13397      InputSourceRef isr;
13398      VOIDSTAR closure;
13399      char *message;
13400      int count;
13401      int error;
13402 {
13403     char *end_str;
13404     char buf[MSG_SIZ];
13405     ChessProgramState *cps = (ChessProgramState *)closure;
13406
13407     if (isr != cps->isr) return; /* Killed intentionally */
13408     if (count <= 0) {
13409         if (count == 0) {
13410             sprintf(buf,
13411                     _("Error: %s chess program (%s) exited unexpectedly"),
13412                     cps->which, cps->program);
13413         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13414                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13415                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13416                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13417                 } else {
13418                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13419                 }
13420                 gameInfo.resultDetails = StrSave(buf);
13421             }
13422             RemoveInputSource(cps->isr);
13423             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13424         } else {
13425             sprintf(buf,
13426                     _("Error reading from %s chess program (%s)"),
13427                     cps->which, cps->program);
13428             RemoveInputSource(cps->isr);
13429
13430             /* [AS] Program is misbehaving badly... kill it */
13431             if( count == -2 ) {
13432                 DestroyChildProcess( cps->pr, 9 );
13433                 cps->pr = NoProc;
13434             }
13435
13436             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13437         }
13438         return;
13439     }
13440     
13441     if ((end_str = strchr(message, '\r')) != NULL)
13442       *end_str = NULLCHAR;
13443     if ((end_str = strchr(message, '\n')) != NULL)
13444       *end_str = NULLCHAR;
13445     
13446     if (appData.debugMode) {
13447         TimeMark now; int print = 1;
13448         char *quote = ""; char c; int i;
13449
13450         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13451                 char start = message[0];
13452                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13453                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13454                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13455                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13456                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13457                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13458                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13459                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13460                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13461                     print = (appData.engineComments >= 2);
13462                 }
13463                 message[0] = start; // restore original message
13464         }
13465         if(print) {
13466                 GetTimeMark(&now);
13467                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13468                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13469                         quote,
13470                         message);
13471         }
13472     }
13473
13474     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13475     if (appData.icsEngineAnalyze) {
13476         if (strstr(message, "whisper") != NULL ||
13477              strstr(message, "kibitz") != NULL || 
13478             strstr(message, "tellics") != NULL) return;
13479     }
13480
13481     HandleMachineMove(message, cps);
13482 }
13483
13484
13485 void
13486 SendTimeControl(cps, mps, tc, inc, sd, st)
13487      ChessProgramState *cps;
13488      int mps, inc, sd, st;
13489      long tc;
13490 {
13491     char buf[MSG_SIZ];
13492     int seconds;
13493
13494     if( timeControl_2 > 0 ) {
13495         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13496             tc = timeControl_2;
13497         }
13498     }
13499     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13500     inc /= cps->timeOdds;
13501     st  /= cps->timeOdds;
13502
13503     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13504
13505     if (st > 0) {
13506       /* Set exact time per move, normally using st command */
13507       if (cps->stKludge) {
13508         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13509         seconds = st % 60;
13510         if (seconds == 0) {
13511           sprintf(buf, "level 1 %d\n", st/60);
13512         } else {
13513           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13514         }
13515       } else {
13516         sprintf(buf, "st %d\n", st);
13517       }
13518     } else {
13519       /* Set conventional or incremental time control, using level command */
13520       if (seconds == 0) {
13521         /* Note old gnuchess bug -- minutes:seconds used to not work.
13522            Fixed in later versions, but still avoid :seconds
13523            when seconds is 0. */
13524         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13525       } else {
13526         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13527                 seconds, inc/1000);
13528       }
13529     }
13530     SendToProgram(buf, cps);
13531
13532     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13533     /* Orthogonally, limit search to given depth */
13534     if (sd > 0) {
13535       if (cps->sdKludge) {
13536         sprintf(buf, "depth\n%d\n", sd);
13537       } else {
13538         sprintf(buf, "sd %d\n", sd);
13539       }
13540       SendToProgram(buf, cps);
13541     }
13542
13543     if(cps->nps > 0) { /* [HGM] nps */
13544         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13545         else {
13546                 sprintf(buf, "nps %d\n", cps->nps);
13547               SendToProgram(buf, cps);
13548         }
13549     }
13550 }
13551
13552 ChessProgramState *WhitePlayer()
13553 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13554 {
13555     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13556        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13557         return &second;
13558     return &first;
13559 }
13560
13561 void
13562 SendTimeRemaining(cps, machineWhite)
13563      ChessProgramState *cps;
13564      int /*boolean*/ machineWhite;
13565 {
13566     char message[MSG_SIZ];
13567     long time, otime;
13568
13569     /* Note: this routine must be called when the clocks are stopped
13570        or when they have *just* been set or switched; otherwise
13571        it will be off by the time since the current tick started.
13572     */
13573     if (machineWhite) {
13574         time = whiteTimeRemaining / 10;
13575         otime = blackTimeRemaining / 10;
13576     } else {
13577         time = blackTimeRemaining / 10;
13578         otime = whiteTimeRemaining / 10;
13579     }
13580     /* [HGM] translate opponent's time by time-odds factor */
13581     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13582     if (appData.debugMode) {
13583         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13584     }
13585
13586     if (time <= 0) time = 1;
13587     if (otime <= 0) otime = 1;
13588     
13589     sprintf(message, "time %ld\n", time);
13590     SendToProgram(message, cps);
13591
13592     sprintf(message, "otim %ld\n", otime);
13593     SendToProgram(message, cps);
13594 }
13595
13596 int
13597 BoolFeature(p, name, loc, cps)
13598      char **p;
13599      char *name;
13600      int *loc;
13601      ChessProgramState *cps;
13602 {
13603   char buf[MSG_SIZ];
13604   int len = strlen(name);
13605   int val;
13606   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13607     (*p) += len + 1;
13608     sscanf(*p, "%d", &val);
13609     *loc = (val != 0);
13610     while (**p && **p != ' ') (*p)++;
13611     sprintf(buf, "accepted %s\n", name);
13612     SendToProgram(buf, cps);
13613     return TRUE;
13614   }
13615   return FALSE;
13616 }
13617
13618 int
13619 IntFeature(p, name, loc, cps)
13620      char **p;
13621      char *name;
13622      int *loc;
13623      ChessProgramState *cps;
13624 {
13625   char buf[MSG_SIZ];
13626   int len = strlen(name);
13627   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13628     (*p) += len + 1;
13629     sscanf(*p, "%d", loc);
13630     while (**p && **p != ' ') (*p)++;
13631     sprintf(buf, "accepted %s\n", name);
13632     SendToProgram(buf, cps);
13633     return TRUE;
13634   }
13635   return FALSE;
13636 }
13637
13638 int
13639 StringFeature(p, name, loc, cps)
13640      char **p;
13641      char *name;
13642      char loc[];
13643      ChessProgramState *cps;
13644 {
13645   char buf[MSG_SIZ];
13646   int len = strlen(name);
13647   if (strncmp((*p), name, len) == 0
13648       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13649     (*p) += len + 2;
13650     sscanf(*p, "%[^\"]", loc);
13651     while (**p && **p != '\"') (*p)++;
13652     if (**p == '\"') (*p)++;
13653     sprintf(buf, "accepted %s\n", name);
13654     SendToProgram(buf, cps);
13655     return TRUE;
13656   }
13657   return FALSE;
13658 }
13659
13660 int 
13661 ParseOption(Option *opt, ChessProgramState *cps)
13662 // [HGM] options: process the string that defines an engine option, and determine
13663 // name, type, default value, and allowed value range
13664 {
13665         char *p, *q, buf[MSG_SIZ];
13666         int n, min = (-1)<<31, max = 1<<31, def;
13667
13668         if(p = strstr(opt->name, " -spin ")) {
13669             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13670             if(max < min) max = min; // enforce consistency
13671             if(def < min) def = min;
13672             if(def > max) def = max;
13673             opt->value = def;
13674             opt->min = min;
13675             opt->max = max;
13676             opt->type = Spin;
13677         } else if((p = strstr(opt->name, " -slider "))) {
13678             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13679             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13680             if(max < min) max = min; // enforce consistency
13681             if(def < min) def = min;
13682             if(def > max) def = max;
13683             opt->value = def;
13684             opt->min = min;
13685             opt->max = max;
13686             opt->type = Spin; // Slider;
13687         } else if((p = strstr(opt->name, " -string "))) {
13688             opt->textValue = p+9;
13689             opt->type = TextBox;
13690         } else if((p = strstr(opt->name, " -file "))) {
13691             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13692             opt->textValue = p+7;
13693             opt->type = TextBox; // FileName;
13694         } else if((p = strstr(opt->name, " -path "))) {
13695             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13696             opt->textValue = p+7;
13697             opt->type = TextBox; // PathName;
13698         } else if(p = strstr(opt->name, " -check ")) {
13699             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13700             opt->value = (def != 0);
13701             opt->type = CheckBox;
13702         } else if(p = strstr(opt->name, " -combo ")) {
13703             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13704             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13705             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13706             opt->value = n = 0;
13707             while(q = StrStr(q, " /// ")) {
13708                 n++; *q = 0;    // count choices, and null-terminate each of them
13709                 q += 5;
13710                 if(*q == '*') { // remember default, which is marked with * prefix
13711                     q++;
13712                     opt->value = n;
13713                 }
13714                 cps->comboList[cps->comboCnt++] = q;
13715             }
13716             cps->comboList[cps->comboCnt++] = NULL;
13717             opt->max = n + 1;
13718             opt->type = ComboBox;
13719         } else if(p = strstr(opt->name, " -button")) {
13720             opt->type = Button;
13721         } else if(p = strstr(opt->name, " -save")) {
13722             opt->type = SaveButton;
13723         } else return FALSE;
13724         *p = 0; // terminate option name
13725         // now look if the command-line options define a setting for this engine option.
13726         if(cps->optionSettings && cps->optionSettings[0])
13727             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13728         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13729                 sprintf(buf, "option %s", p);
13730                 if(p = strstr(buf, ",")) *p = 0;
13731                 strcat(buf, "\n");
13732                 SendToProgram(buf, cps);
13733         }
13734         return TRUE;
13735 }
13736
13737 void
13738 FeatureDone(cps, val)
13739      ChessProgramState* cps;
13740      int val;
13741 {
13742   DelayedEventCallback cb = GetDelayedEvent();
13743   if ((cb == InitBackEnd3 && cps == &first) ||
13744       (cb == TwoMachinesEventIfReady && cps == &second)) {
13745     CancelDelayedEvent();
13746     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13747   }
13748   cps->initDone = val;
13749 }
13750
13751 /* Parse feature command from engine */
13752 void
13753 ParseFeatures(args, cps)
13754      char* args;
13755      ChessProgramState *cps;  
13756 {
13757   char *p = args;
13758   char *q;
13759   int val;
13760   char buf[MSG_SIZ];
13761
13762   for (;;) {
13763     while (*p == ' ') p++;
13764     if (*p == NULLCHAR) return;
13765
13766     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13767     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13768     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13769     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13770     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13771     if (BoolFeature(&p, "reuse", &val, cps)) {
13772       /* Engine can disable reuse, but can't enable it if user said no */
13773       if (!val) cps->reuse = FALSE;
13774       continue;
13775     }
13776     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13777     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13778       if (gameMode == TwoMachinesPlay) {
13779         DisplayTwoMachinesTitle();
13780       } else {
13781         DisplayTitle("");
13782       }
13783       continue;
13784     }
13785     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13786     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13787     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13788     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13789     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13790     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13791     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13792     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13793     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13794     if (IntFeature(&p, "done", &val, cps)) {
13795       FeatureDone(cps, val);
13796       continue;
13797     }
13798     /* Added by Tord: */
13799     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13800     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13801     /* End of additions by Tord */
13802
13803     /* [HGM] added features: */
13804     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13805     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13806     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13807     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13808     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13809     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13810     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13811         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13812             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13813             SendToProgram(buf, cps);
13814             continue;
13815         }
13816         if(cps->nrOptions >= MAX_OPTIONS) {
13817             cps->nrOptions--;
13818             sprintf(buf, "%s engine has too many options\n", cps->which);
13819             DisplayError(buf, 0);
13820         }
13821         continue;
13822     }
13823     /* End of additions by HGM */
13824
13825     /* unknown feature: complain and skip */
13826     q = p;
13827     while (*q && *q != '=') q++;
13828     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13829     SendToProgram(buf, cps);
13830     p = q;
13831     if (*p == '=') {
13832       p++;
13833       if (*p == '\"') {
13834         p++;
13835         while (*p && *p != '\"') p++;
13836         if (*p == '\"') p++;
13837       } else {
13838         while (*p && *p != ' ') p++;
13839       }
13840     }
13841   }
13842
13843 }
13844
13845 void
13846 PeriodicUpdatesEvent(newState)
13847      int newState;
13848 {
13849     if (newState == appData.periodicUpdates)
13850       return;
13851
13852     appData.periodicUpdates=newState;
13853
13854     /* Display type changes, so update it now */
13855 //    DisplayAnalysis();
13856
13857     /* Get the ball rolling again... */
13858     if (newState) {
13859         AnalysisPeriodicEvent(1);
13860         StartAnalysisClock();
13861     }
13862 }
13863
13864 void
13865 PonderNextMoveEvent(newState)
13866      int newState;
13867 {
13868     if (newState == appData.ponderNextMove) return;
13869     if (gameMode == EditPosition) EditPositionDone(TRUE);
13870     if (newState) {
13871         SendToProgram("hard\n", &first);
13872         if (gameMode == TwoMachinesPlay) {
13873             SendToProgram("hard\n", &second);
13874         }
13875     } else {
13876         SendToProgram("easy\n", &first);
13877         thinkOutput[0] = NULLCHAR;
13878         if (gameMode == TwoMachinesPlay) {
13879             SendToProgram("easy\n", &second);
13880         }
13881     }
13882     appData.ponderNextMove = newState;
13883 }
13884
13885 void
13886 NewSettingEvent(option, feature, command, value)
13887      char *command;
13888      int option, value, *feature;
13889 {
13890     char buf[MSG_SIZ];
13891
13892     if (gameMode == EditPosition) EditPositionDone(TRUE);
13893     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13894     if(feature == NULL || *feature) SendToProgram(buf, &first);
13895     if (gameMode == TwoMachinesPlay) {
13896         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13897     }
13898 }
13899
13900 void
13901 ShowThinkingEvent()
13902 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13903 {
13904     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13905     int newState = appData.showThinking
13906         // [HGM] thinking: other features now need thinking output as well
13907         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13908     
13909     if (oldState == newState) return;
13910     oldState = newState;
13911     if (gameMode == EditPosition) EditPositionDone(TRUE);
13912     if (oldState) {
13913         SendToProgram("post\n", &first);
13914         if (gameMode == TwoMachinesPlay) {
13915             SendToProgram("post\n", &second);
13916         }
13917     } else {
13918         SendToProgram("nopost\n", &first);
13919         thinkOutput[0] = NULLCHAR;
13920         if (gameMode == TwoMachinesPlay) {
13921             SendToProgram("nopost\n", &second);
13922         }
13923     }
13924 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13925 }
13926
13927 void
13928 AskQuestionEvent(title, question, replyPrefix, which)
13929      char *title; char *question; char *replyPrefix; char *which;
13930 {
13931   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13932   if (pr == NoProc) return;
13933   AskQuestion(title, question, replyPrefix, pr);
13934 }
13935
13936 void
13937 DisplayMove(moveNumber)
13938      int moveNumber;
13939 {
13940     char message[MSG_SIZ];
13941     char res[MSG_SIZ];
13942     char cpThinkOutput[MSG_SIZ];
13943
13944     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13945     
13946     if (moveNumber == forwardMostMove - 1 || 
13947         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13948
13949         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13950
13951         if (strchr(cpThinkOutput, '\n')) {
13952             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13953         }
13954     } else {
13955         *cpThinkOutput = NULLCHAR;
13956     }
13957
13958     /* [AS] Hide thinking from human user */
13959     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13960         *cpThinkOutput = NULLCHAR;
13961         if( thinkOutput[0] != NULLCHAR ) {
13962             int i;
13963
13964             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13965                 cpThinkOutput[i] = '.';
13966             }
13967             cpThinkOutput[i] = NULLCHAR;
13968             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13969         }
13970     }
13971
13972     if (moveNumber == forwardMostMove - 1 &&
13973         gameInfo.resultDetails != NULL) {
13974         if (gameInfo.resultDetails[0] == NULLCHAR) {
13975             sprintf(res, " %s", PGNResult(gameInfo.result));
13976         } else {
13977             sprintf(res, " {%s} %s",
13978                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13979         }
13980     } else {
13981         res[0] = NULLCHAR;
13982     }
13983
13984     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13985         DisplayMessage(res, cpThinkOutput);
13986     } else {
13987         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13988                 WhiteOnMove(moveNumber) ? " " : ".. ",
13989                 parseList[moveNumber], res);
13990         DisplayMessage(message, cpThinkOutput);
13991     }
13992 }
13993
13994 void
13995 DisplayComment(moveNumber, text)
13996      int moveNumber;
13997      char *text;
13998 {
13999     char title[MSG_SIZ];
14000     char buf[8000]; // comment can be long!
14001     int score, depth;
14002     
14003     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14004       strcpy(title, "Comment");
14005     } else {
14006       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14007               WhiteOnMove(moveNumber) ? " " : ".. ",
14008               parseList[moveNumber]);
14009     }
14010     // [HGM] PV info: display PV info together with (or as) comment
14011     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14012       if(text == NULL) text = "";                                           
14013       score = pvInfoList[moveNumber].score;
14014       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14015               depth, (pvInfoList[moveNumber].time+50)/100, text);
14016       text = buf;
14017     }
14018     if (text != NULL && (appData.autoDisplayComment || commentUp))
14019         CommentPopUp(title, text);
14020 }
14021
14022 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14023  * might be busy thinking or pondering.  It can be omitted if your
14024  * gnuchess is configured to stop thinking immediately on any user
14025  * input.  However, that gnuchess feature depends on the FIONREAD
14026  * ioctl, which does not work properly on some flavors of Unix.
14027  */
14028 void
14029 Attention(cps)
14030      ChessProgramState *cps;
14031 {
14032 #if ATTENTION
14033     if (!cps->useSigint) return;
14034     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14035     switch (gameMode) {
14036       case MachinePlaysWhite:
14037       case MachinePlaysBlack:
14038       case TwoMachinesPlay:
14039       case IcsPlayingWhite:
14040       case IcsPlayingBlack:
14041       case AnalyzeMode:
14042       case AnalyzeFile:
14043         /* Skip if we know it isn't thinking */
14044         if (!cps->maybeThinking) return;
14045         if (appData.debugMode)
14046           fprintf(debugFP, "Interrupting %s\n", cps->which);
14047         InterruptChildProcess(cps->pr);
14048         cps->maybeThinking = FALSE;
14049         break;
14050       default:
14051         break;
14052     }
14053 #endif /*ATTENTION*/
14054 }
14055
14056 int
14057 CheckFlags()
14058 {
14059     if (whiteTimeRemaining <= 0) {
14060         if (!whiteFlag) {
14061             whiteFlag = TRUE;
14062             if (appData.icsActive) {
14063                 if (appData.autoCallFlag &&
14064                     gameMode == IcsPlayingBlack && !blackFlag) {
14065                   SendToICS(ics_prefix);
14066                   SendToICS("flag\n");
14067                 }
14068             } else {
14069                 if (blackFlag) {
14070                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14071                 } else {
14072                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14073                     if (appData.autoCallFlag) {
14074                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14075                         return TRUE;
14076                     }
14077                 }
14078             }
14079         }
14080     }
14081     if (blackTimeRemaining <= 0) {
14082         if (!blackFlag) {
14083             blackFlag = TRUE;
14084             if (appData.icsActive) {
14085                 if (appData.autoCallFlag &&
14086                     gameMode == IcsPlayingWhite && !whiteFlag) {
14087                   SendToICS(ics_prefix);
14088                   SendToICS("flag\n");
14089                 }
14090             } else {
14091                 if (whiteFlag) {
14092                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14093                 } else {
14094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14095                     if (appData.autoCallFlag) {
14096                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14097                         return TRUE;
14098                     }
14099                 }
14100             }
14101         }
14102     }
14103     return FALSE;
14104 }
14105
14106 void
14107 CheckTimeControl()
14108 {
14109     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14110         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14111
14112     /*
14113      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14114      */
14115     if ( !WhiteOnMove(forwardMostMove) )
14116         /* White made time control */
14117         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14118         /* [HGM] time odds: correct new time quota for time odds! */
14119                                             / WhitePlayer()->timeOdds;
14120       else
14121         /* Black made time control */
14122         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14123                                             / WhitePlayer()->other->timeOdds;
14124 }
14125
14126 void
14127 DisplayBothClocks()
14128 {
14129     int wom = gameMode == EditPosition ?
14130       !blackPlaysFirst : WhiteOnMove(currentMove);
14131     DisplayWhiteClock(whiteTimeRemaining, wom);
14132     DisplayBlackClock(blackTimeRemaining, !wom);
14133 }
14134
14135
14136 /* Timekeeping seems to be a portability nightmare.  I think everyone
14137    has ftime(), but I'm really not sure, so I'm including some ifdefs
14138    to use other calls if you don't.  Clocks will be less accurate if
14139    you have neither ftime nor gettimeofday.
14140 */
14141
14142 /* VS 2008 requires the #include outside of the function */
14143 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14144 #include <sys/timeb.h>
14145 #endif
14146
14147 /* Get the current time as a TimeMark */
14148 void
14149 GetTimeMark(tm)
14150      TimeMark *tm;
14151 {
14152 #if HAVE_GETTIMEOFDAY
14153
14154     struct timeval timeVal;
14155     struct timezone timeZone;
14156
14157     gettimeofday(&timeVal, &timeZone);
14158     tm->sec = (long) timeVal.tv_sec; 
14159     tm->ms = (int) (timeVal.tv_usec / 1000L);
14160
14161 #else /*!HAVE_GETTIMEOFDAY*/
14162 #if HAVE_FTIME
14163
14164 // include <sys/timeb.h> / moved to just above start of function
14165     struct timeb timeB;
14166
14167     ftime(&timeB);
14168     tm->sec = (long) timeB.time;
14169     tm->ms = (int) timeB.millitm;
14170
14171 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14172     tm->sec = (long) time(NULL);
14173     tm->ms = 0;
14174 #endif
14175 #endif
14176 }
14177
14178 /* Return the difference in milliseconds between two
14179    time marks.  We assume the difference will fit in a long!
14180 */
14181 long
14182 SubtractTimeMarks(tm2, tm1)
14183      TimeMark *tm2, *tm1;
14184 {
14185     return 1000L*(tm2->sec - tm1->sec) +
14186            (long) (tm2->ms - tm1->ms);
14187 }
14188
14189
14190 /*
14191  * Code to manage the game clocks.
14192  *
14193  * In tournament play, black starts the clock and then white makes a move.
14194  * We give the human user a slight advantage if he is playing white---the
14195  * clocks don't run until he makes his first move, so it takes zero time.
14196  * Also, we don't account for network lag, so we could get out of sync
14197  * with GNU Chess's clock -- but then, referees are always right.  
14198  */
14199
14200 static TimeMark tickStartTM;
14201 static long intendedTickLength;
14202
14203 long
14204 NextTickLength(timeRemaining)
14205      long timeRemaining;
14206 {
14207     long nominalTickLength, nextTickLength;
14208
14209     if (timeRemaining > 0L && timeRemaining <= 10000L)
14210       nominalTickLength = 100L;
14211     else
14212       nominalTickLength = 1000L;
14213     nextTickLength = timeRemaining % nominalTickLength;
14214     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14215
14216     return nextTickLength;
14217 }
14218
14219 /* Adjust clock one minute up or down */
14220 void
14221 AdjustClock(Boolean which, int dir)
14222 {
14223     if(which) blackTimeRemaining += 60000*dir;
14224     else      whiteTimeRemaining += 60000*dir;
14225     DisplayBothClocks();
14226 }
14227
14228 /* Stop clocks and reset to a fresh time control */
14229 void
14230 ResetClocks() 
14231 {
14232     (void) StopClockTimer();
14233     if (appData.icsActive) {
14234         whiteTimeRemaining = blackTimeRemaining = 0;
14235     } else if (searchTime) {
14236         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14237         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14238     } else { /* [HGM] correct new time quote for time odds */
14239         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14240         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14241     }
14242     if (whiteFlag || blackFlag) {
14243         DisplayTitle("");
14244         whiteFlag = blackFlag = FALSE;
14245     }
14246     DisplayBothClocks();
14247 }
14248
14249 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14250
14251 /* Decrement running clock by amount of time that has passed */
14252 void
14253 DecrementClocks()
14254 {
14255     long timeRemaining;
14256     long lastTickLength, fudge;
14257     TimeMark now;
14258
14259     if (!appData.clockMode) return;
14260     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14261         
14262     GetTimeMark(&now);
14263
14264     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14265
14266     /* Fudge if we woke up a little too soon */
14267     fudge = intendedTickLength - lastTickLength;
14268     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14269
14270     if (WhiteOnMove(forwardMostMove)) {
14271         if(whiteNPS >= 0) lastTickLength = 0;
14272         timeRemaining = whiteTimeRemaining -= lastTickLength;
14273         DisplayWhiteClock(whiteTimeRemaining - fudge,
14274                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14275     } else {
14276         if(blackNPS >= 0) lastTickLength = 0;
14277         timeRemaining = blackTimeRemaining -= lastTickLength;
14278         DisplayBlackClock(blackTimeRemaining - fudge,
14279                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14280     }
14281
14282     if (CheckFlags()) return;
14283         
14284     tickStartTM = now;
14285     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14286     StartClockTimer(intendedTickLength);
14287
14288     /* if the time remaining has fallen below the alarm threshold, sound the
14289      * alarm. if the alarm has sounded and (due to a takeback or time control
14290      * with increment) the time remaining has increased to a level above the
14291      * threshold, reset the alarm so it can sound again. 
14292      */
14293     
14294     if (appData.icsActive && appData.icsAlarm) {
14295
14296         /* make sure we are dealing with the user's clock */
14297         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14298                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14299            )) return;
14300
14301         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14302             alarmSounded = FALSE;
14303         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14304             PlayAlarmSound();
14305             alarmSounded = TRUE;
14306         }
14307     }
14308 }
14309
14310
14311 /* A player has just moved, so stop the previously running
14312    clock and (if in clock mode) start the other one.
14313    We redisplay both clocks in case we're in ICS mode, because
14314    ICS gives us an update to both clocks after every move.
14315    Note that this routine is called *after* forwardMostMove
14316    is updated, so the last fractional tick must be subtracted
14317    from the color that is *not* on move now.
14318 */
14319 void
14320 SwitchClocks(int newMoveNr)
14321 {
14322     long lastTickLength;
14323     TimeMark now;
14324     int flagged = FALSE;
14325
14326     GetTimeMark(&now);
14327
14328     if (StopClockTimer() && appData.clockMode) {
14329         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14330         if (!WhiteOnMove(forwardMostMove)) {
14331             if(blackNPS >= 0) lastTickLength = 0;
14332             blackTimeRemaining -= lastTickLength;
14333            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14334 //         if(pvInfoList[forwardMostMove-1].time == -1)
14335                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14336                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14337         } else {
14338            if(whiteNPS >= 0) lastTickLength = 0;
14339            whiteTimeRemaining -= lastTickLength;
14340            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14341 //         if(pvInfoList[forwardMostMove-1].time == -1)
14342                  pvInfoList[forwardMostMove-1].time = 
14343                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14344         }
14345         flagged = CheckFlags();
14346     }
14347     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14348     CheckTimeControl();
14349
14350     if (flagged || !appData.clockMode) return;
14351
14352     switch (gameMode) {
14353       case MachinePlaysBlack:
14354       case MachinePlaysWhite:
14355       case BeginningOfGame:
14356         if (pausing) return;
14357         break;
14358
14359       case EditGame:
14360       case PlayFromGameFile:
14361       case IcsExamining:
14362         return;
14363
14364       default:
14365         break;
14366     }
14367
14368     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14369         if(WhiteOnMove(forwardMostMove))
14370              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14371         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14372     }
14373
14374     tickStartTM = now;
14375     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14376       whiteTimeRemaining : blackTimeRemaining);
14377     StartClockTimer(intendedTickLength);
14378 }
14379         
14380
14381 /* Stop both clocks */
14382 void
14383 StopClocks()
14384 {       
14385     long lastTickLength;
14386     TimeMark now;
14387
14388     if (!StopClockTimer()) return;
14389     if (!appData.clockMode) return;
14390
14391     GetTimeMark(&now);
14392
14393     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14394     if (WhiteOnMove(forwardMostMove)) {
14395         if(whiteNPS >= 0) lastTickLength = 0;
14396         whiteTimeRemaining -= lastTickLength;
14397         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14398     } else {
14399         if(blackNPS >= 0) lastTickLength = 0;
14400         blackTimeRemaining -= lastTickLength;
14401         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14402     }
14403     CheckFlags();
14404 }
14405         
14406 /* Start clock of player on move.  Time may have been reset, so
14407    if clock is already running, stop and restart it. */
14408 void
14409 StartClocks()
14410 {
14411     (void) StopClockTimer(); /* in case it was running already */
14412     DisplayBothClocks();
14413     if (CheckFlags()) return;
14414
14415     if (!appData.clockMode) return;
14416     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14417
14418     GetTimeMark(&tickStartTM);
14419     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14420       whiteTimeRemaining : blackTimeRemaining);
14421
14422    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14423     whiteNPS = blackNPS = -1; 
14424     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14425        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14426         whiteNPS = first.nps;
14427     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14428        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14429         blackNPS = first.nps;
14430     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14431         whiteNPS = second.nps;
14432     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14433         blackNPS = second.nps;
14434     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14435
14436     StartClockTimer(intendedTickLength);
14437 }
14438
14439 char *
14440 TimeString(ms)
14441      long ms;
14442 {
14443     long second, minute, hour, day;
14444     char *sign = "";
14445     static char buf[32];
14446     
14447     if (ms > 0 && ms <= 9900) {
14448       /* convert milliseconds to tenths, rounding up */
14449       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14450
14451       sprintf(buf, " %03.1f ", tenths/10.0);
14452       return buf;
14453     }
14454
14455     /* convert milliseconds to seconds, rounding up */
14456     /* use floating point to avoid strangeness of integer division
14457        with negative dividends on many machines */
14458     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14459
14460     if (second < 0) {
14461         sign = "-";
14462         second = -second;
14463     }
14464     
14465     day = second / (60 * 60 * 24);
14466     second = second % (60 * 60 * 24);
14467     hour = second / (60 * 60);
14468     second = second % (60 * 60);
14469     minute = second / 60;
14470     second = second % 60;
14471     
14472     if (day > 0)
14473       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14474               sign, day, hour, minute, second);
14475     else if (hour > 0)
14476       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14477     else
14478       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14479     
14480     return buf;
14481 }
14482
14483
14484 /*
14485  * This is necessary because some C libraries aren't ANSI C compliant yet.
14486  */
14487 char *
14488 StrStr(string, match)
14489      char *string, *match;
14490 {
14491     int i, length;
14492     
14493     length = strlen(match);
14494     
14495     for (i = strlen(string) - length; i >= 0; i--, string++)
14496       if (!strncmp(match, string, length))
14497         return string;
14498     
14499     return NULL;
14500 }
14501
14502 char *
14503 StrCaseStr(string, match)
14504      char *string, *match;
14505 {
14506     int i, j, length;
14507     
14508     length = strlen(match);
14509     
14510     for (i = strlen(string) - length; i >= 0; i--, string++) {
14511         for (j = 0; j < length; j++) {
14512             if (ToLower(match[j]) != ToLower(string[j]))
14513               break;
14514         }
14515         if (j == length) return string;
14516     }
14517
14518     return NULL;
14519 }
14520
14521 #ifndef _amigados
14522 int
14523 StrCaseCmp(s1, s2)
14524      char *s1, *s2;
14525 {
14526     char c1, c2;
14527     
14528     for (;;) {
14529         c1 = ToLower(*s1++);
14530         c2 = ToLower(*s2++);
14531         if (c1 > c2) return 1;
14532         if (c1 < c2) return -1;
14533         if (c1 == NULLCHAR) return 0;
14534     }
14535 }
14536
14537
14538 int
14539 ToLower(c)
14540      int c;
14541 {
14542     return isupper(c) ? tolower(c) : c;
14543 }
14544
14545
14546 int
14547 ToUpper(c)
14548      int c;
14549 {
14550     return islower(c) ? toupper(c) : c;
14551 }
14552 #endif /* !_amigados    */
14553
14554 char *
14555 StrSave(s)
14556      char *s;
14557 {
14558     char *ret;
14559
14560     if ((ret = (char *) malloc(strlen(s) + 1))) {
14561         strcpy(ret, s);
14562     }
14563     return ret;
14564 }
14565
14566 char *
14567 StrSavePtr(s, savePtr)
14568      char *s, **savePtr;
14569 {
14570     if (*savePtr) {
14571         free(*savePtr);
14572     }
14573     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14574         strcpy(*savePtr, s);
14575     }
14576     return(*savePtr);
14577 }
14578
14579 char *
14580 PGNDate()
14581 {
14582     time_t clock;
14583     struct tm *tm;
14584     char buf[MSG_SIZ];
14585
14586     clock = time((time_t *)NULL);
14587     tm = localtime(&clock);
14588     sprintf(buf, "%04d.%02d.%02d",
14589             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14590     return StrSave(buf);
14591 }
14592
14593
14594 char *
14595 PositionToFEN(move, overrideCastling)
14596      int move;
14597      char *overrideCastling;
14598 {
14599     int i, j, fromX, fromY, toX, toY;
14600     int whiteToPlay;
14601     char buf[128];
14602     char *p, *q;
14603     int emptycount;
14604     ChessSquare piece;
14605
14606     whiteToPlay = (gameMode == EditPosition) ?
14607       !blackPlaysFirst : (move % 2 == 0);
14608     p = buf;
14609
14610     /* Piece placement data */
14611     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14612         emptycount = 0;
14613         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14614             if (boards[move][i][j] == EmptySquare) {
14615                 emptycount++;
14616             } else { ChessSquare piece = boards[move][i][j];
14617                 if (emptycount > 0) {
14618                     if(emptycount<10) /* [HGM] can be >= 10 */
14619                         *p++ = '0' + emptycount;
14620                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14621                     emptycount = 0;
14622                 }
14623                 if(PieceToChar(piece) == '+') {
14624                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14625                     *p++ = '+';
14626                     piece = (ChessSquare)(DEMOTED piece);
14627                 } 
14628                 *p++ = PieceToChar(piece);
14629                 if(p[-1] == '~') {
14630                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14631                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14632                     *p++ = '~';
14633                 }
14634             }
14635         }
14636         if (emptycount > 0) {
14637             if(emptycount<10) /* [HGM] can be >= 10 */
14638                 *p++ = '0' + emptycount;
14639             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14640             emptycount = 0;
14641         }
14642         *p++ = '/';
14643     }
14644     *(p - 1) = ' ';
14645
14646     /* [HGM] print Crazyhouse or Shogi holdings */
14647     if( gameInfo.holdingsWidth ) {
14648         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14649         q = p;
14650         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14651             piece = boards[move][i][BOARD_WIDTH-1];
14652             if( piece != EmptySquare )
14653               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14654                   *p++ = PieceToChar(piece);
14655         }
14656         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14657             piece = boards[move][BOARD_HEIGHT-i-1][0];
14658             if( piece != EmptySquare )
14659               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14660                   *p++ = PieceToChar(piece);
14661         }
14662
14663         if( q == p ) *p++ = '-';
14664         *p++ = ']';
14665         *p++ = ' ';
14666     }
14667
14668     /* Active color */
14669     *p++ = whiteToPlay ? 'w' : 'b';
14670     *p++ = ' ';
14671
14672   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14673     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14674   } else {
14675   if(nrCastlingRights) {
14676      q = p;
14677      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14678        /* [HGM] write directly from rights */
14679            if(boards[move][CASTLING][2] != NoRights &&
14680               boards[move][CASTLING][0] != NoRights   )
14681                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14682            if(boards[move][CASTLING][2] != NoRights &&
14683               boards[move][CASTLING][1] != NoRights   )
14684                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14685            if(boards[move][CASTLING][5] != NoRights &&
14686               boards[move][CASTLING][3] != NoRights   )
14687                 *p++ = boards[move][CASTLING][3] + AAA;
14688            if(boards[move][CASTLING][5] != NoRights &&
14689               boards[move][CASTLING][4] != NoRights   )
14690                 *p++ = boards[move][CASTLING][4] + AAA;
14691      } else {
14692
14693         /* [HGM] write true castling rights */
14694         if( nrCastlingRights == 6 ) {
14695             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14696                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14697             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14698                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14699             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14700                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14701             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14702                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14703         }
14704      }
14705      if (q == p) *p++ = '-'; /* No castling rights */
14706      *p++ = ' ';
14707   }
14708
14709   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14710      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14711     /* En passant target square */
14712     if (move > backwardMostMove) {
14713         fromX = moveList[move - 1][0] - AAA;
14714         fromY = moveList[move - 1][1] - ONE;
14715         toX = moveList[move - 1][2] - AAA;
14716         toY = moveList[move - 1][3] - ONE;
14717         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14718             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14719             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14720             fromX == toX) {
14721             /* 2-square pawn move just happened */
14722             *p++ = toX + AAA;
14723             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14724         } else {
14725             *p++ = '-';
14726         }
14727     } else if(move == backwardMostMove) {
14728         // [HGM] perhaps we should always do it like this, and forget the above?
14729         if((signed char)boards[move][EP_STATUS] >= 0) {
14730             *p++ = boards[move][EP_STATUS] + AAA;
14731             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14732         } else {
14733             *p++ = '-';
14734         }
14735     } else {
14736         *p++ = '-';
14737     }
14738     *p++ = ' ';
14739   }
14740   }
14741
14742     /* [HGM] find reversible plies */
14743     {   int i = 0, j=move;
14744
14745         if (appData.debugMode) { int k;
14746             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14747             for(k=backwardMostMove; k<=forwardMostMove; k++)
14748                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14749
14750         }
14751
14752         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14753         if( j == backwardMostMove ) i += initialRulePlies;
14754         sprintf(p, "%d ", i);
14755         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14756     }
14757     /* Fullmove number */
14758     sprintf(p, "%d", (move / 2) + 1);
14759     
14760     return StrSave(buf);
14761 }
14762
14763 Boolean
14764 ParseFEN(board, blackPlaysFirst, fen)
14765     Board board;
14766      int *blackPlaysFirst;
14767      char *fen;
14768 {
14769     int i, j;
14770     char *p;
14771     int emptycount;
14772     ChessSquare piece;
14773
14774     p = fen;
14775
14776     /* [HGM] by default clear Crazyhouse holdings, if present */
14777     if(gameInfo.holdingsWidth) {
14778        for(i=0; i<BOARD_HEIGHT; i++) {
14779            board[i][0]             = EmptySquare; /* black holdings */
14780            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14781            board[i][1]             = (ChessSquare) 0; /* black counts */
14782            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14783        }
14784     }
14785
14786     /* Piece placement data */
14787     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14788         j = 0;
14789         for (;;) {
14790             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14791                 if (*p == '/') p++;
14792                 emptycount = gameInfo.boardWidth - j;
14793                 while (emptycount--)
14794                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14795                 break;
14796 #if(BOARD_FILES >= 10)
14797             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14798                 p++; emptycount=10;
14799                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14800                 while (emptycount--)
14801                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14802 #endif
14803             } else if (isdigit(*p)) {
14804                 emptycount = *p++ - '0';
14805                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14806                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14807                 while (emptycount--)
14808                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14809             } else if (*p == '+' || isalpha(*p)) {
14810                 if (j >= gameInfo.boardWidth) return FALSE;
14811                 if(*p=='+') {
14812                     piece = CharToPiece(*++p);
14813                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14814                     piece = (ChessSquare) (PROMOTED piece ); p++;
14815                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14816                 } else piece = CharToPiece(*p++);
14817
14818                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14819                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14820                     piece = (ChessSquare) (PROMOTED piece);
14821                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14822                     p++;
14823                 }
14824                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14825             } else {
14826                 return FALSE;
14827             }
14828         }
14829     }
14830     while (*p == '/' || *p == ' ') p++;
14831
14832     /* [HGM] look for Crazyhouse holdings here */
14833     while(*p==' ') p++;
14834     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14835         if(*p == '[') p++;
14836         if(*p == '-' ) *p++; /* empty holdings */ else {
14837             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14838             /* if we would allow FEN reading to set board size, we would   */
14839             /* have to add holdings and shift the board read so far here   */
14840             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14841                 *p++;
14842                 if((int) piece >= (int) BlackPawn ) {
14843                     i = (int)piece - (int)BlackPawn;
14844                     i = PieceToNumber((ChessSquare)i);
14845                     if( i >= gameInfo.holdingsSize ) return FALSE;
14846                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14847                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14848                 } else {
14849                     i = (int)piece - (int)WhitePawn;
14850                     i = PieceToNumber((ChessSquare)i);
14851                     if( i >= gameInfo.holdingsSize ) return FALSE;
14852                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14853                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14854                 }
14855             }
14856         }
14857         if(*p == ']') *p++;
14858     }
14859
14860     while(*p == ' ') p++;
14861
14862     /* Active color */
14863     switch (*p++) {
14864       case 'w':
14865         *blackPlaysFirst = FALSE;
14866         break;
14867       case 'b': 
14868         *blackPlaysFirst = TRUE;
14869         break;
14870       default:
14871         return FALSE;
14872     }
14873
14874     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14875     /* return the extra info in global variiables             */
14876
14877     /* set defaults in case FEN is incomplete */
14878     board[EP_STATUS] = EP_UNKNOWN;
14879     for(i=0; i<nrCastlingRights; i++ ) {
14880         board[CASTLING][i] =
14881             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14882     }   /* assume possible unless obviously impossible */
14883     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14884     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14885     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14886                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14887     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14888     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14889     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14890                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14891     FENrulePlies = 0;
14892
14893     while(*p==' ') p++;
14894     if(nrCastlingRights) {
14895       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14896           /* castling indicator present, so default becomes no castlings */
14897           for(i=0; i<nrCastlingRights; i++ ) {
14898                  board[CASTLING][i] = NoRights;
14899           }
14900       }
14901       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14902              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14903              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14904              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14905         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14906
14907         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14908             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14909             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14910         }
14911         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14912             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14913         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14914                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14915         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14916                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14917         switch(c) {
14918           case'K':
14919               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14920               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14921               board[CASTLING][2] = whiteKingFile;
14922               break;
14923           case'Q':
14924               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14925               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14926               board[CASTLING][2] = whiteKingFile;
14927               break;
14928           case'k':
14929               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14930               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14931               board[CASTLING][5] = blackKingFile;
14932               break;
14933           case'q':
14934               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14935               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14936               board[CASTLING][5] = blackKingFile;
14937           case '-':
14938               break;
14939           default: /* FRC castlings */
14940               if(c >= 'a') { /* black rights */
14941                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14942                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14943                   if(i == BOARD_RGHT) break;
14944                   board[CASTLING][5] = i;
14945                   c -= AAA;
14946                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14947                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14948                   if(c > i)
14949                       board[CASTLING][3] = c;
14950                   else
14951                       board[CASTLING][4] = c;
14952               } else { /* white rights */
14953                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14954                     if(board[0][i] == WhiteKing) break;
14955                   if(i == BOARD_RGHT) break;
14956                   board[CASTLING][2] = i;
14957                   c -= AAA - 'a' + 'A';
14958                   if(board[0][c] >= WhiteKing) break;
14959                   if(c > i)
14960                       board[CASTLING][0] = c;
14961                   else
14962                       board[CASTLING][1] = c;
14963               }
14964         }
14965       }
14966       for(i=0; i<nrCastlingRights; i++)
14967         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14968     if (appData.debugMode) {
14969         fprintf(debugFP, "FEN castling rights:");
14970         for(i=0; i<nrCastlingRights; i++)
14971         fprintf(debugFP, " %d", board[CASTLING][i]);
14972         fprintf(debugFP, "\n");
14973     }
14974
14975       while(*p==' ') p++;
14976     }
14977
14978     /* read e.p. field in games that know e.p. capture */
14979     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14980        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14981       if(*p=='-') {
14982         p++; board[EP_STATUS] = EP_NONE;
14983       } else {
14984          char c = *p++ - AAA;
14985
14986          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14987          if(*p >= '0' && *p <='9') *p++;
14988          board[EP_STATUS] = c;
14989       }
14990     }
14991
14992
14993     if(sscanf(p, "%d", &i) == 1) {
14994         FENrulePlies = i; /* 50-move ply counter */
14995         /* (The move number is still ignored)    */
14996     }
14997
14998     return TRUE;
14999 }
15000       
15001 void
15002 EditPositionPasteFEN(char *fen)
15003 {
15004   if (fen != NULL) {
15005     Board initial_position;
15006
15007     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15008       DisplayError(_("Bad FEN position in clipboard"), 0);
15009       return ;
15010     } else {
15011       int savedBlackPlaysFirst = blackPlaysFirst;
15012       EditPositionEvent();
15013       blackPlaysFirst = savedBlackPlaysFirst;
15014       CopyBoard(boards[0], initial_position);
15015       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15016       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15017       DisplayBothClocks();
15018       DrawPosition(FALSE, boards[currentMove]);
15019     }
15020   }
15021 }
15022
15023 static char cseq[12] = "\\   ";
15024
15025 Boolean set_cont_sequence(char *new_seq)
15026 {
15027     int len;
15028     Boolean ret;
15029
15030     // handle bad attempts to set the sequence
15031         if (!new_seq)
15032                 return 0; // acceptable error - no debug
15033
15034     len = strlen(new_seq);
15035     ret = (len > 0) && (len < sizeof(cseq));
15036     if (ret)
15037         strcpy(cseq, new_seq);
15038     else if (appData.debugMode)
15039         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15040     return ret;
15041 }
15042
15043 /*
15044     reformat a source message so words don't cross the width boundary.  internal
15045     newlines are not removed.  returns the wrapped size (no null character unless
15046     included in source message).  If dest is NULL, only calculate the size required
15047     for the dest buffer.  lp argument indicats line position upon entry, and it's
15048     passed back upon exit.
15049 */
15050 int wrap(char *dest, char *src, int count, int width, int *lp)
15051 {
15052     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15053
15054     cseq_len = strlen(cseq);
15055     old_line = line = *lp;
15056     ansi = len = clen = 0;
15057
15058     for (i=0; i < count; i++)
15059     {
15060         if (src[i] == '\033')
15061             ansi = 1;
15062
15063         // if we hit the width, back up
15064         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15065         {
15066             // store i & len in case the word is too long
15067             old_i = i, old_len = len;
15068
15069             // find the end of the last word
15070             while (i && src[i] != ' ' && src[i] != '\n')
15071             {
15072                 i--;
15073                 len--;
15074             }
15075
15076             // word too long?  restore i & len before splitting it
15077             if ((old_i-i+clen) >= width)
15078             {
15079                 i = old_i;
15080                 len = old_len;
15081             }
15082
15083             // extra space?
15084             if (i && src[i-1] == ' ')
15085                 len--;
15086
15087             if (src[i] != ' ' && src[i] != '\n')
15088             {
15089                 i--;
15090                 if (len)
15091                     len--;
15092             }
15093
15094             // now append the newline and continuation sequence
15095             if (dest)
15096                 dest[len] = '\n';
15097             len++;
15098             if (dest)
15099                 strncpy(dest+len, cseq, cseq_len);
15100             len += cseq_len;
15101             line = cseq_len;
15102             clen = cseq_len;
15103             continue;
15104         }
15105
15106         if (dest)
15107             dest[len] = src[i];
15108         len++;
15109         if (!ansi)
15110             line++;
15111         if (src[i] == '\n')
15112             line = 0;
15113         if (src[i] == 'm')
15114             ansi = 0;
15115     }
15116     if (dest && appData.debugMode)
15117     {
15118         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15119             count, width, line, len, *lp);
15120         show_bytes(debugFP, src, count);
15121         fprintf(debugFP, "\ndest: ");
15122         show_bytes(debugFP, dest, len);
15123         fprintf(debugFP, "\n");
15124     }
15125     *lp = dest ? line : old_line;
15126
15127     return len;
15128 }
15129
15130 // [HGM] vari: routines for shelving variations
15131
15132 void 
15133 PushTail(int firstMove, int lastMove)
15134 {
15135         int i, j, nrMoves = lastMove - firstMove;
15136
15137         if(appData.icsActive) { // only in local mode
15138                 forwardMostMove = currentMove; // mimic old ICS behavior
15139                 return;
15140         }
15141         if(storedGames >= MAX_VARIATIONS-1) return;
15142
15143         // push current tail of game on stack
15144         savedResult[storedGames] = gameInfo.result;
15145         savedDetails[storedGames] = gameInfo.resultDetails;
15146         gameInfo.resultDetails = NULL;
15147         savedFirst[storedGames] = firstMove;
15148         savedLast [storedGames] = lastMove;
15149         savedFramePtr[storedGames] = framePtr;
15150         framePtr -= nrMoves; // reserve space for the boards
15151         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15152             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15153             for(j=0; j<MOVE_LEN; j++)
15154                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15155             for(j=0; j<2*MOVE_LEN; j++)
15156                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15157             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15158             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15159             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15160             pvInfoList[firstMove+i-1].depth = 0;
15161             commentList[framePtr+i] = commentList[firstMove+i];
15162             commentList[firstMove+i] = NULL;
15163         }
15164
15165         storedGames++;
15166         forwardMostMove = firstMove; // truncate game so we can start variation
15167         if(storedGames == 1) GreyRevert(FALSE);
15168 }
15169
15170 Boolean
15171 PopTail(Boolean annotate)
15172 {
15173         int i, j, nrMoves;
15174         char buf[8000], moveBuf[20];
15175
15176         if(appData.icsActive) return FALSE; // only in local mode
15177         if(!storedGames) return FALSE; // sanity
15178         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15179
15180         storedGames--;
15181         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15182         nrMoves = savedLast[storedGames] - currentMove;
15183         if(annotate) {
15184                 int cnt = 10;
15185                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15186                 else strcpy(buf, "(");
15187                 for(i=currentMove; i<forwardMostMove; i++) {
15188                         if(WhiteOnMove(i))
15189                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15190                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15191                         strcat(buf, moveBuf);
15192                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15193                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15194                 }
15195                 strcat(buf, ")");
15196         }
15197         for(i=1; i<=nrMoves; i++) { // copy last variation back
15198             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15199             for(j=0; j<MOVE_LEN; j++)
15200                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15201             for(j=0; j<2*MOVE_LEN; j++)
15202                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15203             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15204             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15205             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15206             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15207             commentList[currentMove+i] = commentList[framePtr+i];
15208             commentList[framePtr+i] = NULL;
15209         }
15210         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15211         framePtr = savedFramePtr[storedGames];
15212         gameInfo.result = savedResult[storedGames];
15213         if(gameInfo.resultDetails != NULL) {
15214             free(gameInfo.resultDetails);
15215       }
15216         gameInfo.resultDetails = savedDetails[storedGames];
15217         forwardMostMove = currentMove + nrMoves;
15218         if(storedGames == 0) GreyRevert(TRUE);
15219         return TRUE;
15220 }
15221
15222 void 
15223 CleanupTail()
15224 {       // remove all shelved variations
15225         int i;
15226         for(i=0; i<storedGames; i++) {
15227             if(savedDetails[i])
15228                 free(savedDetails[i]);
15229             savedDetails[i] = NULL;
15230         }
15231         for(i=framePtr; i<MAX_MOVES; i++) {
15232                 if(commentList[i]) free(commentList[i]);
15233                 commentList[i] = NULL;
15234         }
15235         framePtr = MAX_MOVES-1;
15236         storedGames = 0;
15237 }
15238
15239 void
15240 LoadVariation(int index, char *text)
15241 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15242         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15243         int level = 0, move;
15244
15245         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15246         // first find outermost bracketing variation
15247         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15248             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15249                 if(*p == '{') wait = '}'; else
15250                 if(*p == '[') wait = ']'; else
15251                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15252                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15253             }
15254             if(*p == wait) wait = NULLCHAR; // closing ]} found
15255             p++;
15256         }
15257         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15258         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15259         end[1] = NULLCHAR; // clip off comment beyond variation
15260         ToNrEvent(currentMove-1);
15261         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15262         // kludge: use ParsePV() to append variation to game
15263         move = currentMove;
15264         ParsePV(start, TRUE);
15265         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15266         ClearPremoveHighlights();
15267         CommentPopDown();
15268         ToNrEvent(currentMove+1);
15269 }
15270