704847e94f677c7c4d9a77d45dd40e4e44e7dde4
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 # define T_(s) gettext(s)
135 #else 
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s) 
141 #   define N_(s) s 
142 #   define T_(s) s
143 # endif
144 #endif 
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
162                       int toX, int toY));
163 void HandleMachineMove P((char *message, ChessProgramState *cps));
164 int AutoPlayOneMove P((void));
165 int LoadGameOneMove P((ChessMove readAhead));
166 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
167 int LoadPositionFromFile P((char *filename, int n, char *title));
168 int SavePositionToFile P((char *filename));
169 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170                                                                                 Board board));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 void ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
206 int RegisterMove P((void));
207 void MakeRegisteredMove P((void));
208 void TruncateGame P((void));
209 int looking_at P((char *, int *, char *));
210 void CopyPlayerNameIntoFileName P((char **, char *));
211 char *SavePart P((char *));
212 int SaveGameOldStyle P((FILE *));
213 int SaveGamePGN P((FILE *));
214 void GetTimeMark P((TimeMark *));
215 long SubtractTimeMarks P((TimeMark *, TimeMark *));
216 int CheckFlags P((void));
217 long NextTickLength P((long));
218 void CheckTimeControl P((void));
219 void show_bytes P((FILE *, char *, int));
220 int string_to_rating P((char *str));
221 void ParseFeatures P((char* args, ChessProgramState *cps));
222 void InitBackEnd3 P((void));
223 void FeatureDone P((ChessProgramState* cps, int val));
224 void InitChessProgram P((ChessProgramState *cps, int setup));
225 void OutputKibitz(int window, char *text);
226 int PerpetualChase(int first, int last);
227 int EngineOutputIsUp();
228 void InitDrawingSizes(int x, int y);
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271
272 /* States for ics_getting_history */
273 #define H_FALSE 0
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
279
280 /* whosays values for GameEnds */
281 #define GE_ICS 0
282 #define GE_ENGINE 1
283 #define GE_PLAYER 2
284 #define GE_FILE 3
285 #define GE_XBOARD 4
286 #define GE_ENGINE1 5
287 #define GE_ENGINE2 6
288
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
291
292 /* Different types of move when calling RegisterMove */
293 #define CMAIL_MOVE   0
294 #define CMAIL_RESIGN 1
295 #define CMAIL_DRAW   2
296 #define CMAIL_ACCEPT 3
297
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
302
303 /* Telnet protocol constants */
304 #define TN_WILL 0373
305 #define TN_WONT 0374
306 #define TN_DO   0375
307 #define TN_DONT 0376
308 #define TN_IAC  0377
309 #define TN_ECHO 0001
310 #define TN_SGA  0003
311 #define TN_PORT 23
312
313 /* [AS] */
314 static char * safeStrCpy( char * dst, const char * src, size_t count )
315 {
316     assert( dst != NULL );
317     assert( src != NULL );
318     assert( count > 0 );
319
320     strncpy( dst, src, count );
321     dst[ count-1 ] = '\0';
322     return dst;
323 }
324
325 /* Some compiler can't cast u64 to double
326  * This function do the job for us:
327
328  * We use the highest bit for cast, this only
329  * works if the highest bit is not
330  * in use (This should not happen)
331  *
332  * We used this for all compiler
333  */
334 double
335 u64ToDouble(u64 value)
336 {
337   double r;
338   u64 tmp = value & u64Const(0x7fffffffffffffff);
339   r = (double)(s64)tmp;
340   if (value & u64Const(0x8000000000000000))
341        r +=  9.2233720368547758080e18; /* 2^63 */
342  return r;
343 }
344
345 /* Fake up flags for now, as we aren't keeping track of castling
346    availability yet. [HGM] Change of logic: the flag now only
347    indicates the type of castlings allowed by the rule of the game.
348    The actual rights themselves are maintained in the array
349    castlingRights, as part of the game history, and are not probed
350    by this function.
351  */
352 int
353 PosFlags(index)
354 {
355   int flags = F_ALL_CASTLE_OK;
356   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
357   switch (gameInfo.variant) {
358   case VariantSuicide:
359     flags &= ~F_ALL_CASTLE_OK;
360   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
361     flags |= F_IGNORE_CHECK;
362   case VariantLosers:
363     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
364     break;
365   case VariantAtomic:
366     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
367     break;
368   case VariantKriegspiel:
369     flags |= F_KRIEGSPIEL_CAPTURE;
370     break;
371   case VariantCapaRandom: 
372   case VariantFischeRandom:
373     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
374   case VariantNoCastle:
375   case VariantShatranj:
376   case VariantCourier:
377   case VariantMakruk:
378     flags &= ~F_ALL_CASTLE_OK;
379     break;
380   default:
381     break;
382   }
383   return flags;
384 }
385
386 FILE *gameFileFP, *debugFP;
387
388 /* 
389     [AS] Note: sometimes, the sscanf() function is used to parse the input
390     into a fixed-size buffer. Because of this, we must be prepared to
391     receive strings as long as the size of the input buffer, which is currently
392     set to 4K for Windows and 8K for the rest.
393     So, we must either allocate sufficiently large buffers here, or
394     reduce the size of the input buffer in the input reading part.
395 */
396
397 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
398 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
399 char thinkOutput1[MSG_SIZ*10];
400
401 ChessProgramState first, second;
402
403 /* premove variables */
404 int premoveToX = 0;
405 int premoveToY = 0;
406 int premoveFromX = 0;
407 int premoveFromY = 0;
408 int premovePromoChar = 0;
409 int gotPremove = 0;
410 Boolean alarmSounded;
411 /* end premove variables */
412
413 char *ics_prefix = "$";
414 int ics_type = ICS_GENERIC;
415
416 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
417 int pauseExamForwardMostMove = 0;
418 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
419 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
420 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
421 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
422 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
423 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
424 int whiteFlag = FALSE, blackFlag = FALSE;
425 int userOfferedDraw = FALSE;
426 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
427 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
428 int cmailMoveType[CMAIL_MAX_GAMES];
429 long ics_clock_paused = 0;
430 ProcRef icsPR = NoProc, cmailPR = NoProc;
431 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
432 GameMode gameMode = BeginningOfGame;
433 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
434 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
435 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
436 int hiddenThinkOutputState = 0; /* [AS] */
437 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
438 int adjudicateLossPlies = 6;
439 char white_holding[64], black_holding[64];
440 TimeMark lastNodeCountTime;
441 long lastNodeCount=0;
442 int have_sent_ICS_logon = 0;
443 int movesPerSession;
444 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
445 long timeControl_2; /* [AS] Allow separate time controls */
446 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
447 long timeRemaining[2][MAX_MOVES];
448 int matchGame = 0;
449 TimeMark programStartTime;
450 char ics_handle[MSG_SIZ];
451 int have_set_title = 0;
452
453 /* animateTraining preserves the state of appData.animate
454  * when Training mode is activated. This allows the
455  * response to be animated when appData.animate == TRUE and
456  * appData.animateDragging == TRUE.
457  */
458 Boolean animateTraining;
459
460 GameInfo gameInfo;
461
462 AppData appData;
463
464 Board boards[MAX_MOVES];
465 /* [HGM] Following 7 needed for accurate legality tests: */
466 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
467 signed char  initialRights[BOARD_FILES];
468 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
469 int   initialRulePlies, FENrulePlies;
470 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 int loadFlag = 0; 
472 int shuffleOpenings;
473 int mute; // mute all sounds
474
475 // [HGM] vari: next 12 to save and restore variations
476 #define MAX_VARIATIONS 10
477 int framePtr = MAX_MOVES-1; // points to free stack entry
478 int storedGames = 0;
479 int savedFirst[MAX_VARIATIONS];
480 int savedLast[MAX_VARIATIONS];
481 int savedFramePtr[MAX_VARIATIONS];
482 char *savedDetails[MAX_VARIATIONS];
483 ChessMove savedResult[MAX_VARIATIONS];
484
485 void PushTail P((int firstMove, int lastMove));
486 Boolean PopTail P((Boolean annotate));
487 void CleanupTail P((void));
488
489 ChessSquare  FIDEArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackBishop, BlackKnight, BlackRook }
494 };
495
496 ChessSquare twoKingsArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
498         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
499     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
500         BlackKing, BlackKing, BlackKnight, BlackRook }
501 };
502
503 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
504     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
505         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
506     { BlackRook, BlackMan, BlackBishop, BlackQueen,
507         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
508 };
509
510 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
514         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
515 };
516
517 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
519         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
521         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
525     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
526         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackMan, BlackFerz,
528         BlackKing, BlackMan, BlackKnight, BlackRook }
529 };
530
531
532 #if (BOARD_FILES>=10)
533 ChessSquare ShogiArray[2][BOARD_FILES] = {
534     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
535         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
536     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
537         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
538 };
539
540 ChessSquare XiangqiArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
542         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
544         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare CapablancaArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
549         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
551         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
552 };
553
554 ChessSquare GreatArray[2][BOARD_FILES] = {
555     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
556         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
557     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
558         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
559 };
560
561 ChessSquare JanusArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
563         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
564     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
565         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
566 };
567
568 #ifdef GOTHIC
569 ChessSquare GothicArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
571         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
573         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
574 };
575 #else // !GOTHIC
576 #define GothicArray CapablancaArray
577 #endif // !GOTHIC
578
579 #ifdef FALCON
580 ChessSquare FalconArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
582         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
584         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !FALCON
587 #define FalconArray CapablancaArray
588 #endif // !FALCON
589
590 #else // !(BOARD_FILES>=10)
591 #define XiangqiPosition FIDEArray
592 #define CapablancaArray FIDEArray
593 #define GothicArray FIDEArray
594 #define GreatArray FIDEArray
595 #endif // !(BOARD_FILES>=10)
596
597 #if (BOARD_FILES>=12)
598 ChessSquare CourierArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
600         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
602         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
603 };
604 #else // !(BOARD_FILES>=12)
605 #define CourierArray CapablancaArray
606 #endif // !(BOARD_FILES>=12)
607
608
609 Board initialPosition;
610
611
612 /* Convert str to a rating. Checks for special cases of "----",
613
614    "++++", etc. Also strips ()'s */
615 int
616 string_to_rating(str)
617   char *str;
618 {
619   while(*str && !isdigit(*str)) ++str;
620   if (!*str)
621     return 0;   /* One of the special "no rating" cases */
622   else
623     return atoi(str);
624 }
625
626 void
627 ClearProgramStats()
628 {
629     /* Init programStats */
630     programStats.movelist[0] = 0;
631     programStats.depth = 0;
632     programStats.nr_moves = 0;
633     programStats.moves_left = 0;
634     programStats.nodes = 0;
635     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
636     programStats.score = 0;
637     programStats.got_only_move = 0;
638     programStats.got_fail = 0;
639     programStats.line_is_book = 0;
640 }
641
642 void
643 InitBackEnd1()
644 {
645     int matched, min, sec;
646
647     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
648     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
649
650     GetTimeMark(&programStartTime);
651     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
652
653     ClearProgramStats();
654     programStats.ok_to_send = 1;
655     programStats.seen_stat = 0;
656
657     /*
658      * Initialize game list
659      */
660     ListNew(&gameList);
661
662
663     /*
664      * Internet chess server status
665      */
666     if (appData.icsActive) {
667         appData.matchMode = FALSE;
668         appData.matchGames = 0;
669 #if ZIPPY       
670         appData.noChessProgram = !appData.zippyPlay;
671 #else
672         appData.zippyPlay = FALSE;
673         appData.zippyTalk = FALSE;
674         appData.noChessProgram = TRUE;
675 #endif
676         if (*appData.icsHelper != NULLCHAR) {
677             appData.useTelnet = TRUE;
678             appData.telnetProgram = appData.icsHelper;
679         }
680     } else {
681         appData.zippyTalk = appData.zippyPlay = FALSE;
682     }
683
684     /* [AS] Initialize pv info list [HGM] and game state */
685     {
686         int i, j;
687
688         for( i=0; i<=framePtr; i++ ) {
689             pvInfoList[i].depth = -1;
690             boards[i][EP_STATUS] = EP_NONE;
691             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
692         }
693     }
694
695     /*
696      * Parse timeControl resource
697      */
698     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
699                           appData.movesPerSession)) {
700         char buf[MSG_SIZ];
701         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
702         DisplayFatalError(buf, 0, 2);
703     }
704
705     /*
706      * Parse searchTime resource
707      */
708     if (*appData.searchTime != NULLCHAR) {
709         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
710         if (matched == 1) {
711             searchTime = min * 60;
712         } else if (matched == 2) {
713             searchTime = min * 60 + sec;
714         } else {
715             char buf[MSG_SIZ];
716             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
717             DisplayFatalError(buf, 0, 2);
718         }
719     }
720
721     /* [AS] Adjudication threshold */
722     adjudicateLossThreshold = appData.adjudicateLossThreshold;
723     
724     first.which = _("first");
725     second.which = _("second");
726     first.maybeThinking = second.maybeThinking = FALSE;
727     first.pr = second.pr = NoProc;
728     first.isr = second.isr = NULL;
729     first.sendTime = second.sendTime = 2;
730     first.sendDrawOffers = 1;
731     if (appData.firstPlaysBlack) {
732         first.twoMachinesColor = "black\n";
733         second.twoMachinesColor = "white\n";
734     } else {
735         first.twoMachinesColor = "white\n";
736         second.twoMachinesColor = "black\n";
737     }
738     first.program = appData.firstChessProgram;
739     second.program = appData.secondChessProgram;
740     first.host = appData.firstHost;
741     second.host = appData.secondHost;
742     first.dir = appData.firstDirectory;
743     second.dir = appData.secondDirectory;
744     first.other = &second;
745     second.other = &first;
746     first.initString = appData.initString;
747     second.initString = appData.secondInitString;
748     first.computerString = appData.firstComputerString;
749     second.computerString = appData.secondComputerString;
750     first.useSigint = second.useSigint = TRUE;
751     first.useSigterm = second.useSigterm = TRUE;
752     first.reuse = appData.reuseFirst;
753     second.reuse = appData.reuseSecond;
754     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
755     second.nps = appData.secondNPS;
756     first.useSetboard = second.useSetboard = FALSE;
757     first.useSAN = second.useSAN = FALSE;
758     first.usePing = second.usePing = FALSE;
759     first.lastPing = second.lastPing = 0;
760     first.lastPong = second.lastPong = 0;
761     first.usePlayother = second.usePlayother = FALSE;
762     first.useColors = second.useColors = TRUE;
763     first.useUsermove = second.useUsermove = FALSE;
764     first.sendICS = second.sendICS = FALSE;
765     first.sendName = second.sendName = appData.icsActive;
766     first.sdKludge = second.sdKludge = FALSE;
767     first.stKludge = second.stKludge = FALSE;
768     TidyProgramName(first.program, first.host, first.tidy);
769     TidyProgramName(second.program, second.host, second.tidy);
770     first.matchWins = second.matchWins = 0;
771     strcpy(first.variants, appData.variant);
772     strcpy(second.variants, appData.variant);
773     first.analysisSupport = second.analysisSupport = 2; /* detect */
774     first.analyzing = second.analyzing = FALSE;
775     first.initDone = second.initDone = FALSE;
776
777     /* New features added by Tord: */
778     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
779     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
780     /* End of new features added by Tord. */
781     first.fenOverride  = appData.fenOverride1;
782     second.fenOverride = appData.fenOverride2;
783
784     /* [HGM] time odds: set factor for each machine */
785     first.timeOdds  = appData.firstTimeOdds;
786     second.timeOdds = appData.secondTimeOdds;
787     { float norm = 1;
788         if(appData.timeOddsMode) {
789             norm = first.timeOdds;
790             if(norm > second.timeOdds) norm = second.timeOdds;
791         }
792         first.timeOdds /= norm;
793         second.timeOdds /= norm;
794     }
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     first.accumulateTC = appData.firstAccumulateTC;
798     second.accumulateTC = appData.secondAccumulateTC;
799     first.maxNrOfSessions = second.maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     first.debug = second.debug = FALSE;
803     first.supportsNPS = second.supportsNPS = UNKNOWN;
804
805     /* [HGM] options */
806     first.optionSettings  = appData.firstOptions;
807     second.optionSettings = appData.secondOptions;
808
809     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
810     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
811     first.isUCI = appData.firstIsUCI; /* [AS] */
812     second.isUCI = appData.secondIsUCI; /* [AS] */
813     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
814     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
815
816     if (appData.firstProtocolVersion > PROTOVER ||
817         appData.firstProtocolVersion < 1) {
818       char buf[MSG_SIZ];
819       sprintf(buf, _("protocol version %d not supported"),
820               appData.firstProtocolVersion);
821       DisplayFatalError(buf, 0, 2);
822     } else {
823       first.protocolVersion = appData.firstProtocolVersion;
824     }
825
826     if (appData.secondProtocolVersion > PROTOVER ||
827         appData.secondProtocolVersion < 1) {
828       char buf[MSG_SIZ];
829       sprintf(buf, _("protocol version %d not supported"),
830               appData.secondProtocolVersion);
831       DisplayFatalError(buf, 0, 2);
832     } else {
833       second.protocolVersion = appData.secondProtocolVersion;
834     }
835
836     if (appData.icsActive) {
837         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
838 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
839     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
840         appData.clockMode = FALSE;
841         first.sendTime = second.sendTime = 0;
842     }
843     
844 #if ZIPPY
845     /* Override some settings from environment variables, for backward
846        compatibility.  Unfortunately it's not feasible to have the env
847        vars just set defaults, at least in xboard.  Ugh.
848     */
849     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
850       ZippyInit();
851     }
852 #endif
853     
854     if (appData.noChessProgram) {
855         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
856         sprintf(programVersion, "%s", PACKAGE_STRING);
857     } else {
858       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
859       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
860       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
861     }
862
863     if (!appData.icsActive) {
864       char buf[MSG_SIZ];
865       /* Check for variants that are supported only in ICS mode,
866          or not at all.  Some that are accepted here nevertheless
867          have bugs; see comments below.
868       */
869       VariantClass variant = StringToVariant(appData.variant);
870       switch (variant) {
871       case VariantBughouse:     /* need four players and two boards */
872       case VariantKriegspiel:   /* need to hide pieces and move details */
873       /* case VariantFischeRandom: (Fabien: moved below) */
874         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
875         DisplayFatalError(buf, 0, 2);
876         return;
877
878       case VariantUnknown:
879       case VariantLoadable:
880       case Variant29:
881       case Variant30:
882       case Variant31:
883       case Variant32:
884       case Variant33:
885       case Variant34:
886       case Variant35:
887       case Variant36:
888       default:
889         sprintf(buf, _("Unknown variant name %s"), appData.variant);
890         DisplayFatalError(buf, 0, 2);
891         return;
892
893       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
894       case VariantFairy:      /* [HGM] TestLegality definitely off! */
895       case VariantGothic:     /* [HGM] should work */
896       case VariantCapablanca: /* [HGM] should work */
897       case VariantCourier:    /* [HGM] initial forced moves not implemented */
898       case VariantShogi:      /* [HGM] drops not tested for legality */
899       case VariantKnightmate: /* [HGM] should work */
900       case VariantCylinder:   /* [HGM] untested */
901       case VariantFalcon:     /* [HGM] untested */
902       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
903                                  offboard interposition not understood */
904       case VariantNormal:     /* definitely works! */
905       case VariantWildCastle: /* pieces not automatically shuffled */
906       case VariantNoCastle:   /* pieces not automatically shuffled */
907       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
908       case VariantLosers:     /* should work except for win condition,
909                                  and doesn't know captures are mandatory */
910       case VariantSuicide:    /* should work except for win condition,
911                                  and doesn't know captures are mandatory */
912       case VariantGiveaway:   /* should work except for win condition,
913                                  and doesn't know captures are mandatory */
914       case VariantTwoKings:   /* should work */
915       case VariantAtomic:     /* should work except for win condition */
916       case Variant3Check:     /* should work except for win condition */
917       case VariantShatranj:   /* should work except for all win conditions */
918       case VariantMakruk:     /* should work except for daw countdown */
919       case VariantBerolina:   /* might work if TestLegality is off */
920       case VariantCapaRandom: /* should work */
921       case VariantJanus:      /* should work */
922       case VariantSuper:      /* experimental */
923       case VariantGreat:      /* experimental, requires legality testing to be off */
924         break;
925       }
926     }
927
928     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
929     InitEngineUCI( installDir, &second );
930 }
931
932 int NextIntegerFromString( char ** str, long * value )
933 {
934     int result = -1;
935     char * s = *str;
936
937     while( *s == ' ' || *s == '\t' ) {
938         s++;
939     }
940
941     *value = 0;
942
943     if( *s >= '0' && *s <= '9' ) {
944         while( *s >= '0' && *s <= '9' ) {
945             *value = *value * 10 + (*s - '0');
946             s++;
947         }
948
949         result = 0;
950     }
951
952     *str = s;
953
954     return result;
955 }
956
957 int NextTimeControlFromString( char ** str, long * value )
958 {
959     long temp;
960     int result = NextIntegerFromString( str, &temp );
961
962     if( result == 0 ) {
963         *value = temp * 60; /* Minutes */
964         if( **str == ':' ) {
965             (*str)++;
966             result = NextIntegerFromString( str, &temp );
967             *value += temp; /* Seconds */
968         }
969     }
970
971     return result;
972 }
973
974 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
975 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
976     int result = -1; long temp, temp2;
977
978     if(**str != '+') return -1; // old params remain in force!
979     (*str)++;
980     if( NextTimeControlFromString( str, &temp ) ) return -1;
981
982     if(**str != '/') {
983         /* time only: incremental or sudden-death time control */
984         if(**str == '+') { /* increment follows; read it */
985             (*str)++;
986             if(result = NextIntegerFromString( str, &temp2)) return -1;
987             *inc = temp2 * 1000;
988         } else *inc = 0;
989         *moves = 0; *tc = temp * 1000; 
990         return 0;
991     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
992
993     (*str)++; /* classical time control */
994     result = NextTimeControlFromString( str, &temp2);
995     if(result == 0) {
996         *moves = temp/60;
997         *tc    = temp2 * 1000;
998         *inc   = 0;
999     }
1000     return result;
1001 }
1002
1003 int GetTimeQuota(int movenr)
1004 {   /* [HGM] get time to add from the multi-session time-control string */
1005     int moves=1; /* kludge to force reading of first session */
1006     long time, increment;
1007     char *s = fullTimeControlString;
1008
1009     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1010     do {
1011         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1012         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1013         if(movenr == -1) return time;    /* last move before new session     */
1014         if(!moves) return increment;     /* current session is incremental   */
1015         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1016     } while(movenr >= -1);               /* try again for next session       */
1017
1018     return 0; // no new time quota on this move
1019 }
1020
1021 int
1022 ParseTimeControl(tc, ti, mps)
1023      char *tc;
1024      int ti;
1025      int mps;
1026 {
1027   long tc1;
1028   long tc2;
1029   char buf[MSG_SIZ];
1030   
1031   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032   if(ti > 0) {
1033     if(mps)
1034       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1035     else sprintf(buf, "+%s+%d", tc, ti);
1036   } else {
1037     if(mps)
1038              sprintf(buf, "+%d/%s", mps, tc);
1039     else sprintf(buf, "+%s", tc);
1040   }
1041   fullTimeControlString = StrSave(buf);
1042   
1043   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1044     return FALSE;
1045   }
1046   
1047   if( *tc == '/' ) {
1048     /* Parse second time control */
1049     tc++;
1050     
1051     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1052       return FALSE;
1053     }
1054     
1055     if( tc2 == 0 ) {
1056       return FALSE;
1057     }
1058     
1059     timeControl_2 = tc2 * 1000;
1060   }
1061   else {
1062     timeControl_2 = 0;
1063   }
1064   
1065   if( tc1 == 0 ) {
1066     return FALSE;
1067   }
1068   
1069   timeControl = tc1 * 1000;
1070   
1071   if (ti >= 0) {
1072     timeIncrement = ti * 1000;  /* convert to ms */
1073     movesPerSession = 0;
1074   } else {
1075     timeIncrement = 0;
1076     movesPerSession = mps;
1077   }
1078   return TRUE;
1079 }
1080
1081 void
1082 InitBackEnd2()
1083 {
1084     if (appData.debugMode) {
1085         fprintf(debugFP, "%s\n", programVersion);
1086     }
1087
1088     set_cont_sequence(appData.wrapContSeq);
1089     if (appData.matchGames > 0) {
1090         appData.matchMode = TRUE;
1091     } else if (appData.matchMode) {
1092         appData.matchGames = 1;
1093     }
1094     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1095         appData.matchGames = appData.sameColorGames;
1096     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1097         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1098         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099     }
1100     Reset(TRUE, FALSE);
1101     if (appData.noChessProgram || first.protocolVersion == 1) {
1102       InitBackEnd3();
1103     } else {
1104       /* kludge: allow timeout for initial "feature" commands */
1105       FreezeUI();
1106       DisplayMessage("", _("Starting chess program"));
1107       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1108     }
1109 }
1110
1111 void
1112 InitBackEnd3 P((void))
1113 {
1114     GameMode initialMode;
1115     char buf[MSG_SIZ];
1116     int err;
1117
1118     InitChessProgram(&first, startedFromSetupPosition);
1119
1120     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1121         free(programVersion);
1122         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1123         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1124     }
1125
1126     if (appData.icsActive) {
1127 #ifdef WIN32
1128         /* [DM] Make a console window if needed [HGM] merged ifs */
1129         ConsoleCreate(); 
1130 #endif
1131         err = establish();
1132         if (err != 0) {
1133             if (*appData.icsCommPort != NULLCHAR) {
1134                 sprintf(buf, _("Could not open comm port %s"),  
1135                         appData.icsCommPort);
1136             } else {
1137                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1138                         appData.icsHost, appData.icsPort);
1139             }
1140             DisplayFatalError(buf, err, 1);
1141             return;
1142         }
1143         SetICSMode();
1144         telnetISR =
1145           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1146         fromUserISR =
1147           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1148         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1149             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1150     } else if (appData.noChessProgram) {
1151         SetNCPMode();
1152     } else {
1153         SetGNUMode();
1154     }
1155
1156     if (*appData.cmailGameName != NULLCHAR) {
1157         SetCmailMode();
1158         OpenLoopback(&cmailPR);
1159         cmailISR =
1160           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1161     }
1162     
1163     ThawUI();
1164     DisplayMessage("", "");
1165     if (StrCaseCmp(appData.initialMode, "") == 0) {
1166       initialMode = BeginningOfGame;
1167     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1168       initialMode = TwoMachinesPlay;
1169     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1170       initialMode = AnalyzeFile; 
1171     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1172       initialMode = AnalyzeMode;
1173     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1174       initialMode = MachinePlaysWhite;
1175     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1176       initialMode = MachinePlaysBlack;
1177     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1178       initialMode = EditGame;
1179     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1180       initialMode = EditPosition;
1181     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1182       initialMode = Training;
1183     } else {
1184       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1185       DisplayFatalError(buf, 0, 2);
1186       return;
1187     }
1188
1189     if (appData.matchMode) {
1190         /* Set up machine vs. machine match */
1191         if (appData.noChessProgram) {
1192             DisplayFatalError(_("Can't have a match with no chess programs"),
1193                               0, 2);
1194             return;
1195         }
1196         matchMode = TRUE;
1197         matchGame = 1;
1198         if (*appData.loadGameFile != NULLCHAR) {
1199             int index = appData.loadGameIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadGameFromFile(appData.loadGameFile,
1202                                   index,
1203                                   appData.loadGameFile, FALSE)) {
1204                 DisplayFatalError(_("Bad game file"), 0, 1);
1205                 return;
1206             }
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             int index = appData.loadPositionIndex; // [HGM] autoinc
1209             if(index<0) lastIndex = index = 1;
1210             if (!LoadPositionFromFile(appData.loadPositionFile,
1211                                       index,
1212                                       appData.loadPositionFile)) {
1213                 DisplayFatalError(_("Bad position file"), 0, 1);
1214                 return;
1215             }
1216         }
1217         TwoMachinesEvent();
1218     } else if (*appData.cmailGameName != NULLCHAR) {
1219         /* Set up cmail mode */
1220         ReloadCmailMsgEvent(TRUE);
1221     } else {
1222         /* Set up other modes */
1223         if (initialMode == AnalyzeFile) {
1224           if (*appData.loadGameFile == NULLCHAR) {
1225             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1226             return;
1227           }
1228         }
1229         if (*appData.loadGameFile != NULLCHAR) {
1230             (void) LoadGameFromFile(appData.loadGameFile,
1231                                     appData.loadGameIndex,
1232                                     appData.loadGameFile, TRUE);
1233         } else if (*appData.loadPositionFile != NULLCHAR) {
1234             (void) LoadPositionFromFile(appData.loadPositionFile,
1235                                         appData.loadPositionIndex,
1236                                         appData.loadPositionFile);
1237             /* [HGM] try to make self-starting even after FEN load */
1238             /* to allow automatic setup of fairy variants with wtm */
1239             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1240                 gameMode = BeginningOfGame;
1241                 setboardSpoiledMachineBlack = 1;
1242             }
1243             /* [HGM] loadPos: make that every new game uses the setup */
1244             /* from file as long as we do not switch variant          */
1245             if(!blackPlaysFirst) {
1246                 startedFromPositionFile = TRUE;
1247                 CopyBoard(filePosition, boards[0]);
1248             }
1249         }
1250         if (initialMode == AnalyzeMode) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1253             return;
1254           }
1255           if (appData.icsActive) {
1256             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1257             return;
1258           }
1259           AnalyzeModeEvent();
1260         } else if (initialMode == AnalyzeFile) {
1261           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1262           ShowThinkingEvent();
1263           AnalyzeFileEvent();
1264           AnalysisPeriodicEvent(1);
1265         } else if (initialMode == MachinePlaysWhite) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineWhiteEvent();
1277         } else if (initialMode == MachinePlaysBlack) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           MachineBlackEvent();
1289         } else if (initialMode == TwoMachinesPlay) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           TwoMachinesEvent();
1301         } else if (initialMode == EditGame) {
1302           EditGameEvent();
1303         } else if (initialMode == EditPosition) {
1304           EditPositionEvent();
1305         } else if (initialMode == Training) {
1306           if (*appData.loadGameFile == NULLCHAR) {
1307             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308             return;
1309           }
1310           TrainingEvent();
1311         }
1312     }
1313 }
1314
1315 /*
1316  * Establish will establish a contact to a remote host.port.
1317  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1318  *  used to talk to the host.
1319  * Returns 0 if okay, error code if not.
1320  */
1321 int
1322 establish()
1323 {
1324     char buf[MSG_SIZ];
1325
1326     if (*appData.icsCommPort != NULLCHAR) {
1327         /* Talk to the host through a serial comm port */
1328         return OpenCommPort(appData.icsCommPort, &icsPR);
1329
1330     } else if (*appData.gateway != NULLCHAR) {
1331         if (*appData.remoteShell == NULLCHAR) {
1332             /* Use the rcmd protocol to run telnet program on a gateway host */
1333             snprintf(buf, sizeof(buf), "%s %s %s",
1334                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1335             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1336
1337         } else {
1338             /* Use the rsh program to run telnet program on a gateway host */
1339             if (*appData.remoteUser == NULLCHAR) {
1340                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1341                         appData.gateway, appData.telnetProgram,
1342                         appData.icsHost, appData.icsPort);
1343             } else {
1344                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1345                         appData.remoteShell, appData.gateway, 
1346                         appData.remoteUser, appData.telnetProgram,
1347                         appData.icsHost, appData.icsPort);
1348             }
1349             return StartChildProcess(buf, "", &icsPR);
1350
1351         }
1352     } else if (appData.useTelnet) {
1353         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1354
1355     } else {
1356         /* TCP socket interface differs somewhat between
1357            Unix and NT; handle details in the front end.
1358            */
1359         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1360     }
1361 }
1362
1363 void EscapeExpand(char *p, char *q)
1364 {       // [HGM] initstring: routine to shape up string arguments
1365         while(*p++ = *q++) if(p[-1] == '\\')
1366             switch(*q++) {
1367                 case 'n': p[-1] = '\n'; break;
1368                 case 'r': p[-1] = '\r'; break;
1369                 case 't': p[-1] = '\t'; break;
1370                 case '\\': p[-1] = '\\'; break;
1371                 case 0: *p = 0; return;
1372                 default: p[-1] = q[-1]; break;
1373             }
1374 }
1375
1376 void
1377 show_bytes(fp, buf, count)
1378      FILE *fp;
1379      char *buf;
1380      int count;
1381 {
1382     while (count--) {
1383         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1384             fprintf(fp, "\\%03o", *buf & 0xff);
1385         } else {
1386             putc(*buf, fp);
1387         }
1388         buf++;
1389     }
1390     fflush(fp);
1391 }
1392
1393 /* Returns an errno value */
1394 int
1395 OutputMaybeTelnet(pr, message, count, outError)
1396      ProcRef pr;
1397      char *message;
1398      int count;
1399      int *outError;
1400 {
1401     char buf[8192], *p, *q, *buflim;
1402     int left, newcount, outcount;
1403
1404     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1405         *appData.gateway != NULLCHAR) {
1406         if (appData.debugMode) {
1407             fprintf(debugFP, ">ICS: ");
1408             show_bytes(debugFP, message, count);
1409             fprintf(debugFP, "\n");
1410         }
1411         return OutputToProcess(pr, message, count, outError);
1412     }
1413
1414     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1415     p = message;
1416     q = buf;
1417     left = count;
1418     newcount = 0;
1419     while (left) {
1420         if (q >= buflim) {
1421             if (appData.debugMode) {
1422                 fprintf(debugFP, ">ICS: ");
1423                 show_bytes(debugFP, buf, newcount);
1424                 fprintf(debugFP, "\n");
1425             }
1426             outcount = OutputToProcess(pr, buf, newcount, outError);
1427             if (outcount < newcount) return -1; /* to be sure */
1428             q = buf;
1429             newcount = 0;
1430         }
1431         if (*p == '\n') {
1432             *q++ = '\r';
1433             newcount++;
1434         } else if (((unsigned char) *p) == TN_IAC) {
1435             *q++ = (char) TN_IAC;
1436             newcount ++;
1437         }
1438         *q++ = *p++;
1439         newcount++;
1440         left--;
1441     }
1442     if (appData.debugMode) {
1443         fprintf(debugFP, ">ICS: ");
1444         show_bytes(debugFP, buf, newcount);
1445         fprintf(debugFP, "\n");
1446     }
1447     outcount = OutputToProcess(pr, buf, newcount, outError);
1448     if (outcount < newcount) return -1; /* to be sure */
1449     return count;
1450 }
1451
1452 void
1453 read_from_player(isr, closure, message, count, error)
1454      InputSourceRef isr;
1455      VOIDSTAR closure;
1456      char *message;
1457      int count;
1458      int error;
1459 {
1460     int outError, outCount;
1461     static int gotEof = 0;
1462
1463     /* Pass data read from player on to ICS */
1464     if (count > 0) {
1465         gotEof = 0;
1466         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1467         if (outCount < count) {
1468             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469         }
1470     } else if (count < 0) {
1471         RemoveInputSource(isr);
1472         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1473     } else if (gotEof++ > 0) {
1474         RemoveInputSource(isr);
1475         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1476     }
1477 }
1478
1479 void
1480 KeepAlive()
1481 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1482     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1483     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1484     SendToICS("date\n");
1485     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1486 }
1487
1488 /* added routine for printf style output to ics */
1489 void ics_printf(char *format, ...)
1490 {
1491     char buffer[MSG_SIZ];
1492     va_list args;
1493
1494     va_start(args, format);
1495     vsnprintf(buffer, sizeof(buffer), format, args);
1496     buffer[sizeof(buffer)-1] = '\0';
1497     SendToICS(buffer);
1498     va_end(args);
1499 }
1500
1501 void
1502 SendToICS(s)
1503      char *s;
1504 {
1505     int count, outCount, outError;
1506
1507     if (icsPR == NULL) return;
1508
1509     count = strlen(s);
1510     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1511     if (outCount < count) {
1512         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1513     }
1514 }
1515
1516 /* This is used for sending logon scripts to the ICS. Sending
1517    without a delay causes problems when using timestamp on ICC
1518    (at least on my machine). */
1519 void
1520 SendToICSDelayed(s,msdelay)
1521      char *s;
1522      long msdelay;
1523 {
1524     int count, outCount, outError;
1525
1526     if (icsPR == NULL) return;
1527
1528     count = strlen(s);
1529     if (appData.debugMode) {
1530         fprintf(debugFP, ">ICS: ");
1531         show_bytes(debugFP, s, count);
1532         fprintf(debugFP, "\n");
1533     }
1534     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1535                                       msdelay);
1536     if (outCount < count) {
1537         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538     }
1539 }
1540
1541
1542 /* Remove all highlighting escape sequences in s
1543    Also deletes any suffix starting with '(' 
1544    */
1545 char *
1546 StripHighlightAndTitle(s)
1547      char *s;
1548 {
1549     static char retbuf[MSG_SIZ];
1550     char *p = retbuf;
1551
1552     while (*s != NULLCHAR) {
1553         while (*s == '\033') {
1554             while (*s != NULLCHAR && !isalpha(*s)) s++;
1555             if (*s != NULLCHAR) s++;
1556         }
1557         while (*s != NULLCHAR && *s != '\033') {
1558             if (*s == '(' || *s == '[') {
1559                 *p = NULLCHAR;
1560                 return retbuf;
1561             }
1562             *p++ = *s++;
1563         }
1564     }
1565     *p = NULLCHAR;
1566     return retbuf;
1567 }
1568
1569 /* Remove all highlighting escape sequences in s */
1570 char *
1571 StripHighlight(s)
1572      char *s;
1573 {
1574     static char retbuf[MSG_SIZ];
1575     char *p = retbuf;
1576
1577     while (*s != NULLCHAR) {
1578         while (*s == '\033') {
1579             while (*s != NULLCHAR && !isalpha(*s)) s++;
1580             if (*s != NULLCHAR) s++;
1581         }
1582         while (*s != NULLCHAR && *s != '\033') {
1583             *p++ = *s++;
1584         }
1585     }
1586     *p = NULLCHAR;
1587     return retbuf;
1588 }
1589
1590 char *variantNames[] = VARIANT_NAMES;
1591 char *
1592 VariantName(v)
1593      VariantClass v;
1594 {
1595     return variantNames[v];
1596 }
1597
1598
1599 /* Identify a variant from the strings the chess servers use or the
1600    PGN Variant tag names we use. */
1601 VariantClass
1602 StringToVariant(e)
1603      char *e;
1604 {
1605     char *p;
1606     int wnum = -1;
1607     VariantClass v = VariantNormal;
1608     int i, found = FALSE;
1609     char buf[MSG_SIZ];
1610
1611     if (!e) return v;
1612
1613     /* [HGM] skip over optional board-size prefixes */
1614     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1615         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1616         while( *e++ != '_');
1617     }
1618
1619     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1620         v = VariantNormal;
1621         found = TRUE;
1622     } else
1623     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1624       if (StrCaseStr(e, variantNames[i])) {
1625         v = (VariantClass) i;
1626         found = TRUE;
1627         break;
1628       }
1629     }
1630
1631     if (!found) {
1632       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1633           || StrCaseStr(e, "wild/fr") 
1634           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1635         v = VariantFischeRandom;
1636       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1637                  (i = 1, p = StrCaseStr(e, "w"))) {
1638         p += i;
1639         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1640         if (isdigit(*p)) {
1641           wnum = atoi(p);
1642         } else {
1643           wnum = -1;
1644         }
1645         switch (wnum) {
1646         case 0: /* FICS only, actually */
1647         case 1:
1648           /* Castling legal even if K starts on d-file */
1649           v = VariantWildCastle;
1650           break;
1651         case 2:
1652         case 3:
1653         case 4:
1654           /* Castling illegal even if K & R happen to start in
1655              normal positions. */
1656           v = VariantNoCastle;
1657           break;
1658         case 5:
1659         case 7:
1660         case 8:
1661         case 10:
1662         case 11:
1663         case 12:
1664         case 13:
1665         case 14:
1666         case 15:
1667         case 18:
1668         case 19:
1669           /* Castling legal iff K & R start in normal positions */
1670           v = VariantNormal;
1671           break;
1672         case 6:
1673         case 20:
1674         case 21:
1675           /* Special wilds for position setup; unclear what to do here */
1676           v = VariantLoadable;
1677           break;
1678         case 9:
1679           /* Bizarre ICC game */
1680           v = VariantTwoKings;
1681           break;
1682         case 16:
1683           v = VariantKriegspiel;
1684           break;
1685         case 17:
1686           v = VariantLosers;
1687           break;
1688         case 22:
1689           v = VariantFischeRandom;
1690           break;
1691         case 23:
1692           v = VariantCrazyhouse;
1693           break;
1694         case 24:
1695           v = VariantBughouse;
1696           break;
1697         case 25:
1698           v = Variant3Check;
1699           break;
1700         case 26:
1701           /* Not quite the same as FICS suicide! */
1702           v = VariantGiveaway;
1703           break;
1704         case 27:
1705           v = VariantAtomic;
1706           break;
1707         case 28:
1708           v = VariantShatranj;
1709           break;
1710
1711         /* Temporary names for future ICC types.  The name *will* change in 
1712            the next xboard/WinBoard release after ICC defines it. */
1713         case 29:
1714           v = Variant29;
1715           break;
1716         case 30:
1717           v = Variant30;
1718           break;
1719         case 31:
1720           v = Variant31;
1721           break;
1722         case 32:
1723           v = Variant32;
1724           break;
1725         case 33:
1726           v = Variant33;
1727           break;
1728         case 34:
1729           v = Variant34;
1730           break;
1731         case 35:
1732           v = Variant35;
1733           break;
1734         case 36:
1735           v = Variant36;
1736           break;
1737         case 37:
1738           v = VariantShogi;
1739           break;
1740         case 38:
1741           v = VariantXiangqi;
1742           break;
1743         case 39:
1744           v = VariantCourier;
1745           break;
1746         case 40:
1747           v = VariantGothic;
1748           break;
1749         case 41:
1750           v = VariantCapablanca;
1751           break;
1752         case 42:
1753           v = VariantKnightmate;
1754           break;
1755         case 43:
1756           v = VariantFairy;
1757           break;
1758         case 44:
1759           v = VariantCylinder;
1760           break;
1761         case 45:
1762           v = VariantFalcon;
1763           break;
1764         case 46:
1765           v = VariantCapaRandom;
1766           break;
1767         case 47:
1768           v = VariantBerolina;
1769           break;
1770         case 48:
1771           v = VariantJanus;
1772           break;
1773         case 49:
1774           v = VariantSuper;
1775           break;
1776         case 50:
1777           v = VariantGreat;
1778           break;
1779         case -1:
1780           /* Found "wild" or "w" in the string but no number;
1781              must assume it's normal chess. */
1782           v = VariantNormal;
1783           break;
1784         default:
1785           sprintf(buf, _("Unknown wild type %d"), wnum);
1786           DisplayError(buf, 0);
1787           v = VariantUnknown;
1788           break;
1789         }
1790       }
1791     }
1792     if (appData.debugMode) {
1793       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1794               e, wnum, VariantName(v));
1795     }
1796     return v;
1797 }
1798
1799 static int leftover_start = 0, leftover_len = 0;
1800 char star_match[STAR_MATCH_N][MSG_SIZ];
1801
1802 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1803    advance *index beyond it, and set leftover_start to the new value of
1804    *index; else return FALSE.  If pattern contains the character '*', it
1805    matches any sequence of characters not containing '\r', '\n', or the
1806    character following the '*' (if any), and the matched sequence(s) are
1807    copied into star_match.
1808    */
1809 int
1810 looking_at(buf, index, pattern)
1811      char *buf;
1812      int *index;
1813      char *pattern;
1814 {
1815     char *bufp = &buf[*index], *patternp = pattern;
1816     int star_count = 0;
1817     char *matchp = star_match[0];
1818     
1819     for (;;) {
1820         if (*patternp == NULLCHAR) {
1821             *index = leftover_start = bufp - buf;
1822             *matchp = NULLCHAR;
1823             return TRUE;
1824         }
1825         if (*bufp == NULLCHAR) return FALSE;
1826         if (*patternp == '*') {
1827             if (*bufp == *(patternp + 1)) {
1828                 *matchp = NULLCHAR;
1829                 matchp = star_match[++star_count];
1830                 patternp += 2;
1831                 bufp++;
1832                 continue;
1833             } else if (*bufp == '\n' || *bufp == '\r') {
1834                 patternp++;
1835                 if (*patternp == NULLCHAR)
1836                   continue;
1837                 else
1838                   return FALSE;
1839             } else {
1840                 *matchp++ = *bufp++;
1841                 continue;
1842             }
1843         }
1844         if (*patternp != *bufp) return FALSE;
1845         patternp++;
1846         bufp++;
1847     }
1848 }
1849
1850 void
1851 SendToPlayer(data, length)
1852      char *data;
1853      int length;
1854 {
1855     int error, outCount;
1856     outCount = OutputToProcess(NoProc, data, length, &error);
1857     if (outCount < length) {
1858         DisplayFatalError(_("Error writing to display"), error, 1);
1859     }
1860 }
1861
1862 void
1863 PackHolding(packed, holding)
1864      char packed[];
1865      char *holding;
1866 {
1867     char *p = holding;
1868     char *q = packed;
1869     int runlength = 0;
1870     int curr = 9999;
1871     do {
1872         if (*p == curr) {
1873             runlength++;
1874         } else {
1875             switch (runlength) {
1876               case 0:
1877                 break;
1878               case 1:
1879                 *q++ = curr;
1880                 break;
1881               case 2:
1882                 *q++ = curr;
1883                 *q++ = curr;
1884                 break;
1885               default:
1886                 sprintf(q, "%d", runlength);
1887                 while (*q) q++;
1888                 *q++ = curr;
1889                 break;
1890             }
1891             runlength = 1;
1892             curr = *p;
1893         }
1894     } while (*p++);
1895     *q = NULLCHAR;
1896 }
1897
1898 /* Telnet protocol requests from the front end */
1899 void
1900 TelnetRequest(ddww, option)
1901      unsigned char ddww, option;
1902 {
1903     unsigned char msg[3];
1904     int outCount, outError;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1907
1908     if (appData.debugMode) {
1909         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1910         switch (ddww) {
1911           case TN_DO:
1912             ddwwStr = "DO";
1913             break;
1914           case TN_DONT:
1915             ddwwStr = "DONT";
1916             break;
1917           case TN_WILL:
1918             ddwwStr = "WILL";
1919             break;
1920           case TN_WONT:
1921             ddwwStr = "WONT";
1922             break;
1923           default:
1924             ddwwStr = buf1;
1925             sprintf(buf1, "%d", ddww);
1926             break;
1927         }
1928         switch (option) {
1929           case TN_ECHO:
1930             optionStr = "ECHO";
1931             break;
1932           default:
1933             optionStr = buf2;
1934             sprintf(buf2, "%d", option);
1935             break;
1936         }
1937         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1938     }
1939     msg[0] = TN_IAC;
1940     msg[1] = ddww;
1941     msg[2] = option;
1942     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1943     if (outCount < 3) {
1944         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1945     }
1946 }
1947
1948 void
1949 DoEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DO, TN_ECHO);
1953 }
1954
1955 void
1956 DontEcho()
1957 {
1958     if (!appData.icsActive) return;
1959     TelnetRequest(TN_DONT, TN_ECHO);
1960 }
1961
1962 void
1963 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1964 {
1965     /* put the holdings sent to us by the server on the board holdings area */
1966     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1967     char p;
1968     ChessSquare piece;
1969
1970     if(gameInfo.holdingsWidth < 2)  return;
1971     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1972         return; // prevent overwriting by pre-board holdings
1973
1974     if( (int)lowestPiece >= BlackPawn ) {
1975         holdingsColumn = 0;
1976         countsColumn = 1;
1977         holdingsStartRow = BOARD_HEIGHT-1;
1978         direction = -1;
1979     } else {
1980         holdingsColumn = BOARD_WIDTH-1;
1981         countsColumn = BOARD_WIDTH-2;
1982         holdingsStartRow = 0;
1983         direction = 1;
1984     }
1985
1986     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1987         board[i][holdingsColumn] = EmptySquare;
1988         board[i][countsColumn]   = (ChessSquare) 0;
1989     }
1990     while( (p=*holdings++) != NULLCHAR ) {
1991         piece = CharToPiece( ToUpper(p) );
1992         if(piece == EmptySquare) continue;
1993         /*j = (int) piece - (int) WhitePawn;*/
1994         j = PieceToNumber(piece);
1995         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1996         if(j < 0) continue;               /* should not happen */
1997         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1998         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1999         board[holdingsStartRow+j*direction][countsColumn]++;
2000     }
2001 }
2002
2003
2004 void
2005 VariantSwitch(Board board, VariantClass newVariant)
2006 {
2007    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2008    static Board oldBoard;
2009
2010    startedFromPositionFile = FALSE;
2011    if(gameInfo.variant == newVariant) return;
2012
2013    /* [HGM] This routine is called each time an assignment is made to
2014     * gameInfo.variant during a game, to make sure the board sizes
2015     * are set to match the new variant. If that means adding or deleting
2016     * holdings, we shift the playing board accordingly
2017     * This kludge is needed because in ICS observe mode, we get boards
2018     * of an ongoing game without knowing the variant, and learn about the
2019     * latter only later. This can be because of the move list we requested,
2020     * in which case the game history is refilled from the beginning anyway,
2021     * but also when receiving holdings of a crazyhouse game. In the latter
2022     * case we want to add those holdings to the already received position.
2023     */
2024
2025    
2026    if (appData.debugMode) {
2027      fprintf(debugFP, "Switch board from %s to %s\n",
2028              VariantName(gameInfo.variant), VariantName(newVariant));
2029      setbuf(debugFP, NULL);
2030    }
2031    shuffleOpenings = 0;       /* [HGM] shuffle */
2032    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2033    switch(newVariant) 
2034      {
2035      case VariantShogi:
2036        newWidth = 9;  newHeight = 9;
2037        gameInfo.holdingsSize = 7;
2038      case VariantBughouse:
2039      case VariantCrazyhouse:
2040        newHoldingsWidth = 2; break;
2041      case VariantGreat:
2042        newWidth = 10;
2043      case VariantSuper:
2044        newHoldingsWidth = 2;
2045        gameInfo.holdingsSize = 8;
2046        break;
2047      case VariantGothic:
2048      case VariantCapablanca:
2049      case VariantCapaRandom:
2050        newWidth = 10;
2051      default:
2052        newHoldingsWidth = gameInfo.holdingsSize = 0;
2053      };
2054    
2055    if(newWidth  != gameInfo.boardWidth  ||
2056       newHeight != gameInfo.boardHeight ||
2057       newHoldingsWidth != gameInfo.holdingsWidth ) {
2058      
2059      /* shift position to new playing area, if needed */
2060      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++) 
2062          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065        for(i=0; i<newHeight; i++) {
2066          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2067          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2068        }
2069      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2070        for(i=0; i<BOARD_HEIGHT; i++)
2071          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2072            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2073              board[i][j];
2074      }
2075      gameInfo.boardWidth  = newWidth;
2076      gameInfo.boardHeight = newHeight;
2077      gameInfo.holdingsWidth = newHoldingsWidth;
2078      gameInfo.variant = newVariant;
2079      InitDrawingSizes(-2, 0);
2080    } else gameInfo.variant = newVariant;
2081    CopyBoard(oldBoard, board);   // remember correctly formatted board
2082      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2083    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2084 }
2085
2086 static int loggedOn = FALSE;
2087
2088 /*-- Game start info cache: --*/
2089 int gs_gamenum;
2090 char gs_kind[MSG_SIZ];
2091 static char player1Name[128] = "";
2092 static char player2Name[128] = "";
2093 static char cont_seq[] = "\n\\   ";
2094 static int player1Rating = -1;
2095 static int player2Rating = -1;
2096 /*----------------------------*/
2097
2098 ColorClass curColor = ColorNormal;
2099 int suppressKibitz = 0;
2100
2101 // [HGM] seekgraph
2102 Boolean soughtPending = FALSE;
2103 Boolean seekGraphUp;
2104 #define MAX_SEEK_ADS 200
2105 #define SQUARE 0x80
2106 char *seekAdList[MAX_SEEK_ADS];
2107 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2108 float tcList[MAX_SEEK_ADS];
2109 char colorList[MAX_SEEK_ADS];
2110 int nrOfSeekAds = 0;
2111 int minRating = 1010, maxRating = 2800;
2112 int hMargin = 10, vMargin = 20, h, w;
2113 extern int squareSize, lineGap;
2114
2115 void
2116 PlotSeekAd(int i)
2117 {
2118         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2119         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2120         if(r < minRating+100 && r >=0 ) r = minRating+100;
2121         if(r > maxRating) r = maxRating;
2122         if(tc < 1.) tc = 1.;
2123         if(tc > 95.) tc = 95.;
2124         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2125         y = ((double)r - minRating)/(maxRating - minRating)
2126             * (h-vMargin-squareSize/8-1) + vMargin;
2127         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2128         if(strstr(seekAdList[i], " u ")) color = 1;
2129         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2130            !strstr(seekAdList[i], "bullet") &&
2131            !strstr(seekAdList[i], "blitz") &&
2132            !strstr(seekAdList[i], "standard") ) color = 2;
2133         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2134         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2135 }
2136
2137 void
2138 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2139 {
2140         char buf[MSG_SIZ], *ext = "";
2141         VariantClass v = StringToVariant(type);
2142         if(strstr(type, "wild")) {
2143             ext = type + 4; // append wild number
2144             if(v == VariantFischeRandom) type = "chess960"; else
2145             if(v == VariantLoadable) type = "setup"; else
2146             type = VariantName(v);
2147         }
2148         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2149         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2150             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2151             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2152             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2153             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2154             seekNrList[nrOfSeekAds] = nr;
2155             zList[nrOfSeekAds] = 0;
2156             seekAdList[nrOfSeekAds++] = StrSave(buf);
2157             if(plot) PlotSeekAd(nrOfSeekAds-1);
2158         }
2159 }
2160
2161 void
2162 EraseSeekDot(int i)
2163 {
2164     int x = xList[i], y = yList[i], d=squareSize/4, k;
2165     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2166     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2167     // now replot every dot that overlapped
2168     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2169         int xx = xList[k], yy = yList[k];
2170         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2171             DrawSeekDot(xx, yy, colorList[k]);
2172     }
2173 }
2174
2175 void
2176 RemoveSeekAd(int nr)
2177 {
2178         int i;
2179         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2180             EraseSeekDot(i);
2181             if(seekAdList[i]) free(seekAdList[i]);
2182             seekAdList[i] = seekAdList[--nrOfSeekAds];
2183             seekNrList[i] = seekNrList[nrOfSeekAds];
2184             ratingList[i] = ratingList[nrOfSeekAds];
2185             colorList[i]  = colorList[nrOfSeekAds];
2186             tcList[i] = tcList[nrOfSeekAds];
2187             xList[i]  = xList[nrOfSeekAds];
2188             yList[i]  = yList[nrOfSeekAds];
2189             zList[i]  = zList[nrOfSeekAds];
2190             seekAdList[nrOfSeekAds] = NULL;
2191             break;
2192         }
2193 }
2194
2195 Boolean
2196 MatchSoughtLine(char *line)
2197 {
2198     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2199     int nr, base, inc, u=0; char dummy;
2200
2201     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2202        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2203        (u=1) &&
2204        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2205         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2206         // match: compact and save the line
2207         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2208         return TRUE;
2209     }
2210     return FALSE;
2211 }
2212
2213 int
2214 DrawSeekGraph()
2215 {
2216     int i;
2217     if(!seekGraphUp) return FALSE;
2218     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2219     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2220
2221     DrawSeekBackground(0, 0, w, h);
2222     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2223     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2224     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2225         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2226         yy = h-1-yy;
2227         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2228         if(i%500 == 0) {
2229             char buf[MSG_SIZ];
2230             sprintf(buf, "%d", i);
2231             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2232         }
2233     }
2234     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2235     for(i=1; i<100; i+=(i<10?1:5)) {
2236         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2237         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2238         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2239             char buf[MSG_SIZ];
2240             sprintf(buf, "%d", i);
2241             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2242         }
2243     }
2244     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2245     return TRUE;
2246 }
2247
2248 int SeekGraphClick(ClickType click, int x, int y, int moving)
2249 {
2250     static int lastDown = 0, displayed = 0, lastSecond;
2251     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2252         if(click == Release || moving) return FALSE;
2253         nrOfSeekAds = 0;
2254         soughtPending = TRUE;
2255         SendToICS(ics_prefix);
2256         SendToICS("sought\n"); // should this be "sought all"?
2257     } else { // issue challenge based on clicked ad
2258         int dist = 10000; int i, closest = 0, second = 0;
2259         for(i=0; i<nrOfSeekAds; i++) {
2260             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2261             if(d < dist) { dist = d; closest = i; }
2262             second += (d - zList[i] < 120); // count in-range ads
2263             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2264         }
2265         if(dist < 120) {
2266             char buf[MSG_SIZ];
2267             second = (second > 1);
2268             if(displayed != closest || second != lastSecond) {
2269                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2270                 lastSecond = second; displayed = closest;
2271             }
2272             if(click == Press) {
2273                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2274                 lastDown = closest;
2275                 return TRUE;
2276             } // on press 'hit', only show info
2277             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2278             sprintf(buf, "play %d\n", seekNrList[closest]);
2279             SendToICS(ics_prefix);
2280             SendToICS(buf);
2281             return TRUE; // let incoming board of started game pop down the graph
2282         } else if(click == Release) { // release 'miss' is ignored
2283             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2284             if(moving == 2) { // right up-click
2285                 nrOfSeekAds = 0; // refresh graph
2286                 soughtPending = TRUE;
2287                 SendToICS(ics_prefix);
2288                 SendToICS("sought\n"); // should this be "sought all"?
2289             }
2290             return TRUE;
2291         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2292         // press miss or release hit 'pop down' seek graph
2293         seekGraphUp = FALSE;
2294         DrawPosition(TRUE, NULL);
2295     }
2296     return TRUE;
2297 }
2298
2299 void
2300 read_from_ics(isr, closure, data, count, error)
2301      InputSourceRef isr;
2302      VOIDSTAR closure;
2303      char *data;
2304      int count;
2305      int error;
2306 {
2307 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2308 #define STARTED_NONE 0
2309 #define STARTED_MOVES 1
2310 #define STARTED_BOARD 2
2311 #define STARTED_OBSERVE 3
2312 #define STARTED_HOLDINGS 4
2313 #define STARTED_CHATTER 5
2314 #define STARTED_COMMENT 6
2315 #define STARTED_MOVES_NOHIDE 7
2316     
2317     static int started = STARTED_NONE;
2318     static char parse[20000];
2319     static int parse_pos = 0;
2320     static char buf[BUF_SIZE + 1];
2321     static int firstTime = TRUE, intfSet = FALSE;
2322     static ColorClass prevColor = ColorNormal;
2323     static int savingComment = FALSE;
2324     static int cmatch = 0; // continuation sequence match
2325     char *bp;
2326     char str[500];
2327     int i, oldi;
2328     int buf_len;
2329     int next_out;
2330     int tkind;
2331     int backup;    /* [DM] For zippy color lines */
2332     char *p;
2333     char talker[MSG_SIZ]; // [HGM] chat
2334     int channel;
2335
2336     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2337
2338     if (appData.debugMode) {
2339       if (!error) {
2340         fprintf(debugFP, "<ICS: ");
2341         show_bytes(debugFP, data, count);
2342         fprintf(debugFP, "\n");
2343       }
2344     }
2345
2346     if (appData.debugMode) { int f = forwardMostMove;
2347         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2348                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2349                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2350     }
2351     if (count > 0) {
2352         /* If last read ended with a partial line that we couldn't parse,
2353            prepend it to the new read and try again. */
2354         if (leftover_len > 0) {
2355             for (i=0; i<leftover_len; i++)
2356               buf[i] = buf[leftover_start + i];
2357         }
2358
2359     /* copy new characters into the buffer */
2360     bp = buf + leftover_len;
2361     buf_len=leftover_len;
2362     for (i=0; i<count; i++)
2363     {
2364         // ignore these
2365         if (data[i] == '\r')
2366             continue;
2367
2368         // join lines split by ICS?
2369         if (!appData.noJoin)
2370         {
2371             /*
2372                 Joining just consists of finding matches against the
2373                 continuation sequence, and discarding that sequence
2374                 if found instead of copying it.  So, until a match
2375                 fails, there's nothing to do since it might be the
2376                 complete sequence, and thus, something we don't want
2377                 copied.
2378             */
2379             if (data[i] == cont_seq[cmatch])
2380             {
2381                 cmatch++;
2382                 if (cmatch == strlen(cont_seq))
2383                 {
2384                     cmatch = 0; // complete match.  just reset the counter
2385
2386                     /*
2387                         it's possible for the ICS to not include the space
2388                         at the end of the last word, making our [correct]
2389                         join operation fuse two separate words.  the server
2390                         does this when the space occurs at the width setting.
2391                     */
2392                     if (!buf_len || buf[buf_len-1] != ' ')
2393                     {
2394                         *bp++ = ' ';
2395                         buf_len++;
2396                     }
2397                 }
2398                 continue;
2399             }
2400             else if (cmatch)
2401             {
2402                 /*
2403                     match failed, so we have to copy what matched before
2404                     falling through and copying this character.  In reality,
2405                     this will only ever be just the newline character, but
2406                     it doesn't hurt to be precise.
2407                 */
2408                 strncpy(bp, cont_seq, cmatch);
2409                 bp += cmatch;
2410                 buf_len += cmatch;
2411                 cmatch = 0;
2412             }
2413         }
2414
2415         // copy this char
2416         *bp++ = data[i];
2417         buf_len++;
2418     }
2419
2420         buf[buf_len] = NULLCHAR;
2421 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2422         next_out = 0;
2423         leftover_start = 0;
2424         
2425         i = 0;
2426         while (i < buf_len) {
2427             /* Deal with part of the TELNET option negotiation
2428                protocol.  We refuse to do anything beyond the
2429                defaults, except that we allow the WILL ECHO option,
2430                which ICS uses to turn off password echoing when we are
2431                directly connected to it.  We reject this option
2432                if localLineEditing mode is on (always on in xboard)
2433                and we are talking to port 23, which might be a real
2434                telnet server that will try to keep WILL ECHO on permanently.
2435              */
2436             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2437                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2438                 unsigned char option;
2439                 oldi = i;
2440                 switch ((unsigned char) buf[++i]) {
2441                   case TN_WILL:
2442                     if (appData.debugMode)
2443                       fprintf(debugFP, "\n<WILL ");
2444                     switch (option = (unsigned char) buf[++i]) {
2445                       case TN_ECHO:
2446                         if (appData.debugMode)
2447                           fprintf(debugFP, "ECHO ");
2448                         /* Reply only if this is a change, according
2449                            to the protocol rules. */
2450                         if (remoteEchoOption) break;
2451                         if (appData.localLineEditing &&
2452                             atoi(appData.icsPort) == TN_PORT) {
2453                             TelnetRequest(TN_DONT, TN_ECHO);
2454                         } else {
2455                             EchoOff();
2456                             TelnetRequest(TN_DO, TN_ECHO);
2457                             remoteEchoOption = TRUE;
2458                         }
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", option);
2463                         /* Whatever this is, we don't want it. */
2464                         TelnetRequest(TN_DONT, option);
2465                         break;
2466                     }
2467                     break;
2468                   case TN_WONT:
2469                     if (appData.debugMode)
2470                       fprintf(debugFP, "\n<WONT ");
2471                     switch (option = (unsigned char) buf[++i]) {
2472                       case TN_ECHO:
2473                         if (appData.debugMode)
2474                           fprintf(debugFP, "ECHO ");
2475                         /* Reply only if this is a change, according
2476                            to the protocol rules. */
2477                         if (!remoteEchoOption) break;
2478                         EchoOn();
2479                         TelnetRequest(TN_DONT, TN_ECHO);
2480                         remoteEchoOption = FALSE;
2481                         break;
2482                       default:
2483                         if (appData.debugMode)
2484                           fprintf(debugFP, "%d ", (unsigned char) option);
2485                         /* Whatever this is, it must already be turned
2486                            off, because we never agree to turn on
2487                            anything non-default, so according to the
2488                            protocol rules, we don't reply. */
2489                         break;
2490                     }
2491                     break;
2492                   case TN_DO:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<DO ");
2495                     switch (option = (unsigned char) buf[++i]) {
2496                       default:
2497                         /* Whatever this is, we refuse to do it. */
2498                         if (appData.debugMode)
2499                           fprintf(debugFP, "%d ", option);
2500                         TelnetRequest(TN_WONT, option);
2501                         break;
2502                     }
2503                     break;
2504                   case TN_DONT:
2505                     if (appData.debugMode)
2506                       fprintf(debugFP, "\n<DONT ");
2507                     switch (option = (unsigned char) buf[++i]) {
2508                       default:
2509                         if (appData.debugMode)
2510                           fprintf(debugFP, "%d ", option);
2511                         /* Whatever this is, we are already not doing
2512                            it, because we never agree to do anything
2513                            non-default, so according to the protocol
2514                            rules, we don't reply. */
2515                         break;
2516                     }
2517                     break;
2518                   case TN_IAC:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<IAC ");
2521                     /* Doubled IAC; pass it through */
2522                     i--;
2523                     break;
2524                   default:
2525                     if (appData.debugMode)
2526                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2527                     /* Drop all other telnet commands on the floor */
2528                     break;
2529                 }
2530                 if (oldi > next_out)
2531                   SendToPlayer(&buf[next_out], oldi - next_out);
2532                 if (++i > next_out)
2533                   next_out = i;
2534                 continue;
2535             }
2536                 
2537             /* OK, this at least will *usually* work */
2538             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2539                 loggedOn = TRUE;
2540             }
2541             
2542             if (loggedOn && !intfSet) {
2543                 if (ics_type == ICS_ICC) {
2544                   sprintf(str,
2545                           "/set-quietly interface %s\n/set-quietly style 12\n",
2546                           programVersion);
2547                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2548                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2549                 } else if (ics_type == ICS_CHESSNET) {
2550                   sprintf(str, "/style 12\n");
2551                 } else {
2552                   strcpy(str, "alias $ @\n$set interface ");
2553                   strcat(str, programVersion);
2554                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2555                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2556                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2557 #ifdef WIN32
2558                   strcat(str, "$iset nohighlight 1\n");
2559 #endif
2560                   strcat(str, "$iset lock 1\n$style 12\n");
2561                 }
2562                 SendToICS(str);
2563                 NotifyFrontendLogin();
2564                 intfSet = TRUE;
2565             }
2566
2567             if (started == STARTED_COMMENT) {
2568                 /* Accumulate characters in comment */
2569                 parse[parse_pos++] = buf[i];
2570                 if (buf[i] == '\n') {
2571                     parse[parse_pos] = NULLCHAR;
2572                     if(chattingPartner>=0) {
2573                         char mess[MSG_SIZ];
2574                         sprintf(mess, "%s%s", talker, parse);
2575                         OutputChatMessage(chattingPartner, mess);
2576                         chattingPartner = -1;
2577                         next_out = i+1; // [HGM] suppress printing in ICS window
2578                     } else
2579                     if(!suppressKibitz) // [HGM] kibitz
2580                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2581                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2582                         int nrDigit = 0, nrAlph = 0, j;
2583                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2584                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2585                         parse[parse_pos] = NULLCHAR;
2586                         // try to be smart: if it does not look like search info, it should go to
2587                         // ICS interaction window after all, not to engine-output window.
2588                         for(j=0; j<parse_pos; j++) { // count letters and digits
2589                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2590                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2591                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2592                         }
2593                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2594                             int depth=0; float score;
2595                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2596                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2597                                 pvInfoList[forwardMostMove-1].depth = depth;
2598                                 pvInfoList[forwardMostMove-1].score = 100*score;
2599                             }
2600                             OutputKibitz(suppressKibitz, parse);
2601                         } else {
2602                             char tmp[MSG_SIZ];
2603                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2604                             SendToPlayer(tmp, strlen(tmp));
2605                         }
2606                         next_out = i+1; // [HGM] suppress printing in ICS window
2607                     }
2608                     started = STARTED_NONE;
2609                 } else {
2610                     /* Don't match patterns against characters in comment */
2611                     i++;
2612                     continue;
2613                 }
2614             }
2615             if (started == STARTED_CHATTER) {
2616                 if (buf[i] != '\n') {
2617                     /* Don't match patterns against characters in chatter */
2618                     i++;
2619                     continue;
2620                 }
2621                 started = STARTED_NONE;
2622                 if(suppressKibitz) next_out = i+1;
2623             }
2624
2625             /* Kludge to deal with rcmd protocol */
2626             if (firstTime && looking_at(buf, &i, "\001*")) {
2627                 DisplayFatalError(&buf[1], 0, 1);
2628                 continue;
2629             } else {
2630                 firstTime = FALSE;
2631             }
2632
2633             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2634                 ics_type = ICS_ICC;
2635                 ics_prefix = "/";
2636                 if (appData.debugMode)
2637                   fprintf(debugFP, "ics_type %d\n", ics_type);
2638                 continue;
2639             }
2640             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2641                 ics_type = ICS_FICS;
2642                 ics_prefix = "$";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2648                 ics_type = ICS_CHESSNET;
2649                 ics_prefix = "/";
2650                 if (appData.debugMode)
2651                   fprintf(debugFP, "ics_type %d\n", ics_type);
2652                 continue;
2653             }
2654
2655             if (!loggedOn &&
2656                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2657                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2658                  looking_at(buf, &i, "will be \"*\""))) {
2659               strcpy(ics_handle, star_match[0]);
2660               continue;
2661             }
2662
2663             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2664               char buf[MSG_SIZ];
2665               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2666               DisplayIcsInteractionTitle(buf);
2667               have_set_title = TRUE;
2668             }
2669
2670             /* skip finger notes */
2671             if (started == STARTED_NONE &&
2672                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2673                  (buf[i] == '1' && buf[i+1] == '0')) &&
2674                 buf[i+2] == ':' && buf[i+3] == ' ') {
2675               started = STARTED_CHATTER;
2676               i += 3;
2677               continue;
2678             }
2679
2680             oldi = i;
2681             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2682             if(appData.seekGraph) {
2683                 if(soughtPending && MatchSoughtLine(buf+i)) {
2684                     i = strstr(buf+i, "rated") - buf;
2685                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                     next_out = leftover_start = i;
2687                     started = STARTED_CHATTER;
2688                     suppressKibitz = TRUE;
2689                     continue;
2690                 }
2691                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2692                         && looking_at(buf, &i, "* ads displayed")) {
2693                     soughtPending = FALSE;
2694                     seekGraphUp = TRUE;
2695                     DrawSeekGraph();
2696                     continue;
2697                 }
2698                 if(appData.autoRefresh) {
2699                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2700                         int s = (ics_type == ICS_ICC); // ICC format differs
2701                         if(seekGraphUp)
2702                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2703                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2704                         looking_at(buf, &i, "*% "); // eat prompt
2705                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2706                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2707                         next_out = i; // suppress
2708                         continue;
2709                     }
2710                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2711                         char *p = star_match[0];
2712                         while(*p) {
2713                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2714                             while(*p && *p++ != ' '); // next
2715                         }
2716                         looking_at(buf, &i, "*% "); // eat prompt
2717                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2718                         next_out = i;
2719                         continue;
2720                     }
2721                 }
2722             }
2723
2724             /* skip formula vars */
2725             if (started == STARTED_NONE &&
2726                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2727               started = STARTED_CHATTER;
2728               i += 3;
2729               continue;
2730             }
2731
2732             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2733             if (appData.autoKibitz && started == STARTED_NONE && 
2734                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2735                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2736                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2737                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2738                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2739                         suppressKibitz = TRUE;
2740                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2741                         next_out = i;
2742                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2743                                 && (gameMode == IcsPlayingWhite)) ||
2744                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2745                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2746                             started = STARTED_CHATTER; // own kibitz we simply discard
2747                         else {
2748                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2749                             parse_pos = 0; parse[0] = NULLCHAR;
2750                             savingComment = TRUE;
2751                             suppressKibitz = gameMode != IcsObserving ? 2 :
2752                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2753                         } 
2754                         continue;
2755                 } else
2756                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2757                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2758                          && atoi(star_match[0])) {
2759                     // suppress the acknowledgements of our own autoKibitz
2760                     char *p;
2761                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2763                     SendToPlayer(star_match[0], strlen(star_match[0]));
2764                     if(looking_at(buf, &i, "*% ")) // eat prompt
2765                         suppressKibitz = FALSE;
2766                     next_out = i;
2767                     continue;
2768                 }
2769             } // [HGM] kibitz: end of patch
2770
2771             // [HGM] chat: intercept tells by users for which we have an open chat window
2772             channel = -1;
2773             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2774                                            looking_at(buf, &i, "* whispers:") ||
2775                                            looking_at(buf, &i, "* kibitzes:") ||
2776                                            looking_at(buf, &i, "* shouts:") ||
2777                                            looking_at(buf, &i, "* c-shouts:") ||
2778                                            looking_at(buf, &i, "--> * ") ||
2779                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2781                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2782                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2783                 int p;
2784                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2785                 chattingPartner = -1;
2786
2787                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2788                 for(p=0; p<MAX_CHAT; p++) {
2789                     if(channel == atoi(chatPartner[p])) {
2790                     talker[0] = '['; strcat(talker, "] ");
2791                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2792                     chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("kibitzes", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2803                 for(p=0; p<MAX_CHAT; p++) {
2804                     if(!strcmp("whispers", chatPartner[p])) {
2805                         talker[0] = '['; strcat(talker, "] ");
2806                         chattingPartner = p; break;
2807                     }
2808                 } else
2809                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2810                   if(buf[i-8] == '-' && buf[i-3] == 't')
2811                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2812                     if(!strcmp("c-shouts", chatPartner[p])) {
2813                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                   if(chattingPartner < 0)
2818                   for(p=0; p<MAX_CHAT; p++) {
2819                     if(!strcmp("shouts", chatPartner[p])) {
2820                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2821                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2822                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2823                         chattingPartner = p; break;
2824                     }
2825                   }
2826                 }
2827                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2828                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2829                     talker[0] = 0; Colorize(ColorTell, FALSE);
2830                     chattingPartner = p; break;
2831                 }
2832                 if(chattingPartner<0) i = oldi; else {
2833                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2834                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2835                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2836                     started = STARTED_COMMENT;
2837                     parse_pos = 0; parse[0] = NULLCHAR;
2838                     savingComment = 3 + chattingPartner; // counts as TRUE
2839                     suppressKibitz = TRUE;
2840                     continue;
2841                 }
2842             } // [HGM] chat: end of patch
2843
2844             if (appData.zippyTalk || appData.zippyPlay) {
2845                 /* [DM] Backup address for color zippy lines */
2846                 backup = i;
2847 #if ZIPPY
2848                if (loggedOn == TRUE)
2849                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2850                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #if ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case 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     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5265          SetCharTable(pieceNickName, appData.pieceNickNames);
5266     else SetCharTable(pieceNickName, "............");
5267
5268     switch (gameInfo.variant) {
5269     case VariantFischeRandom:
5270       shuffleOpenings = TRUE;
5271     default:
5272       pieces = FIDEArray;
5273       break;
5274     case VariantShatranj:
5275       pieces = ShatranjArray;
5276       nrCastlingRights = 0;
5277       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5278       break;
5279     case VariantMakruk:
5280       pieces = makrukArray;
5281       nrCastlingRights = 0;
5282       startedFromSetupPosition = TRUE;
5283       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5284       break;
5285     case VariantTwoKings:
5286       pieces = twoKingsArray;
5287       break;
5288     case VariantCapaRandom:
5289       shuffleOpenings = TRUE;
5290     case VariantCapablanca:
5291       pieces = CapablancaArray;
5292       gameInfo.boardWidth = 10;
5293       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5294       break;
5295     case VariantGothic:
5296       pieces = GothicArray;
5297       gameInfo.boardWidth = 10;
5298       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5299       break;
5300     case VariantJanus:
5301       pieces = JanusArray;
5302       gameInfo.boardWidth = 10;
5303       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5304       nrCastlingRights = 6;
5305         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5306         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5307         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5308         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5309         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5310         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5311       break;
5312     case VariantFalcon:
5313       pieces = FalconArray;
5314       gameInfo.boardWidth = 10;
5315       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5316       break;
5317     case VariantXiangqi:
5318       pieces = XiangqiArray;
5319       gameInfo.boardWidth  = 9;
5320       gameInfo.boardHeight = 10;
5321       nrCastlingRights = 0;
5322       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5323       break;
5324     case VariantShogi:
5325       pieces = ShogiArray;
5326       gameInfo.boardWidth  = 9;
5327       gameInfo.boardHeight = 9;
5328       gameInfo.holdingsSize = 7;
5329       nrCastlingRights = 0;
5330       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5331       break;
5332     case VariantCourier:
5333       pieces = CourierArray;
5334       gameInfo.boardWidth  = 12;
5335       nrCastlingRights = 0;
5336       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5337       break;
5338     case VariantKnightmate:
5339       pieces = KnightmateArray;
5340       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5341       break;
5342     case VariantFairy:
5343       pieces = fairyArray;
5344       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5345       break;
5346     case VariantGreat:
5347       pieces = GreatArray;
5348       gameInfo.boardWidth = 10;
5349       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5350       gameInfo.holdingsSize = 8;
5351       break;
5352     case VariantSuper:
5353       pieces = FIDEArray;
5354       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5355       gameInfo.holdingsSize = 8;
5356       startedFromSetupPosition = TRUE;
5357       break;
5358     case VariantCrazyhouse:
5359     case VariantBughouse:
5360       pieces = FIDEArray;
5361       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5362       gameInfo.holdingsSize = 5;
5363       break;
5364     case VariantWildCastle:
5365       pieces = FIDEArray;
5366       /* !!?shuffle with kings guaranteed to be on d or e file */
5367       shuffleOpenings = 1;
5368       break;
5369     case VariantNoCastle:
5370       pieces = FIDEArray;
5371       nrCastlingRights = 0;
5372       /* !!?unconstrained back-rank shuffle */
5373       shuffleOpenings = 1;
5374       break;
5375     }
5376
5377     overrule = 0;
5378     if(appData.NrFiles >= 0) {
5379         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5380         gameInfo.boardWidth = appData.NrFiles;
5381     }
5382     if(appData.NrRanks >= 0) {
5383         gameInfo.boardHeight = appData.NrRanks;
5384     }
5385     if(appData.holdingsSize >= 0) {
5386         i = appData.holdingsSize;
5387         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5388         gameInfo.holdingsSize = i;
5389     }
5390     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5391     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5392         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5393
5394     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5395     if(pawnRow < 1) pawnRow = 1;
5396     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5397
5398     /* User pieceToChar list overrules defaults */
5399     if(appData.pieceToCharTable != NULL)
5400         SetCharTable(pieceToChar, appData.pieceToCharTable);
5401
5402     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5403
5404         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5405             s = (ChessSquare) 0; /* account holding counts in guard band */
5406         for( i=0; i<BOARD_HEIGHT; i++ )
5407             initialPosition[i][j] = s;
5408
5409         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5410         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5411         initialPosition[pawnRow][j] = WhitePawn;
5412         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5413         if(gameInfo.variant == VariantXiangqi) {
5414             if(j&1) {
5415                 initialPosition[pawnRow][j] = 
5416                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5417                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5418                    initialPosition[2][j] = WhiteCannon;
5419                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5420                 }
5421             }
5422         }
5423         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5424     }
5425     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5426
5427             j=BOARD_LEFT+1;
5428             initialPosition[1][j] = WhiteBishop;
5429             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5430             j=BOARD_RGHT-2;
5431             initialPosition[1][j] = WhiteRook;
5432             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5433     }
5434
5435     if( nrCastlingRights == -1) {
5436         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5437         /*       This sets default castling rights from none to normal corners   */
5438         /* Variants with other castling rights must set them themselves above    */
5439         nrCastlingRights = 6;
5440        
5441         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5442         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5443         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5444         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5445         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5446         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5447      }
5448
5449      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5450      if(gameInfo.variant == VariantGreat) { // promotion commoners
5451         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5452         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5453         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5454         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5455      }
5456   if (appData.debugMode) {
5457     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5458   }
5459     if(shuffleOpenings) {
5460         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5461         startedFromSetupPosition = TRUE;
5462     }
5463     if(startedFromPositionFile) {
5464       /* [HGM] loadPos: use PositionFile for every new game */
5465       CopyBoard(initialPosition, filePosition);
5466       for(i=0; i<nrCastlingRights; i++)
5467           initialRights[i] = filePosition[CASTLING][i];
5468       startedFromSetupPosition = TRUE;
5469     }
5470
5471     CopyBoard(boards[0], initialPosition);
5472
5473     if(oldx != gameInfo.boardWidth ||
5474        oldy != gameInfo.boardHeight ||
5475        oldh != gameInfo.holdingsWidth
5476 #ifdef GOTHIC
5477        || oldv == VariantGothic ||        // For licensing popups
5478        gameInfo.variant == VariantGothic
5479 #endif
5480 #ifdef FALCON
5481        || oldv == VariantFalcon ||
5482        gameInfo.variant == VariantFalcon
5483 #endif
5484                                          )
5485             InitDrawingSizes(-2 ,0);
5486
5487     if (redraw)
5488       DrawPosition(TRUE, boards[currentMove]);
5489 }
5490
5491 void
5492 SendBoard(cps, moveNum)
5493      ChessProgramState *cps;
5494      int moveNum;
5495 {
5496     char message[MSG_SIZ];
5497     
5498     if (cps->useSetboard) {
5499       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5500       sprintf(message, "setboard %s\n", fen);
5501       SendToProgram(message, cps);
5502       free(fen);
5503
5504     } else {
5505       ChessSquare *bp;
5506       int i, j;
5507       /* Kludge to set black to move, avoiding the troublesome and now
5508        * deprecated "black" command.
5509        */
5510       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5511
5512       SendToProgram("edit\n", cps);
5513       SendToProgram("#\n", cps);
5514       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5515         bp = &boards[moveNum][i][BOARD_LEFT];
5516         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5517           if ((int) *bp < (int) BlackPawn) {
5518             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5519                     AAA + j, ONE + i);
5520             if(message[0] == '+' || message[0] == '~') {
5521                 sprintf(message, "%c%c%c+\n",
5522                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5523                         AAA + j, ONE + i);
5524             }
5525             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5526                 message[1] = BOARD_RGHT   - 1 - j + '1';
5527                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5528             }
5529             SendToProgram(message, cps);
5530           }
5531         }
5532       }
5533     
5534       SendToProgram("c\n", cps);
5535       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5536         bp = &boards[moveNum][i][BOARD_LEFT];
5537         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5538           if (((int) *bp != (int) EmptySquare)
5539               && ((int) *bp >= (int) BlackPawn)) {
5540             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5541                     AAA + j, ONE + i);
5542             if(message[0] == '+' || message[0] == '~') {
5543                 sprintf(message, "%c%c%c+\n",
5544                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5545                         AAA + j, ONE + i);
5546             }
5547             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5548                 message[1] = BOARD_RGHT   - 1 - j + '1';
5549                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5550             }
5551             SendToProgram(message, cps);
5552           }
5553         }
5554       }
5555     
5556       SendToProgram(".\n", cps);
5557     }
5558     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5559 }
5560
5561 static int autoQueen; // [HGM] oneclick
5562
5563 int
5564 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5565 {
5566     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5567     /* [HGM] add Shogi promotions */
5568     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5569     ChessSquare piece;
5570     ChessMove moveType;
5571     Boolean premove;
5572
5573     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5574     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5575
5576     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5577       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5578         return FALSE;
5579
5580     piece = boards[currentMove][fromY][fromX];
5581     if(gameInfo.variant == VariantShogi) {
5582         promotionZoneSize = 3;
5583         highestPromotingPiece = (int)WhiteFerz;
5584     } else if(gameInfo.variant == VariantMakruk) {
5585         promotionZoneSize = 3;
5586     }
5587
5588     // next weed out all moves that do not touch the promotion zone at all
5589     if((int)piece >= BlackPawn) {
5590         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5591              return FALSE;
5592         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5593     } else {
5594         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5595            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5596     }
5597
5598     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5599
5600     // weed out mandatory Shogi promotions
5601     if(gameInfo.variant == VariantShogi) {
5602         if(piece >= BlackPawn) {
5603             if(toY == 0 && piece == BlackPawn ||
5604                toY == 0 && piece == BlackQueen ||
5605                toY <= 1 && piece == BlackKnight) {
5606                 *promoChoice = '+';
5607                 return FALSE;
5608             }
5609         } else {
5610             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5611                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5612                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5613                 *promoChoice = '+';
5614                 return FALSE;
5615             }
5616         }
5617     }
5618
5619     // weed out obviously illegal Pawn moves
5620     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5621         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5622         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5623         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5624         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5625         // note we are not allowed to test for valid (non-)capture, due to premove
5626     }
5627
5628     // we either have a choice what to promote to, or (in Shogi) whether to promote
5629     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5630         *promoChoice = PieceToChar(BlackFerz);  // no choice
5631         return FALSE;
5632     }
5633     if(autoQueen) { // predetermined
5634         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5635              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5636         else *promoChoice = PieceToChar(BlackQueen);
5637         return FALSE;
5638     }
5639
5640     // suppress promotion popup on illegal moves that are not premoves
5641     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5642               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5643     if(appData.testLegality && !premove) {
5644         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5645                         fromY, fromX, toY, toX, NULLCHAR);
5646         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5647            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5648             return FALSE;
5649     }
5650
5651     return TRUE;
5652 }
5653
5654 int
5655 InPalace(row, column)
5656      int row, column;
5657 {   /* [HGM] for Xiangqi */
5658     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5659          column < (BOARD_WIDTH + 4)/2 &&
5660          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5661     return FALSE;
5662 }
5663
5664 int
5665 PieceForSquare (x, y)
5666      int x;
5667      int y;
5668 {
5669   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5670      return -1;
5671   else
5672      return boards[currentMove][y][x];
5673 }
5674
5675 int
5676 OKToStartUserMove(x, y)
5677      int x, y;
5678 {
5679     ChessSquare from_piece;
5680     int white_piece;
5681
5682     if (matchMode) return FALSE;
5683     if (gameMode == EditPosition) return TRUE;
5684
5685     if (x >= 0 && y >= 0)
5686       from_piece = boards[currentMove][y][x];
5687     else
5688       from_piece = EmptySquare;
5689
5690     if (from_piece == EmptySquare) return FALSE;
5691
5692     white_piece = (int)from_piece >= (int)WhitePawn &&
5693       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5694
5695     switch (gameMode) {
5696       case PlayFromGameFile:
5697       case AnalyzeFile:
5698       case TwoMachinesPlay:
5699       case EndOfGame:
5700         return FALSE;
5701
5702       case IcsObserving:
5703       case IcsIdle:
5704         return FALSE;
5705
5706       case MachinePlaysWhite:
5707       case IcsPlayingBlack:
5708         if (appData.zippyPlay) return FALSE;
5709         if (white_piece) {
5710             DisplayMoveError(_("You are playing Black"));
5711             return FALSE;
5712         }
5713         break;
5714
5715       case MachinePlaysBlack:
5716       case IcsPlayingWhite:
5717         if (appData.zippyPlay) return FALSE;
5718         if (!white_piece) {
5719             DisplayMoveError(_("You are playing White"));
5720             return FALSE;
5721         }
5722         break;
5723
5724       case EditGame:
5725         if (!white_piece && WhiteOnMove(currentMove)) {
5726             DisplayMoveError(_("It is White's turn"));
5727             return FALSE;
5728         }           
5729         if (white_piece && !WhiteOnMove(currentMove)) {
5730             DisplayMoveError(_("It is Black's turn"));
5731             return FALSE;
5732         }           
5733         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5734             /* Editing correspondence game history */
5735             /* Could disallow this or prompt for confirmation */
5736             cmailOldMove = -1;
5737         }
5738         break;
5739
5740       case BeginningOfGame:
5741         if (appData.icsActive) return FALSE;
5742         if (!appData.noChessProgram) {
5743             if (!white_piece) {
5744                 DisplayMoveError(_("You are playing White"));
5745                 return FALSE;
5746             }
5747         }
5748         break;
5749         
5750       case Training:
5751         if (!white_piece && WhiteOnMove(currentMove)) {
5752             DisplayMoveError(_("It is White's turn"));
5753             return FALSE;
5754         }           
5755         if (white_piece && !WhiteOnMove(currentMove)) {
5756             DisplayMoveError(_("It is Black's turn"));
5757             return FALSE;
5758         }           
5759         break;
5760
5761       default:
5762       case IcsExamining:
5763         break;
5764     }
5765     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5766         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5767         && gameMode != AnalyzeFile && gameMode != Training) {
5768         DisplayMoveError(_("Displayed position is not current"));
5769         return FALSE;
5770     }
5771     return TRUE;
5772 }
5773
5774 Boolean
5775 OnlyMove(int *x, int *y, Boolean captures) {
5776     DisambiguateClosure cl;
5777     if (appData.zippyPlay) return FALSE;
5778     switch(gameMode) {
5779       case MachinePlaysBlack:
5780       case IcsPlayingWhite:
5781       case BeginningOfGame:
5782         if(!WhiteOnMove(currentMove)) return FALSE;
5783         break;
5784       case MachinePlaysWhite:
5785       case IcsPlayingBlack:
5786         if(WhiteOnMove(currentMove)) return FALSE;
5787         break;
5788       default:
5789         return FALSE;
5790     }
5791     cl.pieceIn = EmptySquare; 
5792     cl.rfIn = *y;
5793     cl.ffIn = *x;
5794     cl.rtIn = -1;
5795     cl.ftIn = -1;
5796     cl.promoCharIn = NULLCHAR;
5797     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5798     if( cl.kind == NormalMove ||
5799         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5800         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5801         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5802         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5803       fromX = cl.ff;
5804       fromY = cl.rf;
5805       *x = cl.ft;
5806       *y = cl.rt;
5807       return TRUE;
5808     }
5809     if(cl.kind != ImpossibleMove) return FALSE;
5810     cl.pieceIn = EmptySquare;
5811     cl.rfIn = -1;
5812     cl.ffIn = -1;
5813     cl.rtIn = *y;
5814     cl.ftIn = *x;
5815     cl.promoCharIn = NULLCHAR;
5816     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5817     if( cl.kind == NormalMove ||
5818         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5819         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5820         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5821         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5822       fromX = cl.ff;
5823       fromY = cl.rf;
5824       *x = cl.ft;
5825       *y = cl.rt;
5826       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5827       return TRUE;
5828     }
5829     return FALSE;
5830 }
5831
5832 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5833 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5834 int lastLoadGameUseList = FALSE;
5835 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5836 ChessMove lastLoadGameStart = (ChessMove) 0;
5837
5838 ChessMove
5839 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5840      int fromX, fromY, toX, toY;
5841      int promoChar;
5842      Boolean captureOwn;
5843 {
5844     ChessMove moveType;
5845     ChessSquare pdown, pup;
5846
5847     /* Check if the user is playing in turn.  This is complicated because we
5848        let the user "pick up" a piece before it is his turn.  So the piece he
5849        tried to pick up may have been captured by the time he puts it down!
5850        Therefore we use the color the user is supposed to be playing in this
5851        test, not the color of the piece that is currently on the starting
5852        square---except in EditGame mode, where the user is playing both
5853        sides; fortunately there the capture race can't happen.  (It can
5854        now happen in IcsExamining mode, but that's just too bad.  The user
5855        will get a somewhat confusing message in that case.)
5856        */
5857
5858     switch (gameMode) {
5859       case PlayFromGameFile:
5860       case AnalyzeFile:
5861       case TwoMachinesPlay:
5862       case EndOfGame:
5863       case IcsObserving:
5864       case IcsIdle:
5865         /* We switched into a game mode where moves are not accepted,
5866            perhaps while the mouse button was down. */
5867         return ImpossibleMove;
5868
5869       case MachinePlaysWhite:
5870         /* User is moving for Black */
5871         if (WhiteOnMove(currentMove)) {
5872             DisplayMoveError(_("It is White's turn"));
5873             return ImpossibleMove;
5874         }
5875         break;
5876
5877       case MachinePlaysBlack:
5878         /* User is moving for White */
5879         if (!WhiteOnMove(currentMove)) {
5880             DisplayMoveError(_("It is Black's turn"));
5881             return ImpossibleMove;
5882         }
5883         break;
5884
5885       case EditGame:
5886       case IcsExamining:
5887       case BeginningOfGame:
5888       case AnalyzeMode:
5889       case Training:
5890         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5891             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5892             /* User is moving for Black */
5893             if (WhiteOnMove(currentMove)) {
5894                 DisplayMoveError(_("It is White's turn"));
5895                 return ImpossibleMove;
5896             }
5897         } else {
5898             /* User is moving for White */
5899             if (!WhiteOnMove(currentMove)) {
5900                 DisplayMoveError(_("It is Black's turn"));
5901                 return ImpossibleMove;
5902             }
5903         }
5904         break;
5905
5906       case IcsPlayingBlack:
5907         /* User is moving for Black */
5908         if (WhiteOnMove(currentMove)) {
5909             if (!appData.premove) {
5910                 DisplayMoveError(_("It is White's turn"));
5911             } else if (toX >= 0 && toY >= 0) {
5912                 premoveToX = toX;
5913                 premoveToY = toY;
5914                 premoveFromX = fromX;
5915                 premoveFromY = fromY;
5916                 premovePromoChar = promoChar;
5917                 gotPremove = 1;
5918                 if (appData.debugMode) 
5919                     fprintf(debugFP, "Got premove: fromX %d,"
5920                             "fromY %d, toX %d, toY %d\n",
5921                             fromX, fromY, toX, toY);
5922             }
5923             return ImpossibleMove;
5924         }
5925         break;
5926
5927       case IcsPlayingWhite:
5928         /* User is moving for White */
5929         if (!WhiteOnMove(currentMove)) {
5930             if (!appData.premove) {
5931                 DisplayMoveError(_("It is Black's turn"));
5932             } else if (toX >= 0 && toY >= 0) {
5933                 premoveToX = toX;
5934                 premoveToY = toY;
5935                 premoveFromX = fromX;
5936                 premoveFromY = fromY;
5937                 premovePromoChar = promoChar;
5938                 gotPremove = 1;
5939                 if (appData.debugMode) 
5940                     fprintf(debugFP, "Got premove: fromX %d,"
5941                             "fromY %d, toX %d, toY %d\n",
5942                             fromX, fromY, toX, toY);
5943             }
5944             return ImpossibleMove;
5945         }
5946         break;
5947
5948       default:
5949         break;
5950
5951       case EditPosition:
5952         /* EditPosition, empty square, or different color piece;
5953            click-click move is possible */
5954         if (toX == -2 || toY == -2) {
5955             boards[0][fromY][fromX] = EmptySquare;
5956             return AmbiguousMove;
5957         } else if (toX >= 0 && toY >= 0) {
5958             boards[0][toY][toX] = boards[0][fromY][fromX];
5959             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5960                 if(boards[0][fromY][0] != EmptySquare) {
5961                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5962                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5963                 }
5964             } else
5965             if(fromX == BOARD_RGHT+1) {
5966                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5967                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5968                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5969                 }
5970             } else
5971             boards[0][fromY][fromX] = EmptySquare;
5972             return AmbiguousMove;
5973         }
5974         return ImpossibleMove;
5975     }
5976
5977     if(toX < 0 || toY < 0) return ImpossibleMove;
5978     pdown = boards[currentMove][fromY][fromX];
5979     pup = boards[currentMove][toY][toX];
5980
5981     /* [HGM] If move started in holdings, it means a drop */
5982     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5983          if( pup != EmptySquare ) return ImpossibleMove;
5984          if(appData.testLegality) {
5985              /* it would be more logical if LegalityTest() also figured out
5986               * which drops are legal. For now we forbid pawns on back rank.
5987               * Shogi is on its own here...
5988               */
5989              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5990                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5991                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5992          }
5993          return WhiteDrop; /* Not needed to specify white or black yet */
5994     }
5995
5996     /* [HGM] always test for legality, to get promotion info */
5997     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5998                                          fromY, fromX, toY, toX, promoChar);
5999     /* [HGM] but possibly ignore an IllegalMove result */
6000     if (appData.testLegality) {
6001         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6002             DisplayMoveError(_("Illegal move"));
6003             return ImpossibleMove;
6004         }
6005     }
6006
6007     return moveType;
6008     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6009        function is made into one that returns an OK move type if FinishMove
6010        should be called. This to give the calling driver routine the
6011        opportunity to finish the userMove input with a promotion popup,
6012        without bothering the user with this for invalid or illegal moves */
6013
6014 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6015 }
6016
6017 /* Common tail of UserMoveEvent and DropMenuEvent */
6018 int
6019 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6020      ChessMove moveType;
6021      int fromX, fromY, toX, toY;
6022      /*char*/int promoChar;
6023 {
6024     char *bookHit = 0;
6025
6026     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6027         // [HGM] superchess: suppress promotions to non-available piece
6028         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6029         if(WhiteOnMove(currentMove)) {
6030             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6031         } else {
6032             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6033         }
6034     }
6035
6036     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6037        move type in caller when we know the move is a legal promotion */
6038     if(moveType == NormalMove && promoChar)
6039         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6040
6041     /* [HGM] convert drag-and-drop piece drops to standard form */
6042     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6043          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6044            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6045                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6046            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6047            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6048            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6049            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6050          fromY = DROP_RANK;
6051     }
6052
6053     /* [HGM] <popupFix> The following if has been moved here from
6054        UserMoveEvent(). Because it seemed to belong here (why not allow
6055        piece drops in training games?), and because it can only be
6056        performed after it is known to what we promote. */
6057     if (gameMode == Training) {
6058       /* compare the move played on the board to the next move in the
6059        * game. If they match, display the move and the opponent's response. 
6060        * If they don't match, display an error message.
6061        */
6062       int saveAnimate;
6063       Board testBoard;
6064       CopyBoard(testBoard, boards[currentMove]);
6065       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6066
6067       if (CompareBoards(testBoard, boards[currentMove+1])) {
6068         ForwardInner(currentMove+1);
6069
6070         /* Autoplay the opponent's response.
6071          * if appData.animate was TRUE when Training mode was entered,
6072          * the response will be animated.
6073          */
6074         saveAnimate = appData.animate;
6075         appData.animate = animateTraining;
6076         ForwardInner(currentMove+1);
6077         appData.animate = saveAnimate;
6078
6079         /* check for the end of the game */
6080         if (currentMove >= forwardMostMove) {
6081           gameMode = PlayFromGameFile;
6082           ModeHighlight();
6083           SetTrainingModeOff();
6084           DisplayInformation(_("End of game"));
6085         }
6086       } else {
6087         DisplayError(_("Incorrect move"), 0);
6088       }
6089       return 1;
6090     }
6091
6092   /* Ok, now we know that the move is good, so we can kill
6093      the previous line in Analysis Mode */
6094   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6095                                 && currentMove < forwardMostMove) {
6096     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6097   }
6098
6099   /* If we need the chess program but it's dead, restart it */
6100   ResurrectChessProgram();
6101
6102   /* A user move restarts a paused game*/
6103   if (pausing)
6104     PauseEvent();
6105
6106   thinkOutput[0] = NULLCHAR;
6107
6108   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6109
6110   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6111
6112   if (gameMode == BeginningOfGame) {
6113     if (appData.noChessProgram) {
6114       gameMode = EditGame;
6115       SetGameInfo();
6116     } else {
6117       char buf[MSG_SIZ];
6118       gameMode = MachinePlaysBlack;
6119       StartClocks();
6120       SetGameInfo();
6121       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6122       DisplayTitle(buf);
6123       if (first.sendName) {
6124         sprintf(buf, "name %s\n", gameInfo.white);
6125         SendToProgram(buf, &first);
6126       }
6127       StartClocks();
6128     }
6129     ModeHighlight();
6130   }
6131
6132   /* Relay move to ICS or chess engine */
6133   if (appData.icsActive) {
6134     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6135         gameMode == IcsExamining) {
6136       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6137         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6138         SendToICS("draw ");
6139         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6140       }
6141       // also send plain move, in case ICS does not understand atomic claims
6142       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6143       ics_user_moved = 1;
6144     }
6145   } else {
6146     if (first.sendTime && (gameMode == BeginningOfGame ||
6147                            gameMode == MachinePlaysWhite ||
6148                            gameMode == MachinePlaysBlack)) {
6149       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6150     }
6151     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6152          // [HGM] book: if program might be playing, let it use book
6153         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6154         first.maybeThinking = TRUE;
6155     } else SendMoveToProgram(forwardMostMove-1, &first);
6156     if (currentMove == cmailOldMove + 1) {
6157       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6158     }
6159   }
6160
6161   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6162
6163   switch (gameMode) {
6164   case EditGame:
6165     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6166     case MT_NONE:
6167     case MT_CHECK:
6168       break;
6169     case MT_CHECKMATE:
6170     case MT_STAINMATE:
6171       if (WhiteOnMove(currentMove)) {
6172         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6173       } else {
6174         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6175       }
6176       break;
6177     case MT_STALEMATE:
6178       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6179       break;
6180     }
6181     break;
6182     
6183   case MachinePlaysBlack:
6184   case MachinePlaysWhite:
6185     /* disable certain menu options while machine is thinking */
6186     SetMachineThinkingEnables();
6187     break;
6188
6189   default:
6190     break;
6191   }
6192
6193   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6194         
6195   if(bookHit) { // [HGM] book: simulate book reply
6196         static char bookMove[MSG_SIZ]; // a bit generous?
6197
6198         programStats.nodes = programStats.depth = programStats.time = 
6199         programStats.score = programStats.got_only_move = 0;
6200         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6201
6202         strcpy(bookMove, "move ");
6203         strcat(bookMove, bookHit);
6204         HandleMachineMove(bookMove, &first);
6205   }
6206   return 1;
6207 }
6208
6209 void
6210 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6211      int fromX, fromY, toX, toY;
6212      int promoChar;
6213 {
6214     /* [HGM] This routine was added to allow calling of its two logical
6215        parts from other modules in the old way. Before, UserMoveEvent()
6216        automatically called FinishMove() if the move was OK, and returned
6217        otherwise. I separated the two, in order to make it possible to
6218        slip a promotion popup in between. But that it always needs two
6219        calls, to the first part, (now called UserMoveTest() ), and to
6220        FinishMove if the first part succeeded. Calls that do not need
6221        to do anything in between, can call this routine the old way. 
6222     */
6223     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6224 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6225     if(moveType == AmbiguousMove)
6226         DrawPosition(FALSE, boards[currentMove]);
6227     else if(moveType != ImpossibleMove && moveType != Comment)
6228         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6229 }
6230
6231 void
6232 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6233      Board board;
6234      int flags;
6235      ChessMove kind;
6236      int rf, ff, rt, ft;
6237      VOIDSTAR closure;
6238 {
6239     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6240     Markers *m = (Markers *) closure;
6241     if(rf == fromY && ff == fromX)
6242         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6243                          || kind == WhiteCapturesEnPassant
6244                          || kind == BlackCapturesEnPassant);
6245     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6246 }
6247
6248 void
6249 MarkTargetSquares(int clear)
6250 {
6251   int x, y;
6252   if(!appData.markers || !appData.highlightDragging || 
6253      !appData.testLegality || gameMode == EditPosition) return;
6254   if(clear) {
6255     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6256   } else {
6257     int capt = 0;
6258     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6259     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6260       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6261       if(capt)
6262       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6263     }
6264   }
6265   DrawPosition(TRUE, NULL);
6266 }
6267
6268 void LeftClick(ClickType clickType, int xPix, int yPix)
6269 {
6270     int x, y;
6271     Boolean saveAnimate;
6272     static int second = 0, promotionChoice = 0, dragging = 0;
6273     char promoChoice = NULLCHAR;
6274
6275     if(appData.seekGraph && appData.icsActive && loggedOn &&
6276         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6277         SeekGraphClick(clickType, xPix, yPix, 0);
6278         return;
6279     }
6280
6281     if (clickType == Press) ErrorPopDown();
6282     MarkTargetSquares(1);
6283
6284     x = EventToSquare(xPix, BOARD_WIDTH);
6285     y = EventToSquare(yPix, BOARD_HEIGHT);
6286     if (!flipView && y >= 0) {
6287         y = BOARD_HEIGHT - 1 - y;
6288     }
6289     if (flipView && x >= 0) {
6290         x = BOARD_WIDTH - 1 - x;
6291     }
6292
6293     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6294         if(clickType == Release) return; // ignore upclick of click-click destination
6295         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6296         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6297         if(gameInfo.holdingsWidth && 
6298                 (WhiteOnMove(currentMove) 
6299                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6300                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6301             // click in right holdings, for determining promotion piece
6302             ChessSquare p = boards[currentMove][y][x];
6303             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6304             if(p != EmptySquare) {
6305                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6306                 fromX = fromY = -1;
6307                 return;
6308             }
6309         }
6310         DrawPosition(FALSE, boards[currentMove]);
6311         return;
6312     }
6313
6314     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6315     if(clickType == Press
6316             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6317               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6318               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6319         return;
6320
6321     autoQueen = appData.alwaysPromoteToQueen;
6322
6323     if (fromX == -1) {
6324       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6325         if (clickType == Press) {
6326             /* First square */
6327             if (OKToStartUserMove(x, y)) {
6328                 fromX = x;
6329                 fromY = y;
6330                 second = 0;
6331                 MarkTargetSquares(0);
6332                 DragPieceBegin(xPix, yPix); dragging = 1;
6333                 if (appData.highlightDragging) {
6334                     SetHighlights(x, y, -1, -1);
6335                 }
6336             }
6337         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6338             DragPieceEnd(xPix, yPix); dragging = 0;
6339             DrawPosition(FALSE, NULL);
6340         }
6341         return;
6342       }
6343     }
6344
6345     /* fromX != -1 */
6346     if (clickType == Press && gameMode != EditPosition) {
6347         ChessSquare fromP;
6348         ChessSquare toP;
6349         int frc;
6350
6351         // ignore off-board to clicks
6352         if(y < 0 || x < 0) return;
6353
6354         /* Check if clicking again on the same color piece */
6355         fromP = boards[currentMove][fromY][fromX];
6356         toP = boards[currentMove][y][x];
6357         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6358         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6359              WhitePawn <= toP && toP <= WhiteKing &&
6360              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6361              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6362             (BlackPawn <= fromP && fromP <= BlackKing && 
6363              BlackPawn <= toP && toP <= BlackKing &&
6364              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6365              !(fromP == BlackKing && toP == BlackRook && frc))) {
6366             /* Clicked again on same color piece -- changed his mind */
6367             second = (x == fromX && y == fromY);
6368            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6369             if (appData.highlightDragging) {
6370                 SetHighlights(x, y, -1, -1);
6371             } else {
6372                 ClearHighlights();
6373             }
6374             if (OKToStartUserMove(x, y)) {
6375                 fromX = x;
6376                 fromY = y; dragging = 1;
6377                 MarkTargetSquares(0);
6378                 DragPieceBegin(xPix, yPix);
6379             }
6380             return;
6381            }
6382         }
6383         // ignore clicks on holdings
6384         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6385     }
6386
6387     if (clickType == Release && x == fromX && y == fromY) {
6388         DragPieceEnd(xPix, yPix); dragging = 0;
6389         if (appData.animateDragging) {
6390             /* Undo animation damage if any */
6391             DrawPosition(FALSE, NULL);
6392         }
6393         if (second) {
6394             /* Second up/down in same square; just abort move */
6395             second = 0;
6396             fromX = fromY = -1;
6397             ClearHighlights();
6398             gotPremove = 0;
6399             ClearPremoveHighlights();
6400         } else {
6401             /* First upclick in same square; start click-click mode */
6402             SetHighlights(x, y, -1, -1);
6403         }
6404         return;
6405     }
6406
6407     /* we now have a different from- and (possibly off-board) to-square */
6408     /* Completed move */
6409     toX = x;
6410     toY = y;
6411     saveAnimate = appData.animate;
6412     if (clickType == Press) {
6413         /* Finish clickclick move */
6414         if (appData.animate || appData.highlightLastMove) {
6415             SetHighlights(fromX, fromY, toX, toY);
6416         } else {
6417             ClearHighlights();
6418         }
6419     } else {
6420         /* Finish drag move */
6421         if (appData.highlightLastMove) {
6422             SetHighlights(fromX, fromY, toX, toY);
6423         } else {
6424             ClearHighlights();
6425         }
6426         DragPieceEnd(xPix, yPix); dragging = 0;
6427         /* Don't animate move and drag both */
6428         appData.animate = FALSE;
6429     }
6430
6431     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6432     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6433         ChessSquare piece = boards[currentMove][fromY][fromX];
6434         if(gameMode == EditPosition && piece != EmptySquare &&
6435            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6436             int n;
6437              
6438             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6439                 n = PieceToNumber(piece - (int)BlackPawn);
6440                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6441                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6442                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6443             } else
6444             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6445                 n = PieceToNumber(piece);
6446                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6447                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6448                 boards[currentMove][n][BOARD_WIDTH-2]++;
6449             }
6450             boards[currentMove][fromY][fromX] = EmptySquare;
6451         }
6452         ClearHighlights();
6453         fromX = fromY = -1;
6454         DrawPosition(TRUE, boards[currentMove]);
6455         return;
6456     }
6457
6458     // off-board moves should not be highlighted
6459     if(x < 0 || x < 0) ClearHighlights();
6460
6461     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6462         SetHighlights(fromX, fromY, toX, toY);
6463         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6464             // [HGM] super: promotion to captured piece selected from holdings
6465             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6466             promotionChoice = TRUE;
6467             // kludge follows to temporarily execute move on display, without promoting yet
6468             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6469             boards[currentMove][toY][toX] = p;
6470             DrawPosition(FALSE, boards[currentMove]);
6471             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6472             boards[currentMove][toY][toX] = q;
6473             DisplayMessage("Click in holdings to choose piece", "");
6474             return;
6475         }
6476         PromotionPopUp();
6477     } else {
6478         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6479         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6480         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6481         fromX = fromY = -1;
6482     }
6483     appData.animate = saveAnimate;
6484     if (appData.animate || appData.animateDragging) {
6485         /* Undo animation damage if needed */
6486         DrawPosition(FALSE, NULL);
6487     }
6488 }
6489
6490 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6491 {   // front-end-free part taken out of PieceMenuPopup
6492     int whichMenu; int xSqr, ySqr;
6493
6494     if(seekGraphUp) { // [HGM] seekgraph
6495         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6496         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6497         return -2;
6498     }
6499
6500     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6501          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6502         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6503         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6504         if(action == Press)   {
6505             originalFlip = flipView;
6506             flipView = !flipView; // temporarily flip board to see game from partners perspective
6507             DrawPosition(TRUE, partnerBoard);
6508             DisplayMessage(partnerStatus, "");
6509             partnerUp = TRUE;
6510         } else if(action == Release) {
6511             flipView = originalFlip;
6512             DrawPosition(TRUE, boards[currentMove]);
6513             partnerUp = FALSE;
6514         }
6515         return -2;
6516     }
6517
6518     xSqr = EventToSquare(x, BOARD_WIDTH);
6519     ySqr = EventToSquare(y, BOARD_HEIGHT);
6520     if (action == Release) UnLoadPV(); // [HGM] pv
6521     if (action != Press) return -2; // return code to be ignored
6522     switch (gameMode) {
6523       case IcsExamining:
6524         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6525       case EditPosition:
6526         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6527         if (xSqr < 0 || ySqr < 0) return -1;\r
6528         whichMenu = 0; // edit-position menu
6529         break;
6530       case IcsObserving:
6531         if(!appData.icsEngineAnalyze) return -1;
6532       case IcsPlayingWhite:
6533       case IcsPlayingBlack:
6534         if(!appData.zippyPlay) goto noZip;
6535       case AnalyzeMode:
6536       case AnalyzeFile:
6537       case MachinePlaysWhite:
6538       case MachinePlaysBlack:
6539       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6540         if (!appData.dropMenu) {
6541           LoadPV(x, y);
6542           return 2; // flag front-end to grab mouse events
6543         }
6544         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6545            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6546       case EditGame:
6547       noZip:
6548         if (xSqr < 0 || ySqr < 0) return -1;
6549         if (!appData.dropMenu || appData.testLegality &&
6550             gameInfo.variant != VariantBughouse &&
6551             gameInfo.variant != VariantCrazyhouse) return -1;
6552         whichMenu = 1; // drop menu
6553         break;
6554       default:
6555         return -1;
6556     }
6557
6558     if (((*fromX = xSqr) < 0) ||
6559         ((*fromY = ySqr) < 0)) {
6560         *fromX = *fromY = -1;
6561         return -1;
6562     }
6563     if (flipView)
6564       *fromX = BOARD_WIDTH - 1 - *fromX;
6565     else
6566       *fromY = BOARD_HEIGHT - 1 - *fromY;
6567
6568     return whichMenu;
6569 }
6570
6571 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6572 {
6573 //    char * hint = lastHint;
6574     FrontEndProgramStats stats;
6575
6576     stats.which = cps == &first ? 0 : 1;
6577     stats.depth = cpstats->depth;
6578     stats.nodes = cpstats->nodes;
6579     stats.score = cpstats->score;
6580     stats.time = cpstats->time;
6581     stats.pv = cpstats->movelist;
6582     stats.hint = lastHint;
6583     stats.an_move_index = 0;
6584     stats.an_move_count = 0;
6585
6586     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6587         stats.hint = cpstats->move_name;
6588         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6589         stats.an_move_count = cpstats->nr_moves;
6590     }
6591
6592     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6593
6594     SetProgramStats( &stats );
6595 }
6596
6597 void
6598 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6599 {       // count all piece types
6600         int p, f, r;
6601         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6602         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6603         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6604                 p = board[r][f];
6605                 pCnt[p]++;
6606                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6607                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6608                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6609                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6610                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6611                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6612         }
6613 }
6614
6615 int
6616 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6617 {
6618         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6619         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6620                    
6621         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6622         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6623         if(myPawns == 2 && nMine == 3) // KPP
6624             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6625         if(myPawns == 1 && nMine == 2) // KP
6626             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6627         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6628             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6629         if(myPawns) return FALSE;
6630         if(pCnt[WhiteRook+side])
6631             return pCnt[BlackRook-side] || 
6632                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6633                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6634                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6635         if(pCnt[WhiteCannon+side]) {
6636             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6637             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6638         }
6639         if(pCnt[WhiteKnight+side])
6640             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6641         return FALSE;
6642 }
6643
6644 int
6645 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6646 {
6647         VariantClass v = gameInfo.variant;
6648
6649         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6650         if(v == VariantShatranj) return TRUE; // always winnable through baring
6651         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6652         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6653
6654         if(v == VariantXiangqi) {
6655                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6656
6657                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6658                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6659                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6660                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6661                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6662                 if(stale) // we have at least one last-rank P plus perhaps C
6663                     return majors // KPKX
6664                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6665                 else // KCA*E*
6666                     return pCnt[WhiteFerz+side] // KCAK
6667                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6668                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6669                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6670
6671         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6672                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6673                 
6674                 if(nMine == 1) return FALSE; // bare King
6675                 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
6676                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6677                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6678                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6679                 if(pCnt[WhiteKnight+side])
6680                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6681                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6682                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6683                 if(nBishops)
6684                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6685                 if(pCnt[WhiteAlfil+side])
6686                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6687                 if(pCnt[WhiteWazir+side])
6688                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6689         }
6690
6691         return TRUE;
6692 }
6693
6694 int
6695 Adjudicate(ChessProgramState *cps)
6696 {       // [HGM] some adjudications useful with buggy engines
6697         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6698         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6699         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6700         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6701         int k, count = 0; static int bare = 1;
6702         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6703         Boolean canAdjudicate = !appData.icsActive;
6704
6705         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6706         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6707             if( appData.testLegality )
6708             {   /* [HGM] Some more adjudications for obstinate engines */
6709                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6710                 static int moveCount = 6;
6711                 ChessMove result;
6712                 char *reason = NULL;
6713
6714                 /* Count what is on board. */
6715                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6716
6717                 /* Some material-based adjudications that have to be made before stalemate test */
6718                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6719                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6720                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6721                      if(canAdjudicate && appData.checkMates) {
6722                          if(engineOpponent)
6723                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6724                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6725                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6726                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6727                          return 1;
6728                      }
6729                 }
6730
6731                 /* Bare King in Shatranj (loses) or Losers (wins) */
6732                 if( nrW == 1 || nrB == 1) {
6733                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6734                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6735                      if(canAdjudicate && appData.checkMates) {
6736                          if(engineOpponent)
6737                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6738                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6739                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6740                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6741                          return 1;
6742                      }
6743                   } else
6744                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6745                   {    /* bare King */
6746                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6747                         if(canAdjudicate && appData.checkMates) {
6748                             /* but only adjudicate if adjudication enabled */
6749                             if(engineOpponent)
6750                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6751                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6752                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6753                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6754                             return 1;
6755                         }
6756                   }
6757                 } else bare = 1;
6758
6759
6760             // don't wait for engine to announce game end if we can judge ourselves
6761             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6762               case MT_CHECK:
6763                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6764                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6765                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6766                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6767                             checkCnt++;
6768                         if(checkCnt >= 2) {
6769                             reason = "Xboard adjudication: 3rd check";
6770                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6771                             break;
6772                         }
6773                     }
6774                 }
6775               case MT_NONE:
6776               default:
6777                 break;
6778               case MT_STALEMATE:
6779               case MT_STAINMATE:
6780                 reason = "Xboard adjudication: Stalemate";
6781                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6782                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6783                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6784                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6785                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6786                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6787                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6788                                                                         EP_CHECKMATE : EP_WINS);
6789                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6790                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6791                 }
6792                 break;
6793               case MT_CHECKMATE:
6794                 reason = "Xboard adjudication: Checkmate";
6795                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6796                 break;
6797             }
6798
6799                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6800                     case EP_STALEMATE:
6801                         result = GameIsDrawn; break;
6802                     case EP_CHECKMATE:
6803                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6804                     case EP_WINS:
6805                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6806                     default:
6807                         result = (ChessMove) 0;
6808                 }
6809                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6810                     if(engineOpponent)
6811                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6812                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6813                     GameEnds( result, reason, GE_XBOARD );
6814                     return 1;
6815                 }
6816
6817                 /* Next absolutely insufficient mating material. */
6818                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6819                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6820                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6821
6822                      /* always flag draws, for judging claims */
6823                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6824
6825                      if(canAdjudicate && appData.materialDraws) {
6826                          /* but only adjudicate them if adjudication enabled */
6827                          if(engineOpponent) {
6828                            SendToProgram("force\n", engineOpponent); // suppress reply
6829                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6830                          }
6831                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6832                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6833                          return 1;
6834                      }
6835                 }
6836
6837                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6838                 if(gameInfo.variant == VariantXiangqi ?
6839                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6840                  : nrW + nrB == 4 && 
6841                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6842                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6843                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6844                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6845                    ) ) {
6846                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6847                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6848                           if(engineOpponent) {
6849                             SendToProgram("force\n", engineOpponent); // suppress reply
6850                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6851                           }
6852                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6853                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6854                           return 1;
6855                      }
6856                 } else moveCount = 6;
6857             }
6858         }
6859           
6860         if (appData.debugMode) { int i;
6861             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6862                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6863                     appData.drawRepeats);
6864             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6865               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6866             
6867         }
6868
6869         // Repetition draws and 50-move rule can be applied independently of legality testing
6870
6871                 /* Check for rep-draws */
6872                 count = 0;
6873                 for(k = forwardMostMove-2;
6874                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6875                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6876                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6877                     k-=2)
6878                 {   int rights=0;
6879                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6880                         /* compare castling rights */
6881                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6882                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6883                                 rights++; /* King lost rights, while rook still had them */
6884                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6885                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6886                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6887                                    rights++; /* but at least one rook lost them */
6888                         }
6889                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6890                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6891                                 rights++; 
6892                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6893                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6894                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6895                                    rights++;
6896                         }
6897                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6898                             && appData.drawRepeats > 1) {
6899                              /* adjudicate after user-specified nr of repeats */
6900                              int result = GameIsDrawn;
6901                              char *details = "XBoard adjudication: repetition draw";
6902                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6903                                 // [HGM] xiangqi: check for forbidden perpetuals
6904                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6905                                 for(m=forwardMostMove; m>k; m-=2) {
6906                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6907                                         ourPerpetual = 0; // the current mover did not always check
6908                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6909                                         hisPerpetual = 0; // the opponent did not always check
6910                                 }
6911                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6912                                                                         ourPerpetual, hisPerpetual);
6913                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6914                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6915                                     details = "Xboard adjudication: perpetual checking";
6916                                 } else
6917                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6918                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6919                                 } else
6920                                 // Now check for perpetual chases
6921                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6922                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6923                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6924                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6925                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6926                                         details = "Xboard adjudication: perpetual chasing";
6927                                     } else
6928                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6929                                         break; // Abort repetition-checking loop.
6930                                 }
6931                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6932                              }
6933                              if(engineOpponent) {
6934                                SendToProgram("force\n", engineOpponent); // suppress reply
6935                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6936                              }
6937                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6938                              GameEnds( result, details, GE_XBOARD );
6939                              return 1;
6940                         }
6941                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6942                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6943                     }
6944                 }
6945
6946                 /* Now we test for 50-move draws. Determine ply count */
6947                 count = forwardMostMove;
6948                 /* look for last irreversble move */
6949                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6950                     count--;
6951                 /* if we hit starting position, add initial plies */
6952                 if( count == backwardMostMove )
6953                     count -= initialRulePlies;
6954                 count = forwardMostMove - count; 
6955                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6956                         // adjust reversible move counter for checks in Xiangqi
6957                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6958                         if(i < backwardMostMove) i = backwardMostMove;
6959                         while(i <= forwardMostMove) {
6960                                 lastCheck = inCheck; // check evasion does not count
6961                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6962                                 if(inCheck || lastCheck) count--; // check does not count
6963                                 i++;
6964                         }
6965                 }
6966                 if( count >= 100)
6967                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6968                          /* this is used to judge if draw claims are legal */
6969                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6970                          if(engineOpponent) {
6971                            SendToProgram("force\n", engineOpponent); // suppress reply
6972                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6973                          }
6974                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6975                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6976                          return 1;
6977                 }
6978
6979                 /* if draw offer is pending, treat it as a draw claim
6980                  * when draw condition present, to allow engines a way to
6981                  * claim draws before making their move to avoid a race
6982                  * condition occurring after their move
6983                  */
6984                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6985                          char *p = NULL;
6986                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6987                              p = "Draw claim: 50-move rule";
6988                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6989                              p = "Draw claim: 3-fold repetition";
6990                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6991                              p = "Draw claim: insufficient mating material";
6992                          if( p != NULL && canAdjudicate) {
6993                              if(engineOpponent) {
6994                                SendToProgram("force\n", engineOpponent); // suppress reply
6995                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6996                              }
6997                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6998                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6999                              return 1;
7000                          }
7001                 }
7002
7003                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7004                     if(engineOpponent) {
7005                       SendToProgram("force\n", engineOpponent); // suppress reply
7006                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7007                     }
7008                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7009                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7010                     return 1;
7011                 }
7012         return 0;
7013 }
7014
7015 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7016 {   // [HGM] book: this routine intercepts moves to simulate book replies
7017     char *bookHit = NULL;
7018
7019     //first determine if the incoming move brings opponent into his book
7020     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7021         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7022     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7023     if(bookHit != NULL && !cps->bookSuspend) {
7024         // make sure opponent is not going to reply after receiving move to book position
7025         SendToProgram("force\n", cps);
7026         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7027     }
7028     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7029     // now arrange restart after book miss
7030     if(bookHit) {
7031         // after a book hit we never send 'go', and the code after the call to this routine
7032         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7033         char buf[MSG_SIZ];
7034         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7035         SendToProgram(buf, cps);
7036         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7037     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7038         SendToProgram("go\n", cps);
7039         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7040     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7041         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7042             SendToProgram("go\n", cps); 
7043         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7044     }
7045     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7046 }
7047
7048 char *savedMessage;
7049 ChessProgramState *savedState;
7050 void DeferredBookMove(void)
7051 {
7052         if(savedState->lastPing != savedState->lastPong)
7053                     ScheduleDelayedEvent(DeferredBookMove, 10);
7054         else
7055         HandleMachineMove(savedMessage, savedState);
7056 }
7057
7058 void
7059 HandleMachineMove(message, cps)
7060      char *message;
7061      ChessProgramState *cps;
7062 {
7063     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7064     char realname[MSG_SIZ];
7065     int fromX, fromY, toX, toY;
7066     ChessMove moveType;
7067     char promoChar;
7068     char *p;
7069     int machineWhite;
7070     char *bookHit;
7071
7072     cps->userError = 0;
7073
7074 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7075     /*
7076      * Kludge to ignore BEL characters
7077      */
7078     while (*message == '\007') message++;
7079
7080     /*
7081      * [HGM] engine debug message: ignore lines starting with '#' character
7082      */
7083     if(cps->debug && *message == '#') return;
7084
7085     /*
7086      * Look for book output
7087      */
7088     if (cps == &first && bookRequested) {
7089         if (message[0] == '\t' || message[0] == ' ') {
7090             /* Part of the book output is here; append it */
7091             strcat(bookOutput, message);
7092             strcat(bookOutput, "  \n");
7093             return;
7094         } else if (bookOutput[0] != NULLCHAR) {
7095             /* All of book output has arrived; display it */
7096             char *p = bookOutput;
7097             while (*p != NULLCHAR) {
7098                 if (*p == '\t') *p = ' ';
7099                 p++;
7100             }
7101             DisplayInformation(bookOutput);
7102             bookRequested = FALSE;
7103             /* Fall through to parse the current output */
7104         }
7105     }
7106
7107     /*
7108      * Look for machine move.
7109      */
7110     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7111         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7112     {
7113         /* This method is only useful on engines that support ping */
7114         if (cps->lastPing != cps->lastPong) {
7115           if (gameMode == BeginningOfGame) {
7116             /* Extra move from before last new; ignore */
7117             if (appData.debugMode) {
7118                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7119             }
7120           } else {
7121             if (appData.debugMode) {
7122                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7123                         cps->which, gameMode);
7124             }
7125
7126             SendToProgram("undo\n", cps);
7127           }
7128           return;
7129         }
7130
7131         switch (gameMode) {
7132           case BeginningOfGame:
7133             /* Extra move from before last reset; ignore */
7134             if (appData.debugMode) {
7135                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7136             }
7137             return;
7138
7139           case EndOfGame:
7140           case IcsIdle:
7141           default:
7142             /* Extra move after we tried to stop.  The mode test is
7143                not a reliable way of detecting this problem, but it's
7144                the best we can do on engines that don't support ping.
7145             */
7146             if (appData.debugMode) {
7147                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7148                         cps->which, gameMode);
7149             }
7150             SendToProgram("undo\n", cps);
7151             return;
7152
7153           case MachinePlaysWhite:
7154           case IcsPlayingWhite:
7155             machineWhite = TRUE;
7156             break;
7157
7158           case MachinePlaysBlack:
7159           case IcsPlayingBlack:
7160             machineWhite = FALSE;
7161             break;
7162
7163           case TwoMachinesPlay:
7164             machineWhite = (cps->twoMachinesColor[0] == 'w');
7165             break;
7166         }
7167         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7168             if (appData.debugMode) {
7169                 fprintf(debugFP,
7170                         "Ignoring move out of turn by %s, gameMode %d"
7171                         ", forwardMost %d\n",
7172                         cps->which, gameMode, forwardMostMove);
7173             }
7174             return;
7175         }
7176
7177     if (appData.debugMode) { int f = forwardMostMove;
7178         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7179                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7180                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7181     }
7182         if(cps->alphaRank) AlphaRank(machineMove, 4);
7183         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7184                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7185             /* Machine move could not be parsed; ignore it. */
7186             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7187                     machineMove, cps->which);
7188             DisplayError(buf1, 0);
7189             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7190                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7191             if (gameMode == TwoMachinesPlay) {
7192               GameEnds(machineWhite ? BlackWins : WhiteWins,
7193                        buf1, GE_XBOARD);
7194             }
7195             return;
7196         }
7197
7198         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7199         /* So we have to redo legality test with true e.p. status here,  */
7200         /* to make sure an illegal e.p. capture does not slip through,   */
7201         /* to cause a forfeit on a justified illegal-move complaint      */
7202         /* of the opponent.                                              */
7203         if( gameMode==TwoMachinesPlay && appData.testLegality
7204             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7205                                                               ) {
7206            ChessMove moveType;
7207            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7208                              fromY, fromX, toY, toX, promoChar);
7209             if (appData.debugMode) {
7210                 int i;
7211                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7212                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7213                 fprintf(debugFP, "castling rights\n");
7214             }
7215             if(moveType == IllegalMove) {
7216                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7217                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7218                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7219                            buf1, GE_XBOARD);
7220                 return;
7221            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7222            /* [HGM] Kludge to handle engines that send FRC-style castling
7223               when they shouldn't (like TSCP-Gothic) */
7224            switch(moveType) {
7225              case WhiteASideCastleFR:
7226              case BlackASideCastleFR:
7227                toX+=2;
7228                currentMoveString[2]++;
7229                break;
7230              case WhiteHSideCastleFR:
7231              case BlackHSideCastleFR:
7232                toX--;
7233                currentMoveString[2]--;
7234                break;
7235              default: ; // nothing to do, but suppresses warning of pedantic compilers
7236            }
7237         }
7238         hintRequested = FALSE;
7239         lastHint[0] = NULLCHAR;
7240         bookRequested = FALSE;
7241         /* Program may be pondering now */
7242         cps->maybeThinking = TRUE;
7243         if (cps->sendTime == 2) cps->sendTime = 1;
7244         if (cps->offeredDraw) cps->offeredDraw--;
7245
7246         /* currentMoveString is set as a side-effect of ParseOneMove */
7247         strcpy(machineMove, currentMoveString);
7248         strcat(machineMove, "\n");
7249         strcpy(moveList[forwardMostMove], machineMove);
7250
7251         /* [AS] Save move info*/
7252         pvInfoList[ forwardMostMove ].score = programStats.score;
7253         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7254         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7255
7256         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7257
7258         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7259         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7260             int count = 0;
7261
7262             while( count < adjudicateLossPlies ) {
7263                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7264
7265                 if( count & 1 ) {
7266                     score = -score; /* Flip score for winning side */
7267                 }
7268
7269                 if( score > adjudicateLossThreshold ) {
7270                     break;
7271                 }
7272
7273                 count++;
7274             }
7275
7276             if( count >= adjudicateLossPlies ) {
7277                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7278
7279                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7280                     "Xboard adjudication", 
7281                     GE_XBOARD );
7282
7283                 return;
7284             }
7285         }
7286
7287         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7288
7289 #if ZIPPY
7290         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7291             first.initDone) {
7292           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7293                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7294                 SendToICS("draw ");
7295                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7296           }
7297           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7298           ics_user_moved = 1;
7299           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7300                 char buf[3*MSG_SIZ];
7301
7302                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7303                         programStats.score / 100.,
7304                         programStats.depth,
7305                         programStats.time / 100.,
7306                         (unsigned int)programStats.nodes,
7307                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7308                         programStats.movelist);
7309                 SendToICS(buf);
7310 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7311           }
7312         }
7313 #endif
7314
7315         /* [AS] Clear stats for next move */
7316         ClearProgramStats();
7317         thinkOutput[0] = NULLCHAR;
7318         hiddenThinkOutputState = 0;
7319
7320         bookHit = NULL;
7321         if (gameMode == TwoMachinesPlay) {
7322             /* [HGM] relaying draw offers moved to after reception of move */
7323             /* and interpreting offer as claim if it brings draw condition */
7324             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7325                 SendToProgram("draw\n", cps->other);
7326             }
7327             if (cps->other->sendTime) {
7328                 SendTimeRemaining(cps->other,
7329                                   cps->other->twoMachinesColor[0] == 'w');
7330             }
7331             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7332             if (firstMove && !bookHit) {
7333                 firstMove = FALSE;
7334                 if (cps->other->useColors) {
7335                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7336                 }
7337                 SendToProgram("go\n", cps->other);
7338             }
7339             cps->other->maybeThinking = TRUE;
7340         }
7341
7342         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7343         
7344         if (!pausing && appData.ringBellAfterMoves) {
7345             RingBell();
7346         }
7347
7348         /* 
7349          * Reenable menu items that were disabled while
7350          * machine was thinking
7351          */
7352         if (gameMode != TwoMachinesPlay)
7353             SetUserThinkingEnables();
7354
7355         // [HGM] book: after book hit opponent has received move and is now in force mode
7356         // force the book reply into it, and then fake that it outputted this move by jumping
7357         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7358         if(bookHit) {
7359                 static char bookMove[MSG_SIZ]; // a bit generous?
7360
7361                 strcpy(bookMove, "move ");
7362                 strcat(bookMove, bookHit);
7363                 message = bookMove;
7364                 cps = cps->other;
7365                 programStats.nodes = programStats.depth = programStats.time = 
7366                 programStats.score = programStats.got_only_move = 0;
7367                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7368
7369                 if(cps->lastPing != cps->lastPong) {
7370                     savedMessage = message; // args for deferred call
7371                     savedState = cps;
7372                     ScheduleDelayedEvent(DeferredBookMove, 10);
7373                     return;
7374                 }
7375                 goto FakeBookMove;
7376         }
7377
7378         return;
7379     }
7380
7381     /* Set special modes for chess engines.  Later something general
7382      *  could be added here; for now there is just one kludge feature,
7383      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7384      *  when "xboard" is given as an interactive command.
7385      */
7386     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7387         cps->useSigint = FALSE;
7388         cps->useSigterm = FALSE;
7389     }
7390     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7391       ParseFeatures(message+8, cps);
7392       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7393     }
7394
7395     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7396      * want this, I was asked to put it in, and obliged.
7397      */
7398     if (!strncmp(message, "setboard ", 9)) {
7399         Board initial_position;
7400
7401         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7402
7403         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7404             DisplayError(_("Bad FEN received from engine"), 0);
7405             return ;
7406         } else {
7407            Reset(TRUE, FALSE);
7408            CopyBoard(boards[0], initial_position);
7409            initialRulePlies = FENrulePlies;
7410            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7411            else gameMode = MachinePlaysBlack;                 
7412            DrawPosition(FALSE, boards[currentMove]);
7413         }
7414         return;
7415     }
7416
7417     /*
7418      * Look for communication commands
7419      */
7420     if (!strncmp(message, "telluser ", 9)) {
7421         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7422         DisplayNote(message + 9);
7423         return;
7424     }
7425     if (!strncmp(message, "tellusererror ", 14)) {
7426         cps->userError = 1;
7427         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7428         DisplayError(message + 14, 0);
7429         return;
7430     }
7431     if (!strncmp(message, "tellopponent ", 13)) {
7432       if (appData.icsActive) {
7433         if (loggedOn) {
7434           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7435           SendToICS(buf1);
7436         }
7437       } else {
7438         DisplayNote(message + 13);
7439       }
7440       return;
7441     }
7442     if (!strncmp(message, "tellothers ", 11)) {
7443       if (appData.icsActive) {
7444         if (loggedOn) {
7445           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7446           SendToICS(buf1);
7447         }
7448       }
7449       return;
7450     }
7451     if (!strncmp(message, "tellall ", 8)) {
7452       if (appData.icsActive) {
7453         if (loggedOn) {
7454           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7455           SendToICS(buf1);
7456         }
7457       } else {
7458         DisplayNote(message + 8);
7459       }
7460       return;
7461     }
7462     if (strncmp(message, "warning", 7) == 0) {
7463         /* Undocumented feature, use tellusererror in new code */
7464         DisplayError(message, 0);
7465         return;
7466     }
7467     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7468         strcpy(realname, cps->tidy);
7469         strcat(realname, " query");
7470         AskQuestion(realname, buf2, buf1, cps->pr);
7471         return;
7472     }
7473     /* Commands from the engine directly to ICS.  We don't allow these to be 
7474      *  sent until we are logged on. Crafty kibitzes have been known to 
7475      *  interfere with the login process.
7476      */
7477     if (loggedOn) {
7478         if (!strncmp(message, "tellics ", 8)) {
7479             SendToICS(message + 8);
7480             SendToICS("\n");
7481             return;
7482         }
7483         if (!strncmp(message, "tellicsnoalias ", 15)) {
7484             SendToICS(ics_prefix);
7485             SendToICS(message + 15);
7486             SendToICS("\n");
7487             return;
7488         }
7489         /* The following are for backward compatibility only */
7490         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7491             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7492             SendToICS(ics_prefix);
7493             SendToICS(message);
7494             SendToICS("\n");
7495             return;
7496         }
7497     }
7498     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7499         return;
7500     }
7501     /*
7502      * If the move is illegal, cancel it and redraw the board.
7503      * Also deal with other error cases.  Matching is rather loose
7504      * here to accommodate engines written before the spec.
7505      */
7506     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7507         strncmp(message, "Error", 5) == 0) {
7508         if (StrStr(message, "name") || 
7509             StrStr(message, "rating") || StrStr(message, "?") ||
7510             StrStr(message, "result") || StrStr(message, "board") ||
7511             StrStr(message, "bk") || StrStr(message, "computer") ||
7512             StrStr(message, "variant") || StrStr(message, "hint") ||
7513             StrStr(message, "random") || StrStr(message, "depth") ||
7514             StrStr(message, "accepted")) {
7515             return;
7516         }
7517         if (StrStr(message, "protover")) {
7518           /* Program is responding to input, so it's apparently done
7519              initializing, and this error message indicates it is
7520              protocol version 1.  So we don't need to wait any longer
7521              for it to initialize and send feature commands. */
7522           FeatureDone(cps, 1);
7523           cps->protocolVersion = 1;
7524           return;
7525         }
7526         cps->maybeThinking = FALSE;
7527
7528         if (StrStr(message, "draw")) {
7529             /* Program doesn't have "draw" command */
7530             cps->sendDrawOffers = 0;
7531             return;
7532         }
7533         if (cps->sendTime != 1 &&
7534             (StrStr(message, "time") || StrStr(message, "otim"))) {
7535           /* Program apparently doesn't have "time" or "otim" command */
7536           cps->sendTime = 0;
7537           return;
7538         }
7539         if (StrStr(message, "analyze")) {
7540             cps->analysisSupport = FALSE;
7541             cps->analyzing = FALSE;
7542             Reset(FALSE, TRUE);
7543             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7544             DisplayError(buf2, 0);
7545             return;
7546         }
7547         if (StrStr(message, "(no matching move)st")) {
7548           /* Special kludge for GNU Chess 4 only */
7549           cps->stKludge = TRUE;
7550           SendTimeControl(cps, movesPerSession, timeControl,
7551                           timeIncrement, appData.searchDepth,
7552                           searchTime);
7553           return;
7554         }
7555         if (StrStr(message, "(no matching move)sd")) {
7556           /* Special kludge for GNU Chess 4 only */
7557           cps->sdKludge = TRUE;
7558           SendTimeControl(cps, movesPerSession, timeControl,
7559                           timeIncrement, appData.searchDepth,
7560                           searchTime);
7561           return;
7562         }
7563         if (!StrStr(message, "llegal")) {
7564             return;
7565         }
7566         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7567             gameMode == IcsIdle) return;
7568         if (forwardMostMove <= backwardMostMove) return;
7569         if (pausing) PauseEvent();
7570       if(appData.forceIllegal) {
7571             // [HGM] illegal: machine refused move; force position after move into it
7572           SendToProgram("force\n", cps);
7573           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7574                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7575                 // when black is to move, while there might be nothing on a2 or black
7576                 // might already have the move. So send the board as if white has the move.
7577                 // But first we must change the stm of the engine, as it refused the last move
7578                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7579                 if(WhiteOnMove(forwardMostMove)) {
7580                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7581                     SendBoard(cps, forwardMostMove); // kludgeless board
7582                 } else {
7583                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7584                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7585                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7586                 }
7587           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7588             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7589                  gameMode == TwoMachinesPlay)
7590               SendToProgram("go\n", cps);
7591             return;
7592       } else
7593         if (gameMode == PlayFromGameFile) {
7594             /* Stop reading this game file */
7595             gameMode = EditGame;
7596             ModeHighlight();
7597         }
7598         currentMove = forwardMostMove-1;
7599         DisplayMove(currentMove-1); /* before DisplayMoveError */
7600         SwitchClocks(forwardMostMove-1); // [HGM] race
7601         DisplayBothClocks();
7602         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7603                 parseList[currentMove], cps->which);
7604         DisplayMoveError(buf1);
7605         DrawPosition(FALSE, boards[currentMove]);
7606
7607         /* [HGM] illegal-move claim should forfeit game when Xboard */
7608         /* only passes fully legal moves                            */
7609         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7610             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7611                                 "False illegal-move claim", GE_XBOARD );
7612         }
7613         return;
7614     }
7615     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7616         /* Program has a broken "time" command that
7617            outputs a string not ending in newline.
7618            Don't use it. */
7619         cps->sendTime = 0;
7620     }
7621     
7622     /*
7623      * If chess program startup fails, exit with an error message.
7624      * Attempts to recover here are futile.
7625      */
7626     if ((StrStr(message, "unknown host") != NULL)
7627         || (StrStr(message, "No remote directory") != NULL)
7628         || (StrStr(message, "not found") != NULL)
7629         || (StrStr(message, "No such file") != NULL)
7630         || (StrStr(message, "can't alloc") != NULL)
7631         || (StrStr(message, "Permission denied") != NULL)) {
7632
7633         cps->maybeThinking = FALSE;
7634         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7635                 cps->which, cps->program, cps->host, message);
7636         RemoveInputSource(cps->isr);
7637         DisplayFatalError(buf1, 0, 1);
7638         return;
7639     }
7640     
7641     /* 
7642      * Look for hint output
7643      */
7644     if (sscanf(message, "Hint: %s", buf1) == 1) {
7645         if (cps == &first && hintRequested) {
7646             hintRequested = FALSE;
7647             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7648                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7649                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7650                                     PosFlags(forwardMostMove),
7651                                     fromY, fromX, toY, toX, promoChar, buf1);
7652                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7653                 DisplayInformation(buf2);
7654             } else {
7655                 /* Hint move could not be parsed!? */
7656               snprintf(buf2, sizeof(buf2),
7657                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7658                         buf1, cps->which);
7659                 DisplayError(buf2, 0);
7660             }
7661         } else {
7662             strcpy(lastHint, buf1);
7663         }
7664         return;
7665     }
7666
7667     /*
7668      * Ignore other messages if game is not in progress
7669      */
7670     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7671         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7672
7673     /*
7674      * look for win, lose, draw, or draw offer
7675      */
7676     if (strncmp(message, "1-0", 3) == 0) {
7677         char *p, *q, *r = "";
7678         p = strchr(message, '{');
7679         if (p) {
7680             q = strchr(p, '}');
7681             if (q) {
7682                 *q = NULLCHAR;
7683                 r = p + 1;
7684             }
7685         }
7686         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7687         return;
7688     } else if (strncmp(message, "0-1", 3) == 0) {
7689         char *p, *q, *r = "";
7690         p = strchr(message, '{');
7691         if (p) {
7692             q = strchr(p, '}');
7693             if (q) {
7694                 *q = NULLCHAR;
7695                 r = p + 1;
7696             }
7697         }
7698         /* Kludge for Arasan 4.1 bug */
7699         if (strcmp(r, "Black resigns") == 0) {
7700             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7701             return;
7702         }
7703         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7704         return;
7705     } else if (strncmp(message, "1/2", 3) == 0) {
7706         char *p, *q, *r = "";
7707         p = strchr(message, '{');
7708         if (p) {
7709             q = strchr(p, '}');
7710             if (q) {
7711                 *q = NULLCHAR;
7712                 r = p + 1;
7713             }
7714         }
7715             
7716         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7717         return;
7718
7719     } else if (strncmp(message, "White resign", 12) == 0) {
7720         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7721         return;
7722     } else if (strncmp(message, "Black resign", 12) == 0) {
7723         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7724         return;
7725     } else if (strncmp(message, "White matches", 13) == 0 ||
7726                strncmp(message, "Black matches", 13) == 0   ) {
7727         /* [HGM] ignore GNUShogi noises */
7728         return;
7729     } else if (strncmp(message, "White", 5) == 0 &&
7730                message[5] != '(' &&
7731                StrStr(message, "Black") == NULL) {
7732         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7733         return;
7734     } else if (strncmp(message, "Black", 5) == 0 &&
7735                message[5] != '(') {
7736         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7737         return;
7738     } else if (strcmp(message, "resign") == 0 ||
7739                strcmp(message, "computer resigns") == 0) {
7740         switch (gameMode) {
7741           case MachinePlaysBlack:
7742           case IcsPlayingBlack:
7743             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7744             break;
7745           case MachinePlaysWhite:
7746           case IcsPlayingWhite:
7747             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7748             break;
7749           case TwoMachinesPlay:
7750             if (cps->twoMachinesColor[0] == 'w')
7751               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7752             else
7753               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7754             break;
7755           default:
7756             /* can't happen */
7757             break;
7758         }
7759         return;
7760     } else if (strncmp(message, "opponent mates", 14) == 0) {
7761         switch (gameMode) {
7762           case MachinePlaysBlack:
7763           case IcsPlayingBlack:
7764             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7765             break;
7766           case MachinePlaysWhite:
7767           case IcsPlayingWhite:
7768             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7769             break;
7770           case TwoMachinesPlay:
7771             if (cps->twoMachinesColor[0] == 'w')
7772               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7773             else
7774               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7775             break;
7776           default:
7777             /* can't happen */
7778             break;
7779         }
7780         return;
7781     } else if (strncmp(message, "computer mates", 14) == 0) {
7782         switch (gameMode) {
7783           case MachinePlaysBlack:
7784           case IcsPlayingBlack:
7785             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7786             break;
7787           case MachinePlaysWhite:
7788           case IcsPlayingWhite:
7789             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7790             break;
7791           case TwoMachinesPlay:
7792             if (cps->twoMachinesColor[0] == 'w')
7793               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7794             else
7795               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7796             break;
7797           default:
7798             /* can't happen */
7799             break;
7800         }
7801         return;
7802     } else if (strncmp(message, "checkmate", 9) == 0) {
7803         if (WhiteOnMove(forwardMostMove)) {
7804             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7805         } else {
7806             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7807         }
7808         return;
7809     } else if (strstr(message, "Draw") != NULL ||
7810                strstr(message, "game is a draw") != NULL) {
7811         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7812         return;
7813     } else if (strstr(message, "offer") != NULL &&
7814                strstr(message, "draw") != NULL) {
7815 #if ZIPPY
7816         if (appData.zippyPlay && first.initDone) {
7817             /* Relay offer to ICS */
7818             SendToICS(ics_prefix);
7819             SendToICS("draw\n");
7820         }
7821 #endif
7822         cps->offeredDraw = 2; /* valid until this engine moves twice */
7823         if (gameMode == TwoMachinesPlay) {
7824             if (cps->other->offeredDraw) {
7825                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7826             /* [HGM] in two-machine mode we delay relaying draw offer      */
7827             /* until after we also have move, to see if it is really claim */
7828             }
7829         } else if (gameMode == MachinePlaysWhite ||
7830                    gameMode == MachinePlaysBlack) {
7831           if (userOfferedDraw) {
7832             DisplayInformation(_("Machine accepts your draw offer"));
7833             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7834           } else {
7835             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7836           }
7837         }
7838     }
7839
7840     
7841     /*
7842      * Look for thinking output
7843      */
7844     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7845           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7846                                 ) {
7847         int plylev, mvleft, mvtot, curscore, time;
7848         char mvname[MOVE_LEN];
7849         u64 nodes; // [DM]
7850         char plyext;
7851         int ignore = FALSE;
7852         int prefixHint = FALSE;
7853         mvname[0] = NULLCHAR;
7854
7855         switch (gameMode) {
7856           case MachinePlaysBlack:
7857           case IcsPlayingBlack:
7858             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7859             break;
7860           case MachinePlaysWhite:
7861           case IcsPlayingWhite:
7862             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7863             break;
7864           case AnalyzeMode:
7865           case AnalyzeFile:
7866             break;
7867           case IcsObserving: /* [DM] icsEngineAnalyze */
7868             if (!appData.icsEngineAnalyze) ignore = TRUE;
7869             break;
7870           case TwoMachinesPlay:
7871             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7872                 ignore = TRUE;
7873             }
7874             break;
7875           default:
7876             ignore = TRUE;
7877             break;
7878         }
7879
7880         if (!ignore) {
7881             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7882             buf1[0] = NULLCHAR;
7883             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7884                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7885
7886                 if (plyext != ' ' && plyext != '\t') {
7887                     time *= 100;
7888                 }
7889
7890                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7891                 if( cps->scoreIsAbsolute && 
7892                     ( gameMode == MachinePlaysBlack ||
7893                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7894                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7895                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7896                      !WhiteOnMove(currentMove)
7897                     ) )
7898                 {
7899                     curscore = -curscore;
7900                 }
7901
7902
7903                 tempStats.depth = plylev;
7904                 tempStats.nodes = nodes;
7905                 tempStats.time = time;
7906                 tempStats.score = curscore;
7907                 tempStats.got_only_move = 0;
7908
7909                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7910                         int ticklen;
7911
7912                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7913                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7914                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7915                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7916                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7917                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7918                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7919                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7920                 }
7921
7922                 /* Buffer overflow protection */
7923                 if (buf1[0] != NULLCHAR) {
7924                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7925                         && appData.debugMode) {
7926                         fprintf(debugFP,
7927                                 "PV is too long; using the first %u bytes.\n",
7928                                 (unsigned) sizeof(tempStats.movelist) - 1);
7929                     }
7930
7931                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7932                 } else {
7933                     sprintf(tempStats.movelist, " no PV\n");
7934                 }
7935
7936                 if (tempStats.seen_stat) {
7937                     tempStats.ok_to_send = 1;
7938                 }
7939
7940                 if (strchr(tempStats.movelist, '(') != NULL) {
7941                     tempStats.line_is_book = 1;
7942                     tempStats.nr_moves = 0;
7943                     tempStats.moves_left = 0;
7944                 } else {
7945                     tempStats.line_is_book = 0;
7946                 }
7947
7948                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7949                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7950
7951                 SendProgramStatsToFrontend( cps, &tempStats );
7952
7953                 /* 
7954                     [AS] Protect the thinkOutput buffer from overflow... this
7955                     is only useful if buf1 hasn't overflowed first!
7956                 */
7957                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7958                         plylev, 
7959                         (gameMode == TwoMachinesPlay ?
7960                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7961                         ((double) curscore) / 100.0,
7962                         prefixHint ? lastHint : "",
7963                         prefixHint ? " " : "" );
7964
7965                 if( buf1[0] != NULLCHAR ) {
7966                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7967
7968                     if( strlen(buf1) > max_len ) {
7969                         if( appData.debugMode) {
7970                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7971                         }
7972                         buf1[max_len+1] = '\0';
7973                     }
7974
7975                     strcat( thinkOutput, buf1 );
7976                 }
7977
7978                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7979                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7980                     DisplayMove(currentMove - 1);
7981                 }
7982                 return;
7983
7984             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7985                 /* crafty (9.25+) says "(only move) <move>"
7986                  * if there is only 1 legal move
7987                  */
7988                 sscanf(p, "(only move) %s", buf1);
7989                 sprintf(thinkOutput, "%s (only move)", buf1);
7990                 sprintf(programStats.movelist, "%s (only move)", buf1);
7991                 programStats.depth = 1;
7992                 programStats.nr_moves = 1;
7993                 programStats.moves_left = 1;
7994                 programStats.nodes = 1;
7995                 programStats.time = 1;
7996                 programStats.got_only_move = 1;
7997
7998                 /* Not really, but we also use this member to
7999                    mean "line isn't going to change" (Crafty
8000                    isn't searching, so stats won't change) */
8001                 programStats.line_is_book = 1;
8002
8003                 SendProgramStatsToFrontend( cps, &programStats );
8004                 
8005                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
8006                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8007                     DisplayMove(currentMove - 1);
8008                 }
8009                 return;
8010             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8011                               &time, &nodes, &plylev, &mvleft,
8012                               &mvtot, mvname) >= 5) {
8013                 /* The stat01: line is from Crafty (9.29+) in response
8014                    to the "." command */
8015                 programStats.seen_stat = 1;
8016                 cps->maybeThinking = TRUE;
8017
8018                 if (programStats.got_only_move || !appData.periodicUpdates)
8019                   return;
8020
8021                 programStats.depth = plylev;
8022                 programStats.time = time;
8023                 programStats.nodes = nodes;
8024                 programStats.moves_left = mvleft;
8025                 programStats.nr_moves = mvtot;
8026                 strcpy(programStats.move_name, mvname);
8027                 programStats.ok_to_send = 1;
8028                 programStats.movelist[0] = '\0';
8029
8030                 SendProgramStatsToFrontend( cps, &programStats );
8031
8032                 return;
8033
8034             } else if (strncmp(message,"++",2) == 0) {
8035                 /* Crafty 9.29+ outputs this */
8036                 programStats.got_fail = 2;
8037                 return;
8038
8039             } else if (strncmp(message,"--",2) == 0) {
8040                 /* Crafty 9.29+ outputs this */
8041                 programStats.got_fail = 1;
8042                 return;
8043
8044             } else if (thinkOutput[0] != NULLCHAR &&
8045                        strncmp(message, "    ", 4) == 0) {
8046                 unsigned message_len;
8047
8048                 p = message;
8049                 while (*p && *p == ' ') p++;
8050
8051                 message_len = strlen( p );
8052
8053                 /* [AS] Avoid buffer overflow */
8054                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8055                     strcat(thinkOutput, " ");
8056                     strcat(thinkOutput, p);
8057                 }
8058
8059                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8060                     strcat(programStats.movelist, " ");
8061                     strcat(programStats.movelist, p);
8062                 }
8063
8064                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8065                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8066                     DisplayMove(currentMove - 1);
8067                 }
8068                 return;
8069             }
8070         }
8071         else {
8072             buf1[0] = NULLCHAR;
8073
8074             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8075                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8076             {
8077                 ChessProgramStats cpstats;
8078
8079                 if (plyext != ' ' && plyext != '\t') {
8080                     time *= 100;
8081                 }
8082
8083                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8084                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8085                     curscore = -curscore;
8086                 }
8087
8088                 cpstats.depth = plylev;
8089                 cpstats.nodes = nodes;
8090                 cpstats.time = time;
8091                 cpstats.score = curscore;
8092                 cpstats.got_only_move = 0;
8093                 cpstats.movelist[0] = '\0';
8094
8095                 if (buf1[0] != NULLCHAR) {
8096                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8097                 }
8098
8099                 cpstats.ok_to_send = 0;
8100                 cpstats.line_is_book = 0;
8101                 cpstats.nr_moves = 0;
8102                 cpstats.moves_left = 0;
8103
8104                 SendProgramStatsToFrontend( cps, &cpstats );
8105             }
8106         }
8107     }
8108 }
8109
8110
8111 /* Parse a game score from the character string "game", and
8112    record it as the history of the current game.  The game
8113    score is NOT assumed to start from the standard position. 
8114    The display is not updated in any way.
8115    */
8116 void
8117 ParseGameHistory(game)
8118      char *game;
8119 {
8120     ChessMove moveType;
8121     int fromX, fromY, toX, toY, boardIndex;
8122     char promoChar;
8123     char *p, *q;
8124     char buf[MSG_SIZ];
8125
8126     if (appData.debugMode)
8127       fprintf(debugFP, "Parsing game history: %s\n", game);
8128
8129     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8130     gameInfo.site = StrSave(appData.icsHost);
8131     gameInfo.date = PGNDate();
8132     gameInfo.round = StrSave("-");
8133
8134     /* Parse out names of players */
8135     while (*game == ' ') game++;
8136     p = buf;
8137     while (*game != ' ') *p++ = *game++;
8138     *p = NULLCHAR;
8139     gameInfo.white = StrSave(buf);
8140     while (*game == ' ') game++;
8141     p = buf;
8142     while (*game != ' ' && *game != '\n') *p++ = *game++;
8143     *p = NULLCHAR;
8144     gameInfo.black = StrSave(buf);
8145
8146     /* Parse moves */
8147     boardIndex = blackPlaysFirst ? 1 : 0;
8148     yynewstr(game);
8149     for (;;) {
8150         yyboardindex = boardIndex;
8151         moveType = (ChessMove) yylex();
8152         switch (moveType) {
8153           case IllegalMove:             /* maybe suicide chess, etc. */
8154   if (appData.debugMode) {
8155     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8156     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8157     setbuf(debugFP, NULL);
8158   }
8159           case WhitePromotionChancellor:
8160           case BlackPromotionChancellor:
8161           case WhitePromotionArchbishop:
8162           case BlackPromotionArchbishop:
8163           case WhitePromotionQueen:
8164           case BlackPromotionQueen:
8165           case WhitePromotionRook:
8166           case BlackPromotionRook:
8167           case WhitePromotionBishop:
8168           case BlackPromotionBishop:
8169           case WhitePromotionKnight:
8170           case BlackPromotionKnight:
8171           case WhitePromotionKing:
8172           case BlackPromotionKing:
8173           case NormalMove:
8174           case WhiteCapturesEnPassant:
8175           case BlackCapturesEnPassant:
8176           case WhiteKingSideCastle:
8177           case WhiteQueenSideCastle:
8178           case BlackKingSideCastle:
8179           case BlackQueenSideCastle:
8180           case WhiteKingSideCastleWild:
8181           case WhiteQueenSideCastleWild:
8182           case BlackKingSideCastleWild:
8183           case BlackQueenSideCastleWild:
8184           /* PUSH Fabien */
8185           case WhiteHSideCastleFR:
8186           case WhiteASideCastleFR:
8187           case BlackHSideCastleFR:
8188           case BlackASideCastleFR:
8189           /* POP Fabien */
8190             fromX = currentMoveString[0] - AAA;
8191             fromY = currentMoveString[1] - ONE;
8192             toX = currentMoveString[2] - AAA;
8193             toY = currentMoveString[3] - ONE;
8194             promoChar = currentMoveString[4];
8195             break;
8196           case WhiteDrop:
8197           case BlackDrop:
8198             fromX = moveType == WhiteDrop ?
8199               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8200             (int) CharToPiece(ToLower(currentMoveString[0]));
8201             fromY = DROP_RANK;
8202             toX = currentMoveString[2] - AAA;
8203             toY = currentMoveString[3] - ONE;
8204             promoChar = NULLCHAR;
8205             break;
8206           case AmbiguousMove:
8207             /* bug? */
8208             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8209   if (appData.debugMode) {
8210     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8211     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8212     setbuf(debugFP, NULL);
8213   }
8214             DisplayError(buf, 0);
8215             return;
8216           case ImpossibleMove:
8217             /* bug? */
8218             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8219   if (appData.debugMode) {
8220     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8221     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8222     setbuf(debugFP, NULL);
8223   }
8224             DisplayError(buf, 0);
8225             return;
8226           case (ChessMove) 0:   /* end of file */
8227             if (boardIndex < backwardMostMove) {
8228                 /* Oops, gap.  How did that happen? */
8229                 DisplayError(_("Gap in move list"), 0);
8230                 return;
8231             }
8232             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8233             if (boardIndex > forwardMostMove) {
8234                 forwardMostMove = boardIndex;
8235             }
8236             return;
8237           case ElapsedTime:
8238             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8239                 strcat(parseList[boardIndex-1], " ");
8240                 strcat(parseList[boardIndex-1], yy_text);
8241             }
8242             continue;
8243           case Comment:
8244           case PGNTag:
8245           case NAG:
8246           default:
8247             /* ignore */
8248             continue;
8249           case WhiteWins:
8250           case BlackWins:
8251           case GameIsDrawn:
8252           case GameUnfinished:
8253             if (gameMode == IcsExamining) {
8254                 if (boardIndex < backwardMostMove) {
8255                     /* Oops, gap.  How did that happen? */
8256                     return;
8257                 }
8258                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8259                 return;
8260             }
8261             gameInfo.result = moveType;
8262             p = strchr(yy_text, '{');
8263             if (p == NULL) p = strchr(yy_text, '(');
8264             if (p == NULL) {
8265                 p = yy_text;
8266                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8267             } else {
8268                 q = strchr(p, *p == '{' ? '}' : ')');
8269                 if (q != NULL) *q = NULLCHAR;
8270                 p++;
8271             }
8272             gameInfo.resultDetails = StrSave(p);
8273             continue;
8274         }
8275         if (boardIndex >= forwardMostMove &&
8276             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8277             backwardMostMove = blackPlaysFirst ? 1 : 0;
8278             return;
8279         }
8280         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8281                                  fromY, fromX, toY, toX, promoChar,
8282                                  parseList[boardIndex]);
8283         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8284         /* currentMoveString is set as a side-effect of yylex */
8285         strcpy(moveList[boardIndex], currentMoveString);
8286         strcat(moveList[boardIndex], "\n");
8287         boardIndex++;
8288         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8289         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8290           case MT_NONE:
8291           case MT_STALEMATE:
8292           default:
8293             break;
8294           case MT_CHECK:
8295             if(gameInfo.variant != VariantShogi)
8296                 strcat(parseList[boardIndex - 1], "+");
8297             break;
8298           case MT_CHECKMATE:
8299           case MT_STAINMATE:
8300             strcat(parseList[boardIndex - 1], "#");
8301             break;
8302         }
8303     }
8304 }
8305
8306
8307 /* Apply a move to the given board  */
8308 void
8309 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8310      int fromX, fromY, toX, toY;
8311      int promoChar;
8312      Board board;
8313 {
8314   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8315   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8316
8317     /* [HGM] compute & store e.p. status and castling rights for new position */
8318     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8319     { int i;
8320
8321       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8322       oldEP = (signed char)board[EP_STATUS];
8323       board[EP_STATUS] = EP_NONE;
8324
8325       if( board[toY][toX] != EmptySquare ) 
8326            board[EP_STATUS] = EP_CAPTURE;  
8327
8328       if( board[fromY][fromX] == WhitePawn ) {
8329            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8330                board[EP_STATUS] = EP_PAWN_MOVE;
8331            if( toY-fromY==2) {
8332                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8333                         gameInfo.variant != VariantBerolina || toX < fromX)
8334                       board[EP_STATUS] = toX | berolina;
8335                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8336                         gameInfo.variant != VariantBerolina || toX > fromX) 
8337                       board[EP_STATUS] = toX;
8338            }
8339       } else 
8340       if( board[fromY][fromX] == BlackPawn ) {
8341            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8342                board[EP_STATUS] = EP_PAWN_MOVE; 
8343            if( toY-fromY== -2) {
8344                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8345                         gameInfo.variant != VariantBerolina || toX < fromX)
8346                       board[EP_STATUS] = toX | berolina;
8347                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8348                         gameInfo.variant != VariantBerolina || toX > fromX) 
8349                       board[EP_STATUS] = toX;
8350            }
8351        }
8352
8353        for(i=0; i<nrCastlingRights; i++) {
8354            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8355               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8356              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8357        }
8358
8359     }
8360
8361   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8362   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8363        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8364          
8365   if (fromX == toX && fromY == toY) return;
8366
8367   if (fromY == DROP_RANK) {
8368         /* must be first */
8369         piece = board[toY][toX] = (ChessSquare) fromX;
8370   } else {
8371      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8372      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8373      if(gameInfo.variant == VariantKnightmate)
8374          king += (int) WhiteUnicorn - (int) WhiteKing;
8375
8376     /* Code added by Tord: */
8377     /* FRC castling assumed when king captures friendly rook. */
8378     if (board[fromY][fromX] == WhiteKing &&
8379              board[toY][toX] == WhiteRook) {
8380       board[fromY][fromX] = EmptySquare;
8381       board[toY][toX] = EmptySquare;
8382       if(toX > fromX) {
8383         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8384       } else {
8385         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8386       }
8387     } else if (board[fromY][fromX] == BlackKing &&
8388                board[toY][toX] == BlackRook) {
8389       board[fromY][fromX] = EmptySquare;
8390       board[toY][toX] = EmptySquare;
8391       if(toX > fromX) {
8392         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8393       } else {
8394         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8395       }
8396     /* End of code added by Tord */
8397
8398     } else if (board[fromY][fromX] == king
8399         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8400         && toY == fromY && toX > fromX+1) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = king;
8403         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8404         board[fromY][BOARD_RGHT-1] = EmptySquare;
8405     } else if (board[fromY][fromX] == king
8406         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8407                && toY == fromY && toX < fromX-1) {
8408         board[fromY][fromX] = EmptySquare;
8409         board[toY][toX] = king;
8410         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8411         board[fromY][BOARD_LEFT] = EmptySquare;
8412     } else if (board[fromY][fromX] == WhitePawn
8413                && toY >= BOARD_HEIGHT-promoRank
8414                && gameInfo.variant != VariantXiangqi
8415                ) {
8416         /* white pawn promotion */
8417         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8418         if (board[toY][toX] == EmptySquare) {
8419             board[toY][toX] = WhiteQueen;
8420         }
8421         if(gameInfo.variant==VariantBughouse ||
8422            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8423             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8424         board[fromY][fromX] = EmptySquare;
8425     } else if ((fromY == BOARD_HEIGHT-4)
8426                && (toX != fromX)
8427                && gameInfo.variant != VariantXiangqi
8428                && gameInfo.variant != VariantBerolina
8429                && (board[fromY][fromX] == WhitePawn)
8430                && (board[toY][toX] == EmptySquare)) {
8431         board[fromY][fromX] = EmptySquare;
8432         board[toY][toX] = WhitePawn;
8433         captured = board[toY - 1][toX];
8434         board[toY - 1][toX] = EmptySquare;
8435     } else if ((fromY == BOARD_HEIGHT-4)
8436                && (toX == fromX)
8437                && gameInfo.variant == VariantBerolina
8438                && (board[fromY][fromX] == WhitePawn)
8439                && (board[toY][toX] == EmptySquare)) {
8440         board[fromY][fromX] = EmptySquare;
8441         board[toY][toX] = WhitePawn;
8442         if(oldEP & EP_BEROLIN_A) {
8443                 captured = board[fromY][fromX-1];
8444                 board[fromY][fromX-1] = EmptySquare;
8445         }else{  captured = board[fromY][fromX+1];
8446                 board[fromY][fromX+1] = EmptySquare;
8447         }
8448     } else if (board[fromY][fromX] == king
8449         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8450                && toY == fromY && toX > fromX+1) {
8451         board[fromY][fromX] = EmptySquare;
8452         board[toY][toX] = king;
8453         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8454         board[fromY][BOARD_RGHT-1] = EmptySquare;
8455     } else if (board[fromY][fromX] == king
8456         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8457                && toY == fromY && toX < fromX-1) {
8458         board[fromY][fromX] = EmptySquare;
8459         board[toY][toX] = king;
8460         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8461         board[fromY][BOARD_LEFT] = EmptySquare;
8462     } else if (fromY == 7 && fromX == 3
8463                && board[fromY][fromX] == BlackKing
8464                && toY == 7 && toX == 5) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackKing;
8467         board[fromY][7] = EmptySquare;
8468         board[toY][4] = BlackRook;
8469     } else if (fromY == 7 && fromX == 3
8470                && board[fromY][fromX] == BlackKing
8471                && toY == 7 && toX == 1) {
8472         board[fromY][fromX] = EmptySquare;
8473         board[toY][toX] = BlackKing;
8474         board[fromY][0] = EmptySquare;
8475         board[toY][2] = BlackRook;
8476     } else if (board[fromY][fromX] == BlackPawn
8477                && toY < promoRank
8478                && gameInfo.variant != VariantXiangqi
8479                ) {
8480         /* black pawn promotion */
8481         board[toY][toX] = CharToPiece(ToLower(promoChar));
8482         if (board[toY][toX] == EmptySquare) {
8483             board[toY][toX] = BlackQueen;
8484         }
8485         if(gameInfo.variant==VariantBughouse ||
8486            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8487             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8488         board[fromY][fromX] = EmptySquare;
8489     } else if ((fromY == 3)
8490                && (toX != fromX)
8491                && gameInfo.variant != VariantXiangqi
8492                && gameInfo.variant != VariantBerolina
8493                && (board[fromY][fromX] == BlackPawn)
8494                && (board[toY][toX] == EmptySquare)) {
8495         board[fromY][fromX] = EmptySquare;
8496         board[toY][toX] = BlackPawn;
8497         captured = board[toY + 1][toX];
8498         board[toY + 1][toX] = EmptySquare;
8499     } else if ((fromY == 3)
8500                && (toX == fromX)
8501                && gameInfo.variant == VariantBerolina
8502                && (board[fromY][fromX] == BlackPawn)
8503                && (board[toY][toX] == EmptySquare)) {
8504         board[fromY][fromX] = EmptySquare;
8505         board[toY][toX] = BlackPawn;
8506         if(oldEP & EP_BEROLIN_A) {
8507                 captured = board[fromY][fromX-1];
8508                 board[fromY][fromX-1] = EmptySquare;
8509         }else{  captured = board[fromY][fromX+1];
8510                 board[fromY][fromX+1] = EmptySquare;
8511         }
8512     } else {
8513         board[toY][toX] = board[fromY][fromX];
8514         board[fromY][fromX] = EmptySquare;
8515     }
8516
8517     /* [HGM] now we promote for Shogi, if needed */
8518     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8519         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8520   }
8521
8522     if (gameInfo.holdingsWidth != 0) {
8523
8524       /* !!A lot more code needs to be written to support holdings  */
8525       /* [HGM] OK, so I have written it. Holdings are stored in the */
8526       /* penultimate board files, so they are automaticlly stored   */
8527       /* in the game history.                                       */
8528       if (fromY == DROP_RANK) {
8529         /* Delete from holdings, by decreasing count */
8530         /* and erasing image if necessary            */
8531         p = (int) fromX;
8532         if(p < (int) BlackPawn) { /* white drop */
8533              p -= (int)WhitePawn;
8534                  p = PieceToNumber((ChessSquare)p);
8535              if(p >= gameInfo.holdingsSize) p = 0;
8536              if(--board[p][BOARD_WIDTH-2] <= 0)
8537                   board[p][BOARD_WIDTH-1] = EmptySquare;
8538              if((int)board[p][BOARD_WIDTH-2] < 0)
8539                         board[p][BOARD_WIDTH-2] = 0;
8540         } else {                  /* black drop */
8541              p -= (int)BlackPawn;
8542                  p = PieceToNumber((ChessSquare)p);
8543              if(p >= gameInfo.holdingsSize) p = 0;
8544              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8545                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8546              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8547                         board[BOARD_HEIGHT-1-p][1] = 0;
8548         }
8549       }
8550       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8551           && gameInfo.variant != VariantBughouse        ) {
8552         /* [HGM] holdings: Add to holdings, if holdings exist */
8553         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8554                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8555                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8556         }
8557         p = (int) captured;
8558         if (p >= (int) BlackPawn) {
8559           p -= (int)BlackPawn;
8560           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8561                   /* in Shogi restore piece to its original  first */
8562                   captured = (ChessSquare) (DEMOTED captured);
8563                   p = DEMOTED p;
8564           }
8565           p = PieceToNumber((ChessSquare)p);
8566           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8567           board[p][BOARD_WIDTH-2]++;
8568           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8569         } else {
8570           p -= (int)WhitePawn;
8571           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8572                   captured = (ChessSquare) (DEMOTED captured);
8573                   p = DEMOTED p;
8574           }
8575           p = PieceToNumber((ChessSquare)p);
8576           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8577           board[BOARD_HEIGHT-1-p][1]++;
8578           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8579         }
8580       }
8581     } else if (gameInfo.variant == VariantAtomic) {
8582       if (captured != EmptySquare) {
8583         int y, x;
8584         for (y = toY-1; y <= toY+1; y++) {
8585           for (x = toX-1; x <= toX+1; x++) {
8586             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8587                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8588               board[y][x] = EmptySquare;
8589             }
8590           }
8591         }
8592         board[toY][toX] = EmptySquare;
8593       }
8594     }
8595     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8596         /* [HGM] Shogi promotions */
8597         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8598     }
8599
8600     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8601                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8602         // [HGM] superchess: take promotion piece out of holdings
8603         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8604         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8605             if(!--board[k][BOARD_WIDTH-2])
8606                 board[k][BOARD_WIDTH-1] = EmptySquare;
8607         } else {
8608             if(!--board[BOARD_HEIGHT-1-k][1])
8609                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8610         }
8611     }
8612
8613 }
8614
8615 /* Updates forwardMostMove */
8616 void
8617 MakeMove(fromX, fromY, toX, toY, promoChar)
8618      int fromX, fromY, toX, toY;
8619      int promoChar;
8620 {
8621 //    forwardMostMove++; // [HGM] bare: moved downstream
8622
8623     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8624         int timeLeft; static int lastLoadFlag=0; int king, piece;
8625         piece = boards[forwardMostMove][fromY][fromX];
8626         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8627         if(gameInfo.variant == VariantKnightmate)
8628             king += (int) WhiteUnicorn - (int) WhiteKing;
8629         if(forwardMostMove == 0) {
8630             if(blackPlaysFirst) 
8631                 fprintf(serverMoves, "%s;", second.tidy);
8632             fprintf(serverMoves, "%s;", first.tidy);
8633             if(!blackPlaysFirst) 
8634                 fprintf(serverMoves, "%s;", second.tidy);
8635         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8636         lastLoadFlag = loadFlag;
8637         // print base move
8638         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8639         // print castling suffix
8640         if( toY == fromY && piece == king ) {
8641             if(toX-fromX > 1)
8642                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8643             if(fromX-toX >1)
8644                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8645         }
8646         // e.p. suffix
8647         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8648              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8649              boards[forwardMostMove][toY][toX] == EmptySquare
8650              && fromX != toX && fromY != toY)
8651                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8652         // promotion suffix
8653         if(promoChar != NULLCHAR)
8654                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8655         if(!loadFlag) {
8656             fprintf(serverMoves, "/%d/%d",
8657                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8658             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8659             else                      timeLeft = blackTimeRemaining/1000;
8660             fprintf(serverMoves, "/%d", timeLeft);
8661         }
8662         fflush(serverMoves);
8663     }
8664
8665     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8666       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8667                         0, 1);
8668       return;
8669     }
8670     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8671     if (commentList[forwardMostMove+1] != NULL) {
8672         free(commentList[forwardMostMove+1]);
8673         commentList[forwardMostMove+1] = NULL;
8674     }
8675     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8676     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8677     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8678     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8679     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8680     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8681     gameInfo.result = GameUnfinished;
8682     if (gameInfo.resultDetails != NULL) {
8683         free(gameInfo.resultDetails);
8684         gameInfo.resultDetails = NULL;
8685     }
8686     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8687                               moveList[forwardMostMove - 1]);
8688     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8689                              PosFlags(forwardMostMove - 1),
8690                              fromY, fromX, toY, toX, promoChar,
8691                              parseList[forwardMostMove - 1]);
8692     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8693       case MT_NONE:
8694       case MT_STALEMATE:
8695       default:
8696         break;
8697       case MT_CHECK:
8698         if(gameInfo.variant != VariantShogi)
8699             strcat(parseList[forwardMostMove - 1], "+");
8700         break;
8701       case MT_CHECKMATE:
8702       case MT_STAINMATE:
8703         strcat(parseList[forwardMostMove - 1], "#");
8704         break;
8705     }
8706     if (appData.debugMode) {
8707         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8708     }
8709
8710 }
8711
8712 /* Updates currentMove if not pausing */
8713 void
8714 ShowMove(fromX, fromY, toX, toY)
8715 {
8716     int instant = (gameMode == PlayFromGameFile) ?
8717         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8718     if(appData.noGUI) return;
8719     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8720         if (!instant) {
8721             if (forwardMostMove == currentMove + 1) {
8722                 AnimateMove(boards[forwardMostMove - 1],
8723                             fromX, fromY, toX, toY);
8724             }
8725             if (appData.highlightLastMove) {
8726                 SetHighlights(fromX, fromY, toX, toY);
8727             }
8728         }
8729         currentMove = forwardMostMove;
8730     }
8731
8732     if (instant) return;
8733
8734     DisplayMove(currentMove - 1);
8735     DrawPosition(FALSE, boards[currentMove]);
8736     DisplayBothClocks();
8737     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8738 }
8739
8740 void SendEgtPath(ChessProgramState *cps)
8741 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8742         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8743
8744         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8745
8746         while(*p) {
8747             char c, *q = name+1, *r, *s;
8748
8749             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8750             while(*p && *p != ',') *q++ = *p++;
8751             *q++ = ':'; *q = 0;
8752             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8753                 strcmp(name, ",nalimov:") == 0 ) {
8754                 // take nalimov path from the menu-changeable option first, if it is defined
8755                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8756                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8757             } else
8758             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8759                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8760                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8761                 s = r = StrStr(s, ":") + 1; // beginning of path info
8762                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8763                 c = *r; *r = 0;             // temporarily null-terminate path info
8764                     *--q = 0;               // strip of trailig ':' from name
8765                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8766                 *r = c;
8767                 SendToProgram(buf,cps);     // send egtbpath command for this format
8768             }
8769             if(*p == ',') p++; // read away comma to position for next format name
8770         }
8771 }
8772
8773 void
8774 InitChessProgram(cps, setup)
8775      ChessProgramState *cps;
8776      int setup; /* [HGM] needed to setup FRC opening position */
8777 {
8778     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8779     if (appData.noChessProgram) return;
8780     hintRequested = FALSE;
8781     bookRequested = FALSE;
8782
8783     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8784     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8785     if(cps->memSize) { /* [HGM] memory */
8786         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8787         SendToProgram(buf, cps);
8788     }
8789     SendEgtPath(cps); /* [HGM] EGT */
8790     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8791         sprintf(buf, "cores %d\n", appData.smpCores);
8792         SendToProgram(buf, cps);
8793     }
8794
8795     SendToProgram(cps->initString, cps);
8796     if (gameInfo.variant != VariantNormal &&
8797         gameInfo.variant != VariantLoadable
8798         /* [HGM] also send variant if board size non-standard */
8799         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8800                                             ) {
8801       char *v = VariantName(gameInfo.variant);
8802       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8803         /* [HGM] in protocol 1 we have to assume all variants valid */
8804         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8805         DisplayFatalError(buf, 0, 1);
8806         return;
8807       }
8808
8809       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8810       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8811       if( gameInfo.variant == VariantXiangqi )
8812            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8813       if( gameInfo.variant == VariantShogi )
8814            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8815       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8816            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8817       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8818                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8819            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8820       if( gameInfo.variant == VariantCourier )
8821            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8822       if( gameInfo.variant == VariantSuper )
8823            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8824       if( gameInfo.variant == VariantGreat )
8825            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8826
8827       if(overruled) {
8828            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8829                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8830            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8831            if(StrStr(cps->variants, b) == NULL) { 
8832                // specific sized variant not known, check if general sizing allowed
8833                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8834                    if(StrStr(cps->variants, "boardsize") == NULL) {
8835                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8836                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8837                        DisplayFatalError(buf, 0, 1);
8838                        return;
8839                    }
8840                    /* [HGM] here we really should compare with the maximum supported board size */
8841                }
8842            }
8843       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8844       sprintf(buf, "variant %s\n", b);
8845       SendToProgram(buf, cps);
8846     }
8847     currentlyInitializedVariant = gameInfo.variant;
8848
8849     /* [HGM] send opening position in FRC to first engine */
8850     if(setup) {
8851           SendToProgram("force\n", cps);
8852           SendBoard(cps, 0);
8853           /* engine is now in force mode! Set flag to wake it up after first move. */
8854           setboardSpoiledMachineBlack = 1;
8855     }
8856
8857     if (cps->sendICS) {
8858       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8859       SendToProgram(buf, cps);
8860     }
8861     cps->maybeThinking = FALSE;
8862     cps->offeredDraw = 0;
8863     if (!appData.icsActive) {
8864         SendTimeControl(cps, movesPerSession, timeControl,
8865                         timeIncrement, appData.searchDepth,
8866                         searchTime);
8867     }
8868     if (appData.showThinking 
8869         // [HGM] thinking: four options require thinking output to be sent
8870         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8871                                 ) {
8872         SendToProgram("post\n", cps);
8873     }
8874     SendToProgram("hard\n", cps);
8875     if (!appData.ponderNextMove) {
8876         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8877            it without being sure what state we are in first.  "hard"
8878            is not a toggle, so that one is OK.
8879          */
8880         SendToProgram("easy\n", cps);
8881     }
8882     if (cps->usePing) {
8883       sprintf(buf, "ping %d\n", ++cps->lastPing);
8884       SendToProgram(buf, cps);
8885     }
8886     cps->initDone = TRUE;
8887 }   
8888
8889
8890 void
8891 StartChessProgram(cps)
8892      ChessProgramState *cps;
8893 {
8894     char buf[MSG_SIZ];
8895     int err;
8896
8897     if (appData.noChessProgram) return;
8898     cps->initDone = FALSE;
8899
8900     if (strcmp(cps->host, "localhost") == 0) {
8901         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8902     } else if (*appData.remoteShell == NULLCHAR) {
8903         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8904     } else {
8905         if (*appData.remoteUser == NULLCHAR) {
8906           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8907                     cps->program);
8908         } else {
8909           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8910                     cps->host, appData.remoteUser, cps->program);
8911         }
8912         err = StartChildProcess(buf, "", &cps->pr);
8913     }
8914     
8915     if (err != 0) {
8916         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8917         DisplayFatalError(buf, err, 1);
8918         cps->pr = NoProc;
8919         cps->isr = NULL;
8920         return;
8921     }
8922     
8923     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8924     if (cps->protocolVersion > 1) {
8925       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8926       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8927       cps->comboCnt = 0;  //                and values of combo boxes
8928       SendToProgram(buf, cps);
8929     } else {
8930       SendToProgram("xboard\n", cps);
8931     }
8932 }
8933
8934
8935 void
8936 TwoMachinesEventIfReady P((void))
8937 {
8938   if (first.lastPing != first.lastPong) {
8939     DisplayMessage("", _("Waiting for first chess program"));
8940     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8941     return;
8942   }
8943   if (second.lastPing != second.lastPong) {
8944     DisplayMessage("", _("Waiting for second chess program"));
8945     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8946     return;
8947   }
8948   ThawUI();
8949   TwoMachinesEvent();
8950 }
8951
8952 void
8953 NextMatchGame P((void))
8954 {
8955     int index; /* [HGM] autoinc: step load index during match */
8956     Reset(FALSE, TRUE);
8957     if (*appData.loadGameFile != NULLCHAR) {
8958         index = appData.loadGameIndex;
8959         if(index < 0) { // [HGM] autoinc
8960             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8961             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8962         } 
8963         LoadGameFromFile(appData.loadGameFile,
8964                          index,
8965                          appData.loadGameFile, FALSE);
8966     } else if (*appData.loadPositionFile != NULLCHAR) {
8967         index = appData.loadPositionIndex;
8968         if(index < 0) { // [HGM] autoinc
8969             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8970             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8971         } 
8972         LoadPositionFromFile(appData.loadPositionFile,
8973                              index,
8974                              appData.loadPositionFile);
8975     }
8976     TwoMachinesEventIfReady();
8977 }
8978
8979 void UserAdjudicationEvent( int result )
8980 {
8981     ChessMove gameResult = GameIsDrawn;
8982
8983     if( result > 0 ) {
8984         gameResult = WhiteWins;
8985     }
8986     else if( result < 0 ) {
8987         gameResult = BlackWins;
8988     }
8989
8990     if( gameMode == TwoMachinesPlay ) {
8991         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8992     }
8993 }
8994
8995
8996 // [HGM] save: calculate checksum of game to make games easily identifiable
8997 int StringCheckSum(char *s)
8998 {
8999         int i = 0;
9000         if(s==NULL) return 0;
9001         while(*s) i = i*259 + *s++;
9002         return i;
9003 }
9004
9005 int GameCheckSum()
9006 {
9007         int i, sum=0;
9008         for(i=backwardMostMove; i<forwardMostMove; i++) {
9009                 sum += pvInfoList[i].depth;
9010                 sum += StringCheckSum(parseList[i]);
9011                 sum += StringCheckSum(commentList[i]);
9012                 sum *= 261;
9013         }
9014         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9015         return sum + StringCheckSum(commentList[i]);
9016 } // end of save patch
9017
9018 void
9019 GameEnds(result, resultDetails, whosays)
9020      ChessMove result;
9021      char *resultDetails;
9022      int whosays;
9023 {
9024     GameMode nextGameMode;
9025     int isIcsGame;
9026     char buf[MSG_SIZ], popupRequested = 0;
9027
9028     if(endingGame) return; /* [HGM] crash: forbid recursion */
9029     endingGame = 1;
9030     if(twoBoards) { // [HGM] dual: switch back to one board
9031         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9032         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9033     }
9034     if (appData.debugMode) {
9035       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9036               result, resultDetails ? resultDetails : "(null)", whosays);
9037     }
9038
9039     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9040
9041     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9042         /* If we are playing on ICS, the server decides when the
9043            game is over, but the engine can offer to draw, claim 
9044            a draw, or resign. 
9045          */
9046 #if ZIPPY
9047         if (appData.zippyPlay && first.initDone) {
9048             if (result == GameIsDrawn) {
9049                 /* In case draw still needs to be claimed */
9050                 SendToICS(ics_prefix);
9051                 SendToICS("draw\n");
9052             } else if (StrCaseStr(resultDetails, "resign")) {
9053                 SendToICS(ics_prefix);
9054                 SendToICS("resign\n");
9055             }
9056         }
9057 #endif
9058         endingGame = 0; /* [HGM] crash */
9059         return;
9060     }
9061
9062     /* If we're loading the game from a file, stop */
9063     if (whosays == GE_FILE) {
9064       (void) StopLoadGameTimer();
9065       gameFileFP = NULL;
9066     }
9067
9068     /* Cancel draw offers */
9069     first.offeredDraw = second.offeredDraw = 0;
9070
9071     /* If this is an ICS game, only ICS can really say it's done;
9072        if not, anyone can. */
9073     isIcsGame = (gameMode == IcsPlayingWhite || 
9074                  gameMode == IcsPlayingBlack || 
9075                  gameMode == IcsObserving    || 
9076                  gameMode == IcsExamining);
9077
9078     if (!isIcsGame || whosays == GE_ICS) {
9079         /* OK -- not an ICS game, or ICS said it was done */
9080         StopClocks();
9081         if (!isIcsGame && !appData.noChessProgram) 
9082           SetUserThinkingEnables();
9083     
9084         /* [HGM] if a machine claims the game end we verify this claim */
9085         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9086             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9087                 char claimer;
9088                 ChessMove trueResult = (ChessMove) -1;
9089
9090                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9091                                             first.twoMachinesColor[0] :
9092                                             second.twoMachinesColor[0] ;
9093
9094                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9095                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9096                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9097                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9098                 } else
9099                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9100                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9101                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9102                 } else
9103                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9104                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9105                 }
9106
9107                 // now verify win claims, but not in drop games, as we don't understand those yet
9108                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9109                                                  || gameInfo.variant == VariantGreat) &&
9110                     (result == WhiteWins && claimer == 'w' ||
9111                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9112                       if (appData.debugMode) {
9113                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9114                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9115                       }
9116                       if(result != trueResult) {
9117                               sprintf(buf, "False win claim: '%s'", resultDetails);
9118                               result = claimer == 'w' ? BlackWins : WhiteWins;
9119                               resultDetails = buf;
9120                       }
9121                 } else
9122                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9123                     && (forwardMostMove <= backwardMostMove ||
9124                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9125                         (claimer=='b')==(forwardMostMove&1))
9126                                                                                   ) {
9127                       /* [HGM] verify: draws that were not flagged are false claims */
9128                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9129                       result = claimer == 'w' ? BlackWins : WhiteWins;
9130                       resultDetails = buf;
9131                 }
9132                 /* (Claiming a loss is accepted no questions asked!) */
9133             }
9134             /* [HGM] bare: don't allow bare King to win */
9135             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9136                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9137                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9138                && result != GameIsDrawn)
9139             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9140                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9141                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9142                         if(p >= 0 && p <= (int)WhiteKing) k++;
9143                 }
9144                 if (appData.debugMode) {
9145                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9146                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9147                 }
9148                 if(k <= 1) {
9149                         result = GameIsDrawn;
9150                         sprintf(buf, "%s but bare king", resultDetails);
9151                         resultDetails = buf;
9152                 }
9153             }
9154         }
9155
9156
9157         if(serverMoves != NULL && !loadFlag) { char c = '=';
9158             if(result==WhiteWins) c = '+';
9159             if(result==BlackWins) c = '-';
9160             if(resultDetails != NULL)
9161                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9162         }
9163         if (resultDetails != NULL) {
9164             gameInfo.result = result;
9165             gameInfo.resultDetails = StrSave(resultDetails);
9166
9167             /* display last move only if game was not loaded from file */
9168             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9169                 DisplayMove(currentMove - 1);
9170     
9171             if (forwardMostMove != 0) {
9172                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9173                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9174                                                                 ) {
9175                     if (*appData.saveGameFile != NULLCHAR) {
9176                         SaveGameToFile(appData.saveGameFile, TRUE);
9177                     } else if (appData.autoSaveGames) {
9178                         AutoSaveGame();
9179                     }
9180                     if (*appData.savePositionFile != NULLCHAR) {
9181                         SavePositionToFile(appData.savePositionFile);
9182                     }
9183                 }
9184             }
9185
9186             /* Tell program how game ended in case it is learning */
9187             /* [HGM] Moved this to after saving the PGN, just in case */
9188             /* engine died and we got here through time loss. In that */
9189             /* case we will get a fatal error writing the pipe, which */
9190             /* would otherwise lose us the PGN.                       */
9191             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9192             /* output during GameEnds should never be fatal anymore   */
9193             if (gameMode == MachinePlaysWhite ||
9194                 gameMode == MachinePlaysBlack ||
9195                 gameMode == TwoMachinesPlay ||
9196                 gameMode == IcsPlayingWhite ||
9197                 gameMode == IcsPlayingBlack ||
9198                 gameMode == BeginningOfGame) {
9199                 char buf[MSG_SIZ];
9200                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9201                         resultDetails);
9202                 if (first.pr != NoProc) {
9203                     SendToProgram(buf, &first);
9204                 }
9205                 if (second.pr != NoProc &&
9206                     gameMode == TwoMachinesPlay) {
9207                     SendToProgram(buf, &second);
9208                 }
9209             }
9210         }
9211
9212         if (appData.icsActive) {
9213             if (appData.quietPlay &&
9214                 (gameMode == IcsPlayingWhite ||
9215                  gameMode == IcsPlayingBlack)) {
9216                 SendToICS(ics_prefix);
9217                 SendToICS("set shout 1\n");
9218             }
9219             nextGameMode = IcsIdle;
9220             ics_user_moved = FALSE;
9221             /* clean up premove.  It's ugly when the game has ended and the
9222              * premove highlights are still on the board.
9223              */
9224             if (gotPremove) {
9225               gotPremove = FALSE;
9226               ClearPremoveHighlights();
9227               DrawPosition(FALSE, boards[currentMove]);
9228             }
9229             if (whosays == GE_ICS) {
9230                 switch (result) {
9231                 case WhiteWins:
9232                     if (gameMode == IcsPlayingWhite)
9233                         PlayIcsWinSound();
9234                     else if(gameMode == IcsPlayingBlack)
9235                         PlayIcsLossSound();
9236                     break;
9237                 case BlackWins:
9238                     if (gameMode == IcsPlayingBlack)
9239                         PlayIcsWinSound();
9240                     else if(gameMode == IcsPlayingWhite)
9241                         PlayIcsLossSound();
9242                     break;
9243                 case GameIsDrawn:
9244                     PlayIcsDrawSound();
9245                     break;
9246                 default:
9247                     PlayIcsUnfinishedSound();
9248                 }
9249             }
9250         } else if (gameMode == EditGame ||
9251                    gameMode == PlayFromGameFile || 
9252                    gameMode == AnalyzeMode || 
9253                    gameMode == AnalyzeFile) {
9254             nextGameMode = gameMode;
9255         } else {
9256             nextGameMode = EndOfGame;
9257         }
9258         pausing = FALSE;
9259         ModeHighlight();
9260     } else {
9261         nextGameMode = gameMode;
9262     }
9263
9264     if (appData.noChessProgram) {
9265         gameMode = nextGameMode;
9266         ModeHighlight();
9267         endingGame = 0; /* [HGM] crash */
9268         return;
9269     }
9270
9271     if (first.reuse) {
9272         /* Put first chess program into idle state */
9273         if (first.pr != NoProc &&
9274             (gameMode == MachinePlaysWhite ||
9275              gameMode == MachinePlaysBlack ||
9276              gameMode == TwoMachinesPlay ||
9277              gameMode == IcsPlayingWhite ||
9278              gameMode == IcsPlayingBlack ||
9279              gameMode == BeginningOfGame)) {
9280             SendToProgram("force\n", &first);
9281             if (first.usePing) {
9282               char buf[MSG_SIZ];
9283               sprintf(buf, "ping %d\n", ++first.lastPing);
9284               SendToProgram(buf, &first);
9285             }
9286         }
9287     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9288         /* Kill off first chess program */
9289         if (first.isr != NULL)
9290           RemoveInputSource(first.isr);
9291         first.isr = NULL;
9292     
9293         if (first.pr != NoProc) {
9294             ExitAnalyzeMode();
9295             DoSleep( appData.delayBeforeQuit );
9296             SendToProgram("quit\n", &first);
9297             DoSleep( appData.delayAfterQuit );
9298             DestroyChildProcess(first.pr, first.useSigterm);
9299         }
9300         first.pr = NoProc;
9301     }
9302     if (second.reuse) {
9303         /* Put second chess program into idle state */
9304         if (second.pr != NoProc &&
9305             gameMode == TwoMachinesPlay) {
9306             SendToProgram("force\n", &second);
9307             if (second.usePing) {
9308               char buf[MSG_SIZ];
9309               sprintf(buf, "ping %d\n", ++second.lastPing);
9310               SendToProgram(buf, &second);
9311             }
9312         }
9313     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9314         /* Kill off second chess program */
9315         if (second.isr != NULL)
9316           RemoveInputSource(second.isr);
9317         second.isr = NULL;
9318     
9319         if (second.pr != NoProc) {
9320             DoSleep( appData.delayBeforeQuit );
9321             SendToProgram("quit\n", &second);
9322             DoSleep( appData.delayAfterQuit );
9323             DestroyChildProcess(second.pr, second.useSigterm);
9324         }
9325         second.pr = NoProc;
9326     }
9327
9328     if (matchMode && gameMode == TwoMachinesPlay) {
9329         switch (result) {
9330         case WhiteWins:
9331           if (first.twoMachinesColor[0] == 'w') {
9332             first.matchWins++;
9333           } else {
9334             second.matchWins++;
9335           }
9336           break;
9337         case BlackWins:
9338           if (first.twoMachinesColor[0] == 'b') {
9339             first.matchWins++;
9340           } else {
9341             second.matchWins++;
9342           }
9343           break;
9344         default:
9345           break;
9346         }
9347         if (matchGame < appData.matchGames) {
9348             char *tmp;
9349             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9350                 tmp = first.twoMachinesColor;
9351                 first.twoMachinesColor = second.twoMachinesColor;
9352                 second.twoMachinesColor = tmp;
9353             }
9354             gameMode = nextGameMode;
9355             matchGame++;
9356             if(appData.matchPause>10000 || appData.matchPause<10)
9357                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9358             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9359             endingGame = 0; /* [HGM] crash */
9360             return;
9361         } else {
9362             gameMode = nextGameMode;
9363             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9364                     first.tidy, second.tidy,
9365                     first.matchWins, second.matchWins,
9366                     appData.matchGames - (first.matchWins + second.matchWins));
9367             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9368         }
9369     }
9370     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9371         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9372       ExitAnalyzeMode();
9373     gameMode = nextGameMode;
9374     ModeHighlight();
9375     endingGame = 0;  /* [HGM] crash */
9376     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9377       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9378         matchMode = FALSE; appData.matchGames = matchGame = 0;
9379         DisplayNote(buf);
9380       }
9381     }
9382 }
9383
9384 /* Assumes program was just initialized (initString sent).
9385    Leaves program in force mode. */
9386 void
9387 FeedMovesToProgram(cps, upto) 
9388      ChessProgramState *cps;
9389      int upto;
9390 {
9391     int i;
9392     
9393     if (appData.debugMode)
9394       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9395               startedFromSetupPosition ? "position and " : "",
9396               backwardMostMove, upto, cps->which);
9397     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9398         // [HGM] variantswitch: make engine aware of new variant
9399         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9400                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9401         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9402         SendToProgram(buf, cps);
9403         currentlyInitializedVariant = gameInfo.variant;
9404     }
9405     SendToProgram("force\n", cps);
9406     if (startedFromSetupPosition) {
9407         SendBoard(cps, backwardMostMove);
9408     if (appData.debugMode) {
9409         fprintf(debugFP, "feedMoves\n");
9410     }
9411     }
9412     for (i = backwardMostMove; i < upto; i++) {
9413         SendMoveToProgram(i, cps);
9414     }
9415 }
9416
9417
9418 void
9419 ResurrectChessProgram()
9420 {
9421      /* The chess program may have exited.
9422         If so, restart it and feed it all the moves made so far. */
9423
9424     if (appData.noChessProgram || first.pr != NoProc) return;
9425     
9426     StartChessProgram(&first);
9427     InitChessProgram(&first, FALSE);
9428     FeedMovesToProgram(&first, currentMove);
9429
9430     if (!first.sendTime) {
9431         /* can't tell gnuchess what its clock should read,
9432            so we bow to its notion. */
9433         ResetClocks();
9434         timeRemaining[0][currentMove] = whiteTimeRemaining;
9435         timeRemaining[1][currentMove] = blackTimeRemaining;
9436     }
9437
9438     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9439                 appData.icsEngineAnalyze) && first.analysisSupport) {
9440       SendToProgram("analyze\n", &first);
9441       first.analyzing = TRUE;
9442     }
9443 }
9444
9445 /*
9446  * Button procedures
9447  */
9448 void
9449 Reset(redraw, init)
9450      int redraw, init;
9451 {
9452     int i;
9453
9454     if (appData.debugMode) {
9455         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9456                 redraw, init, gameMode);
9457     }
9458     CleanupTail(); // [HGM] vari: delete any stored variations
9459     pausing = pauseExamInvalid = FALSE;
9460     startedFromSetupPosition = blackPlaysFirst = FALSE;
9461     firstMove = TRUE;
9462     whiteFlag = blackFlag = FALSE;
9463     userOfferedDraw = FALSE;
9464     hintRequested = bookRequested = FALSE;
9465     first.maybeThinking = FALSE;
9466     second.maybeThinking = FALSE;
9467     first.bookSuspend = FALSE; // [HGM] book
9468     second.bookSuspend = FALSE;
9469     thinkOutput[0] = NULLCHAR;
9470     lastHint[0] = NULLCHAR;
9471     ClearGameInfo(&gameInfo);
9472     gameInfo.variant = StringToVariant(appData.variant);
9473     ics_user_moved = ics_clock_paused = FALSE;
9474     ics_getting_history = H_FALSE;
9475     ics_gamenum = -1;
9476     white_holding[0] = black_holding[0] = NULLCHAR;
9477     ClearProgramStats();
9478     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9479     
9480     ResetFrontEnd();
9481     ClearHighlights();
9482     flipView = appData.flipView;
9483     ClearPremoveHighlights();
9484     gotPremove = FALSE;
9485     alarmSounded = FALSE;
9486
9487     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9488     if(appData.serverMovesName != NULL) {
9489         /* [HGM] prepare to make moves file for broadcasting */
9490         clock_t t = clock();
9491         if(serverMoves != NULL) fclose(serverMoves);
9492         serverMoves = fopen(appData.serverMovesName, "r");
9493         if(serverMoves != NULL) {
9494             fclose(serverMoves);
9495             /* delay 15 sec before overwriting, so all clients can see end */
9496             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9497         }
9498         serverMoves = fopen(appData.serverMovesName, "w");
9499     }
9500
9501     ExitAnalyzeMode();
9502     gameMode = BeginningOfGame;
9503     ModeHighlight();
9504     if(appData.icsActive) gameInfo.variant = VariantNormal;
9505     currentMove = forwardMostMove = backwardMostMove = 0;
9506     InitPosition(redraw);
9507     for (i = 0; i < MAX_MOVES; i++) {
9508         if (commentList[i] != NULL) {
9509             free(commentList[i]);
9510             commentList[i] = NULL;
9511         }
9512     }
9513     ResetClocks();
9514     timeRemaining[0][0] = whiteTimeRemaining;
9515     timeRemaining[1][0] = blackTimeRemaining;
9516     if (first.pr == NULL) {
9517         StartChessProgram(&first);
9518     }
9519     if (init) {
9520             InitChessProgram(&first, startedFromSetupPosition);
9521     }
9522     DisplayTitle("");
9523     DisplayMessage("", "");
9524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9525     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9526 }
9527
9528 void
9529 AutoPlayGameLoop()
9530 {
9531     for (;;) {
9532         if (!AutoPlayOneMove())
9533           return;
9534         if (matchMode || appData.timeDelay == 0)
9535           continue;
9536         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9537           return;
9538         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9539         break;
9540     }
9541 }
9542
9543
9544 int
9545 AutoPlayOneMove()
9546 {
9547     int fromX, fromY, toX, toY;
9548
9549     if (appData.debugMode) {
9550       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9551     }
9552
9553     if (gameMode != PlayFromGameFile)
9554       return FALSE;
9555
9556     if (currentMove >= forwardMostMove) {
9557       gameMode = EditGame;
9558       ModeHighlight();
9559
9560       /* [AS] Clear current move marker at the end of a game */
9561       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9562
9563       return FALSE;
9564     }
9565     
9566     toX = moveList[currentMove][2] - AAA;
9567     toY = moveList[currentMove][3] - ONE;
9568
9569     if (moveList[currentMove][1] == '@') {
9570         if (appData.highlightLastMove) {
9571             SetHighlights(-1, -1, toX, toY);
9572         }
9573     } else {
9574         fromX = moveList[currentMove][0] - AAA;
9575         fromY = moveList[currentMove][1] - ONE;
9576
9577         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9578
9579         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9580
9581         if (appData.highlightLastMove) {
9582             SetHighlights(fromX, fromY, toX, toY);
9583         }
9584     }
9585     DisplayMove(currentMove);
9586     SendMoveToProgram(currentMove++, &first);
9587     DisplayBothClocks();
9588     DrawPosition(FALSE, boards[currentMove]);
9589     // [HGM] PV info: always display, routine tests if empty
9590     DisplayComment(currentMove - 1, commentList[currentMove]);
9591     return TRUE;
9592 }
9593
9594
9595 int
9596 LoadGameOneMove(readAhead)
9597      ChessMove readAhead;
9598 {
9599     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9600     char promoChar = NULLCHAR;
9601     ChessMove moveType;
9602     char move[MSG_SIZ];
9603     char *p, *q;
9604     
9605     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9606         gameMode != AnalyzeMode && gameMode != Training) {
9607         gameFileFP = NULL;
9608         return FALSE;
9609     }
9610     
9611     yyboardindex = forwardMostMove;
9612     if (readAhead != (ChessMove)0) {
9613       moveType = readAhead;
9614     } else {
9615       if (gameFileFP == NULL)
9616           return FALSE;
9617       moveType = (ChessMove) yylex();
9618     }
9619     
9620     done = FALSE;
9621     switch (moveType) {
9622       case Comment:
9623         if (appData.debugMode) 
9624           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9625         p = yy_text;
9626
9627         /* append the comment but don't display it */
9628         AppendComment(currentMove, p, FALSE);
9629         return TRUE;
9630
9631       case WhiteCapturesEnPassant:
9632       case BlackCapturesEnPassant:
9633       case WhitePromotionChancellor:
9634       case BlackPromotionChancellor:
9635       case WhitePromotionArchbishop:
9636       case BlackPromotionArchbishop:
9637       case WhitePromotionCentaur:
9638       case BlackPromotionCentaur:
9639       case WhitePromotionQueen:
9640       case BlackPromotionQueen:
9641       case WhitePromotionRook:
9642       case BlackPromotionRook:
9643       case WhitePromotionBishop:
9644       case BlackPromotionBishop:
9645       case WhitePromotionKnight:
9646       case BlackPromotionKnight:
9647       case WhitePromotionKing:
9648       case BlackPromotionKing:
9649       case NormalMove:
9650       case WhiteKingSideCastle:
9651       case WhiteQueenSideCastle:
9652       case BlackKingSideCastle:
9653       case BlackQueenSideCastle:
9654       case WhiteKingSideCastleWild:
9655       case WhiteQueenSideCastleWild:
9656       case BlackKingSideCastleWild:
9657       case BlackQueenSideCastleWild:
9658       /* PUSH Fabien */
9659       case WhiteHSideCastleFR:
9660       case WhiteASideCastleFR:
9661       case BlackHSideCastleFR:
9662       case BlackASideCastleFR:
9663       /* POP Fabien */
9664         if (appData.debugMode)
9665           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9666         fromX = currentMoveString[0] - AAA;
9667         fromY = currentMoveString[1] - ONE;
9668         toX = currentMoveString[2] - AAA;
9669         toY = currentMoveString[3] - ONE;
9670         promoChar = currentMoveString[4];
9671         break;
9672
9673       case WhiteDrop:
9674       case BlackDrop:
9675         if (appData.debugMode)
9676           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9677         fromX = moveType == WhiteDrop ?
9678           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9679         (int) CharToPiece(ToLower(currentMoveString[0]));
9680         fromY = DROP_RANK;
9681         toX = currentMoveString[2] - AAA;
9682         toY = currentMoveString[3] - ONE;
9683         break;
9684
9685       case WhiteWins:
9686       case BlackWins:
9687       case GameIsDrawn:
9688       case GameUnfinished:
9689         if (appData.debugMode)
9690           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9691         p = strchr(yy_text, '{');
9692         if (p == NULL) p = strchr(yy_text, '(');
9693         if (p == NULL) {
9694             p = yy_text;
9695             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9696         } else {
9697             q = strchr(p, *p == '{' ? '}' : ')');
9698             if (q != NULL) *q = NULLCHAR;
9699             p++;
9700         }
9701         GameEnds(moveType, p, GE_FILE);
9702         done = TRUE;
9703         if (cmailMsgLoaded) {
9704             ClearHighlights();
9705             flipView = WhiteOnMove(currentMove);
9706             if (moveType == GameUnfinished) flipView = !flipView;
9707             if (appData.debugMode)
9708               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9709         }
9710         break;
9711
9712       case (ChessMove) 0:       /* end of file */
9713         if (appData.debugMode)
9714           fprintf(debugFP, "Parser hit end of file\n");
9715         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9716           case MT_NONE:
9717           case MT_CHECK:
9718             break;
9719           case MT_CHECKMATE:
9720           case MT_STAINMATE:
9721             if (WhiteOnMove(currentMove)) {
9722                 GameEnds(BlackWins, "Black mates", GE_FILE);
9723             } else {
9724                 GameEnds(WhiteWins, "White mates", GE_FILE);
9725             }
9726             break;
9727           case MT_STALEMATE:
9728             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9729             break;
9730         }
9731         done = TRUE;
9732         break;
9733
9734       case MoveNumberOne:
9735         if (lastLoadGameStart == GNUChessGame) {
9736             /* GNUChessGames have numbers, but they aren't move numbers */
9737             if (appData.debugMode)
9738               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9739                       yy_text, (int) moveType);
9740             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9741         }
9742         /* else fall thru */
9743
9744       case XBoardGame:
9745       case GNUChessGame:
9746       case PGNTag:
9747         /* Reached start of next game in file */
9748         if (appData.debugMode)
9749           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9750         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9751           case MT_NONE:
9752           case MT_CHECK:
9753             break;
9754           case MT_CHECKMATE:
9755           case MT_STAINMATE:
9756             if (WhiteOnMove(currentMove)) {
9757                 GameEnds(BlackWins, "Black mates", GE_FILE);
9758             } else {
9759                 GameEnds(WhiteWins, "White mates", GE_FILE);
9760             }
9761             break;
9762           case MT_STALEMATE:
9763             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9764             break;
9765         }
9766         done = TRUE;
9767         break;
9768
9769       case PositionDiagram:     /* should not happen; ignore */
9770       case ElapsedTime:         /* ignore */
9771       case NAG:                 /* ignore */
9772         if (appData.debugMode)
9773           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9774                   yy_text, (int) moveType);
9775         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9776
9777       case IllegalMove:
9778         if (appData.testLegality) {
9779             if (appData.debugMode)
9780               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9781             sprintf(move, _("Illegal move: %d.%s%s"),
9782                     (forwardMostMove / 2) + 1,
9783                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9784             DisplayError(move, 0);
9785             done = TRUE;
9786         } else {
9787             if (appData.debugMode)
9788               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9789                       yy_text, currentMoveString);
9790             fromX = currentMoveString[0] - AAA;
9791             fromY = currentMoveString[1] - ONE;
9792             toX = currentMoveString[2] - AAA;
9793             toY = currentMoveString[3] - ONE;
9794             promoChar = currentMoveString[4];
9795         }
9796         break;
9797
9798       case AmbiguousMove:
9799         if (appData.debugMode)
9800           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9801         sprintf(move, _("Ambiguous move: %d.%s%s"),
9802                 (forwardMostMove / 2) + 1,
9803                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9804         DisplayError(move, 0);
9805         done = TRUE;
9806         break;
9807
9808       default:
9809       case ImpossibleMove:
9810         if (appData.debugMode)
9811           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9812         sprintf(move, _("Illegal move: %d.%s%s"),
9813                 (forwardMostMove / 2) + 1,
9814                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9815         DisplayError(move, 0);
9816         done = TRUE;
9817         break;
9818     }
9819
9820     if (done) {
9821         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9822             DrawPosition(FALSE, boards[currentMove]);
9823             DisplayBothClocks();
9824             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9825               DisplayComment(currentMove - 1, commentList[currentMove]);
9826         }
9827         (void) StopLoadGameTimer();
9828         gameFileFP = NULL;
9829         cmailOldMove = forwardMostMove;
9830         return FALSE;
9831     } else {
9832         /* currentMoveString is set as a side-effect of yylex */
9833         strcat(currentMoveString, "\n");
9834         strcpy(moveList[forwardMostMove], currentMoveString);
9835         
9836         thinkOutput[0] = NULLCHAR;
9837         MakeMove(fromX, fromY, toX, toY, promoChar);
9838         currentMove = forwardMostMove;
9839         return TRUE;
9840     }
9841 }
9842
9843 /* Load the nth game from the given file */
9844 int
9845 LoadGameFromFile(filename, n, title, useList)
9846      char *filename;
9847      int n;
9848      char *title;
9849      /*Boolean*/ int useList;
9850 {
9851     FILE *f;
9852     char buf[MSG_SIZ];
9853
9854     if (strcmp(filename, "-") == 0) {
9855         f = stdin;
9856         title = "stdin";
9857     } else {
9858         f = fopen(filename, "rb");
9859         if (f == NULL) {
9860           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9861             DisplayError(buf, errno);
9862             return FALSE;
9863         }
9864     }
9865     if (fseek(f, 0, 0) == -1) {
9866         /* f is not seekable; probably a pipe */
9867         useList = FALSE;
9868     }
9869     if (useList && n == 0) {
9870         int error = GameListBuild(f);
9871         if (error) {
9872             DisplayError(_("Cannot build game list"), error);
9873         } else if (!ListEmpty(&gameList) &&
9874                    ((ListGame *) gameList.tailPred)->number > 1) {
9875             GameListPopUp(f, title);
9876             return TRUE;
9877         }
9878         GameListDestroy();
9879         n = 1;
9880     }
9881     if (n == 0) n = 1;
9882     return LoadGame(f, n, title, FALSE);
9883 }
9884
9885
9886 void
9887 MakeRegisteredMove()
9888 {
9889     int fromX, fromY, toX, toY;
9890     char promoChar;
9891     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9892         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9893           case CMAIL_MOVE:
9894           case CMAIL_DRAW:
9895             if (appData.debugMode)
9896               fprintf(debugFP, "Restoring %s for game %d\n",
9897                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9898     
9899             thinkOutput[0] = NULLCHAR;
9900             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9901             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9902             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9903             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9904             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9905             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9906             MakeMove(fromX, fromY, toX, toY, promoChar);
9907             ShowMove(fromX, fromY, toX, toY);
9908               
9909             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9910               case MT_NONE:
9911               case MT_CHECK:
9912                 break;
9913                 
9914               case MT_CHECKMATE:
9915               case MT_STAINMATE:
9916                 if (WhiteOnMove(currentMove)) {
9917                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9918                 } else {
9919                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9920                 }
9921                 break;
9922                 
9923               case MT_STALEMATE:
9924                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9925                 break;
9926             }
9927
9928             break;
9929             
9930           case CMAIL_RESIGN:
9931             if (WhiteOnMove(currentMove)) {
9932                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9933             } else {
9934                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9935             }
9936             break;
9937             
9938           case CMAIL_ACCEPT:
9939             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9940             break;
9941               
9942           default:
9943             break;
9944         }
9945     }
9946
9947     return;
9948 }
9949
9950 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9951 int
9952 CmailLoadGame(f, gameNumber, title, useList)
9953      FILE *f;
9954      int gameNumber;
9955      char *title;
9956      int useList;
9957 {
9958     int retVal;
9959
9960     if (gameNumber > nCmailGames) {
9961         DisplayError(_("No more games in this message"), 0);
9962         return FALSE;
9963     }
9964     if (f == lastLoadGameFP) {
9965         int offset = gameNumber - lastLoadGameNumber;
9966         if (offset == 0) {
9967             cmailMsg[0] = NULLCHAR;
9968             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9969                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9970                 nCmailMovesRegistered--;
9971             }
9972             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9973             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9974                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9975             }
9976         } else {
9977             if (! RegisterMove()) return FALSE;
9978         }
9979     }
9980
9981     retVal = LoadGame(f, gameNumber, title, useList);
9982
9983     /* Make move registered during previous look at this game, if any */
9984     MakeRegisteredMove();
9985
9986     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9987         commentList[currentMove]
9988           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9989         DisplayComment(currentMove - 1, commentList[currentMove]);
9990     }
9991
9992     return retVal;
9993 }
9994
9995 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9996 int
9997 ReloadGame(offset)
9998      int offset;
9999 {
10000     int gameNumber = lastLoadGameNumber + offset;
10001     if (lastLoadGameFP == NULL) {
10002         DisplayError(_("No game has been loaded yet"), 0);
10003         return FALSE;
10004     }
10005     if (gameNumber <= 0) {
10006         DisplayError(_("Can't back up any further"), 0);
10007         return FALSE;
10008     }
10009     if (cmailMsgLoaded) {
10010         return CmailLoadGame(lastLoadGameFP, gameNumber,
10011                              lastLoadGameTitle, lastLoadGameUseList);
10012     } else {
10013         return LoadGame(lastLoadGameFP, gameNumber,
10014                         lastLoadGameTitle, lastLoadGameUseList);
10015     }
10016 }
10017
10018
10019
10020 /* Load the nth game from open file f */
10021 int
10022 LoadGame(f, gameNumber, title, useList)
10023      FILE *f;
10024      int gameNumber;
10025      char *title;
10026      int useList;
10027 {
10028     ChessMove cm;
10029     char buf[MSG_SIZ];
10030     int gn = gameNumber;
10031     ListGame *lg = NULL;
10032     int numPGNTags = 0;
10033     int err;
10034     GameMode oldGameMode;
10035     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10036
10037     if (appData.debugMode) 
10038         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10039
10040     if (gameMode == Training )
10041         SetTrainingModeOff();
10042
10043     oldGameMode = gameMode;
10044     if (gameMode != BeginningOfGame) {
10045       Reset(FALSE, TRUE);
10046     }
10047
10048     gameFileFP = f;
10049     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10050         fclose(lastLoadGameFP);
10051     }
10052
10053     if (useList) {
10054         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10055         
10056         if (lg) {
10057             fseek(f, lg->offset, 0);
10058             GameListHighlight(gameNumber);
10059             gn = 1;
10060         }
10061         else {
10062             DisplayError(_("Game number out of range"), 0);
10063             return FALSE;
10064         }
10065     } else {
10066         GameListDestroy();
10067         if (fseek(f, 0, 0) == -1) {
10068             if (f == lastLoadGameFP ?
10069                 gameNumber == lastLoadGameNumber + 1 :
10070                 gameNumber == 1) {
10071                 gn = 1;
10072             } else {
10073                 DisplayError(_("Can't seek on game file"), 0);
10074                 return FALSE;
10075             }
10076         }
10077     }
10078     lastLoadGameFP = f;
10079     lastLoadGameNumber = gameNumber;
10080     strcpy(lastLoadGameTitle, title);
10081     lastLoadGameUseList = useList;
10082
10083     yynewfile(f);
10084
10085     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10086       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10087                 lg->gameInfo.black);
10088             DisplayTitle(buf);
10089     } else if (*title != NULLCHAR) {
10090         if (gameNumber > 1) {
10091             sprintf(buf, "%s %d", title, gameNumber);
10092             DisplayTitle(buf);
10093         } else {
10094             DisplayTitle(title);
10095         }
10096     }
10097
10098     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10099         gameMode = PlayFromGameFile;
10100         ModeHighlight();
10101     }
10102
10103     currentMove = forwardMostMove = backwardMostMove = 0;
10104     CopyBoard(boards[0], initialPosition);
10105     StopClocks();
10106
10107     /*
10108      * Skip the first gn-1 games in the file.
10109      * Also skip over anything that precedes an identifiable 
10110      * start of game marker, to avoid being confused by 
10111      * garbage at the start of the file.  Currently 
10112      * recognized start of game markers are the move number "1",
10113      * the pattern "gnuchess .* game", the pattern
10114      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10115      * A game that starts with one of the latter two patterns
10116      * will also have a move number 1, possibly
10117      * following a position diagram.
10118      * 5-4-02: Let's try being more lenient and allowing a game to
10119      * start with an unnumbered move.  Does that break anything?
10120      */
10121     cm = lastLoadGameStart = (ChessMove) 0;
10122     while (gn > 0) {
10123         yyboardindex = forwardMostMove;
10124         cm = (ChessMove) yylex();
10125         switch (cm) {
10126           case (ChessMove) 0:
10127             if (cmailMsgLoaded) {
10128                 nCmailGames = CMAIL_MAX_GAMES - gn;
10129             } else {
10130                 Reset(TRUE, TRUE);
10131                 DisplayError(_("Game not found in file"), 0);
10132             }
10133             return FALSE;
10134
10135           case GNUChessGame:
10136           case XBoardGame:
10137             gn--;
10138             lastLoadGameStart = cm;
10139             break;
10140             
10141           case MoveNumberOne:
10142             switch (lastLoadGameStart) {
10143               case GNUChessGame:
10144               case XBoardGame:
10145               case PGNTag:
10146                 break;
10147               case MoveNumberOne:
10148               case (ChessMove) 0:
10149                 gn--;           /* count this game */
10150                 lastLoadGameStart = cm;
10151                 break;
10152               default:
10153                 /* impossible */
10154                 break;
10155             }
10156             break;
10157
10158           case PGNTag:
10159             switch (lastLoadGameStart) {
10160               case GNUChessGame:
10161               case PGNTag:
10162               case MoveNumberOne:
10163               case (ChessMove) 0:
10164                 gn--;           /* count this game */
10165                 lastLoadGameStart = cm;
10166                 break;
10167               case XBoardGame:
10168                 lastLoadGameStart = cm; /* game counted already */
10169                 break;
10170               default:
10171                 /* impossible */
10172                 break;
10173             }
10174             if (gn > 0) {
10175                 do {
10176                     yyboardindex = forwardMostMove;
10177                     cm = (ChessMove) yylex();
10178                 } while (cm == PGNTag || cm == Comment);
10179             }
10180             break;
10181
10182           case WhiteWins:
10183           case BlackWins:
10184           case GameIsDrawn:
10185             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10186                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10187                     != CMAIL_OLD_RESULT) {
10188                     nCmailResults ++ ;
10189                     cmailResult[  CMAIL_MAX_GAMES
10190                                 - gn - 1] = CMAIL_OLD_RESULT;
10191                 }
10192             }
10193             break;
10194
10195           case NormalMove:
10196             /* Only a NormalMove can be at the start of a game
10197              * without a position diagram. */
10198             if (lastLoadGameStart == (ChessMove) 0) {
10199               gn--;
10200               lastLoadGameStart = MoveNumberOne;
10201             }
10202             break;
10203
10204           default:
10205             break;
10206         }
10207     }
10208     
10209     if (appData.debugMode)
10210       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10211
10212     if (cm == XBoardGame) {
10213         /* Skip any header junk before position diagram and/or move 1 */
10214         for (;;) {
10215             yyboardindex = forwardMostMove;
10216             cm = (ChessMove) yylex();
10217
10218             if (cm == (ChessMove) 0 ||
10219                 cm == GNUChessGame || cm == XBoardGame) {
10220                 /* Empty game; pretend end-of-file and handle later */
10221                 cm = (ChessMove) 0;
10222                 break;
10223             }
10224
10225             if (cm == MoveNumberOne || cm == PositionDiagram ||
10226                 cm == PGNTag || cm == Comment)
10227               break;
10228         }
10229     } else if (cm == GNUChessGame) {
10230         if (gameInfo.event != NULL) {
10231             free(gameInfo.event);
10232         }
10233         gameInfo.event = StrSave(yy_text);
10234     }   
10235
10236     startedFromSetupPosition = FALSE;
10237     while (cm == PGNTag) {
10238         if (appData.debugMode) 
10239           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10240         err = ParsePGNTag(yy_text, &gameInfo);
10241         if (!err) numPGNTags++;
10242
10243         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10244         if(gameInfo.variant != oldVariant) {
10245             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10246             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10247             InitPosition(TRUE);
10248             oldVariant = gameInfo.variant;
10249             if (appData.debugMode) 
10250               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10251         }
10252
10253
10254         if (gameInfo.fen != NULL) {
10255           Board initial_position;
10256           startedFromSetupPosition = TRUE;
10257           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10258             Reset(TRUE, TRUE);
10259             DisplayError(_("Bad FEN position in file"), 0);
10260             return FALSE;
10261           }
10262           CopyBoard(boards[0], initial_position);
10263           if (blackPlaysFirst) {
10264             currentMove = forwardMostMove = backwardMostMove = 1;
10265             CopyBoard(boards[1], initial_position);
10266             strcpy(moveList[0], "");
10267             strcpy(parseList[0], "");
10268             timeRemaining[0][1] = whiteTimeRemaining;
10269             timeRemaining[1][1] = blackTimeRemaining;
10270             if (commentList[0] != NULL) {
10271               commentList[1] = commentList[0];
10272               commentList[0] = NULL;
10273             }
10274           } else {
10275             currentMove = forwardMostMove = backwardMostMove = 0;
10276           }
10277           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10278           {   int i;
10279               initialRulePlies = FENrulePlies;
10280               for( i=0; i< nrCastlingRights; i++ )
10281                   initialRights[i] = initial_position[CASTLING][i];
10282           }
10283           yyboardindex = forwardMostMove;
10284           free(gameInfo.fen);
10285           gameInfo.fen = NULL;
10286         }
10287
10288         yyboardindex = forwardMostMove;
10289         cm = (ChessMove) yylex();
10290
10291         /* Handle comments interspersed among the tags */
10292         while (cm == Comment) {
10293             char *p;
10294             if (appData.debugMode) 
10295               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10296             p = yy_text;
10297             AppendComment(currentMove, p, FALSE);
10298             yyboardindex = forwardMostMove;
10299             cm = (ChessMove) yylex();
10300         }
10301     }
10302
10303     /* don't rely on existence of Event tag since if game was
10304      * pasted from clipboard the Event tag may not exist
10305      */
10306     if (numPGNTags > 0){
10307         char *tags;
10308         if (gameInfo.variant == VariantNormal) {
10309           VariantClass v = StringToVariant(gameInfo.event);
10310           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10311           if(v < VariantShogi) gameInfo.variant = v;
10312         }
10313         if (!matchMode) {
10314           if( appData.autoDisplayTags ) {
10315             tags = PGNTags(&gameInfo);
10316             TagsPopUp(tags, CmailMsg());
10317             free(tags);
10318           }
10319         }
10320     } else {
10321         /* Make something up, but don't display it now */
10322         SetGameInfo();
10323         TagsPopDown();
10324     }
10325
10326     if (cm == PositionDiagram) {
10327         int i, j;
10328         char *p;
10329         Board initial_position;
10330
10331         if (appData.debugMode)
10332           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10333
10334         if (!startedFromSetupPosition) {
10335             p = yy_text;
10336             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10337               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10338                 switch (*p) {
10339                   case '[':
10340                   case '-':
10341                   case ' ':
10342                   case '\t':
10343                   case '\n':
10344                   case '\r':
10345                     break;
10346                   default:
10347                     initial_position[i][j++] = CharToPiece(*p);
10348                     break;
10349                 }
10350             while (*p == ' ' || *p == '\t' ||
10351                    *p == '\n' || *p == '\r') p++;
10352         
10353             if (strncmp(p, "black", strlen("black"))==0)
10354               blackPlaysFirst = TRUE;
10355             else
10356               blackPlaysFirst = FALSE;
10357             startedFromSetupPosition = TRUE;
10358         
10359             CopyBoard(boards[0], initial_position);
10360             if (blackPlaysFirst) {
10361                 currentMove = forwardMostMove = backwardMostMove = 1;
10362                 CopyBoard(boards[1], initial_position);
10363                 strcpy(moveList[0], "");
10364                 strcpy(parseList[0], "");
10365                 timeRemaining[0][1] = whiteTimeRemaining;
10366                 timeRemaining[1][1] = blackTimeRemaining;
10367                 if (commentList[0] != NULL) {
10368                     commentList[1] = commentList[0];
10369                     commentList[0] = NULL;
10370                 }
10371             } else {
10372                 currentMove = forwardMostMove = backwardMostMove = 0;
10373             }
10374         }
10375         yyboardindex = forwardMostMove;
10376         cm = (ChessMove) yylex();
10377     }
10378
10379     if (first.pr == NoProc) {
10380         StartChessProgram(&first);
10381     }
10382     InitChessProgram(&first, FALSE);
10383     SendToProgram("force\n", &first);
10384     if (startedFromSetupPosition) {
10385         SendBoard(&first, forwardMostMove);
10386     if (appData.debugMode) {
10387         fprintf(debugFP, "Load Game\n");
10388     }
10389         DisplayBothClocks();
10390     }      
10391
10392     /* [HGM] server: flag to write setup moves in broadcast file as one */
10393     loadFlag = appData.suppressLoadMoves;
10394
10395     while (cm == Comment) {
10396         char *p;
10397         if (appData.debugMode) 
10398           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10399         p = yy_text;
10400         AppendComment(currentMove, p, FALSE);
10401         yyboardindex = forwardMostMove;
10402         cm = (ChessMove) yylex();
10403     }
10404
10405     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10406         cm == WhiteWins || cm == BlackWins ||
10407         cm == GameIsDrawn || cm == GameUnfinished) {
10408         DisplayMessage("", _("No moves in game"));
10409         if (cmailMsgLoaded) {
10410             if (appData.debugMode)
10411               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10412             ClearHighlights();
10413             flipView = FALSE;
10414         }
10415         DrawPosition(FALSE, boards[currentMove]);
10416         DisplayBothClocks();
10417         gameMode = EditGame;
10418         ModeHighlight();
10419         gameFileFP = NULL;
10420         cmailOldMove = 0;
10421         return TRUE;
10422     }
10423
10424     // [HGM] PV info: routine tests if comment empty
10425     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10426         DisplayComment(currentMove - 1, commentList[currentMove]);
10427     }
10428     if (!matchMode && appData.timeDelay != 0) 
10429       DrawPosition(FALSE, boards[currentMove]);
10430
10431     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10432       programStats.ok_to_send = 1;
10433     }
10434
10435     /* if the first token after the PGN tags is a move
10436      * and not move number 1, retrieve it from the parser 
10437      */
10438     if (cm != MoveNumberOne)
10439         LoadGameOneMove(cm);
10440
10441     /* load the remaining moves from the file */
10442     while (LoadGameOneMove((ChessMove)0)) {
10443       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10444       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10445     }
10446
10447     /* rewind to the start of the game */
10448     currentMove = backwardMostMove;
10449
10450     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10451
10452     if (oldGameMode == AnalyzeFile ||
10453         oldGameMode == AnalyzeMode) {
10454       AnalyzeFileEvent();
10455     }
10456
10457     if (matchMode || appData.timeDelay == 0) {
10458       ToEndEvent();
10459       gameMode = EditGame;
10460       ModeHighlight();
10461     } else if (appData.timeDelay > 0) {
10462       AutoPlayGameLoop();
10463     }
10464
10465     if (appData.debugMode) 
10466         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10467
10468     loadFlag = 0; /* [HGM] true game starts */
10469     return TRUE;
10470 }
10471
10472 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10473 int
10474 ReloadPosition(offset)
10475      int offset;
10476 {
10477     int positionNumber = lastLoadPositionNumber + offset;
10478     if (lastLoadPositionFP == NULL) {
10479         DisplayError(_("No position has been loaded yet"), 0);
10480         return FALSE;
10481     }
10482     if (positionNumber <= 0) {
10483         DisplayError(_("Can't back up any further"), 0);
10484         return FALSE;
10485     }
10486     return LoadPosition(lastLoadPositionFP, positionNumber,
10487                         lastLoadPositionTitle);
10488 }
10489
10490 /* Load the nth position from the given file */
10491 int
10492 LoadPositionFromFile(filename, n, title)
10493      char *filename;
10494      int n;
10495      char *title;
10496 {
10497     FILE *f;
10498     char buf[MSG_SIZ];
10499
10500     if (strcmp(filename, "-") == 0) {
10501         return LoadPosition(stdin, n, "stdin");
10502     } else {
10503         f = fopen(filename, "rb");
10504         if (f == NULL) {
10505             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10506             DisplayError(buf, errno);
10507             return FALSE;
10508         } else {
10509             return LoadPosition(f, n, title);
10510         }
10511     }
10512 }
10513
10514 /* Load the nth position from the given open file, and close it */
10515 int
10516 LoadPosition(f, positionNumber, title)
10517      FILE *f;
10518      int positionNumber;
10519      char *title;
10520 {
10521     char *p, line[MSG_SIZ];
10522     Board initial_position;
10523     int i, j, fenMode, pn;
10524     
10525     if (gameMode == Training )
10526         SetTrainingModeOff();
10527
10528     if (gameMode != BeginningOfGame) {
10529         Reset(FALSE, TRUE);
10530     }
10531     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10532         fclose(lastLoadPositionFP);
10533     }
10534     if (positionNumber == 0) positionNumber = 1;
10535     lastLoadPositionFP = f;
10536     lastLoadPositionNumber = positionNumber;
10537     strcpy(lastLoadPositionTitle, title);
10538     if (first.pr == NoProc) {
10539       StartChessProgram(&first);
10540       InitChessProgram(&first, FALSE);
10541     }    
10542     pn = positionNumber;
10543     if (positionNumber < 0) {
10544         /* Negative position number means to seek to that byte offset */
10545         if (fseek(f, -positionNumber, 0) == -1) {
10546             DisplayError(_("Can't seek on position file"), 0);
10547             return FALSE;
10548         };
10549         pn = 1;
10550     } else {
10551         if (fseek(f, 0, 0) == -1) {
10552             if (f == lastLoadPositionFP ?
10553                 positionNumber == lastLoadPositionNumber + 1 :
10554                 positionNumber == 1) {
10555                 pn = 1;
10556             } else {
10557                 DisplayError(_("Can't seek on position file"), 0);
10558                 return FALSE;
10559             }
10560         }
10561     }
10562     /* See if this file is FEN or old-style xboard */
10563     if (fgets(line, MSG_SIZ, f) == NULL) {
10564         DisplayError(_("Position not found in file"), 0);
10565         return FALSE;
10566     }
10567     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10568     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10569
10570     if (pn >= 2) {
10571         if (fenMode || line[0] == '#') pn--;
10572         while (pn > 0) {
10573             /* skip positions before number pn */
10574             if (fgets(line, MSG_SIZ, f) == NULL) {
10575                 Reset(TRUE, TRUE);
10576                 DisplayError(_("Position not found in file"), 0);
10577                 return FALSE;
10578             }
10579             if (fenMode || line[0] == '#') pn--;
10580         }
10581     }
10582
10583     if (fenMode) {
10584         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10585             DisplayError(_("Bad FEN position in file"), 0);
10586             return FALSE;
10587         }
10588     } else {
10589         (void) fgets(line, MSG_SIZ, f);
10590         (void) fgets(line, MSG_SIZ, f);
10591     
10592         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10593             (void) fgets(line, MSG_SIZ, f);
10594             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10595                 if (*p == ' ')
10596                   continue;
10597                 initial_position[i][j++] = CharToPiece(*p);
10598             }
10599         }
10600     
10601         blackPlaysFirst = FALSE;
10602         if (!feof(f)) {
10603             (void) fgets(line, MSG_SIZ, f);
10604             if (strncmp(line, "black", strlen("black"))==0)
10605               blackPlaysFirst = TRUE;
10606         }
10607     }
10608     startedFromSetupPosition = TRUE;
10609     
10610     SendToProgram("force\n", &first);
10611     CopyBoard(boards[0], initial_position);
10612     if (blackPlaysFirst) {
10613         currentMove = forwardMostMove = backwardMostMove = 1;
10614         strcpy(moveList[0], "");
10615         strcpy(parseList[0], "");
10616         CopyBoard(boards[1], initial_position);
10617         DisplayMessage("", _("Black to play"));
10618     } else {
10619         currentMove = forwardMostMove = backwardMostMove = 0;
10620         DisplayMessage("", _("White to play"));
10621     }
10622     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10623     SendBoard(&first, forwardMostMove);
10624     if (appData.debugMode) {
10625 int i, j;
10626   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10627   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10628         fprintf(debugFP, "Load Position\n");
10629     }
10630
10631     if (positionNumber > 1) {
10632         sprintf(line, "%s %d", title, positionNumber);
10633         DisplayTitle(line);
10634     } else {
10635         DisplayTitle(title);
10636     }
10637     gameMode = EditGame;
10638     ModeHighlight();
10639     ResetClocks();
10640     timeRemaining[0][1] = whiteTimeRemaining;
10641     timeRemaining[1][1] = blackTimeRemaining;
10642     DrawPosition(FALSE, boards[currentMove]);
10643    
10644     return TRUE;
10645 }
10646
10647
10648 void
10649 CopyPlayerNameIntoFileName(dest, src)
10650      char **dest, *src;
10651 {
10652     while (*src != NULLCHAR && *src != ',') {
10653         if (*src == ' ') {
10654             *(*dest)++ = '_';
10655             src++;
10656         } else {
10657             *(*dest)++ = *src++;
10658         }
10659     }
10660 }
10661
10662 char *DefaultFileName(ext)
10663      char *ext;
10664 {
10665     static char def[MSG_SIZ];
10666     char *p;
10667
10668     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10669         p = def;
10670         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10671         *p++ = '-';
10672         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10673         *p++ = '.';
10674         strcpy(p, ext);
10675     } else {
10676         def[0] = NULLCHAR;
10677     }
10678     return def;
10679 }
10680
10681 /* Save the current game to the given file */
10682 int
10683 SaveGameToFile(filename, append)
10684      char *filename;
10685      int append;
10686 {
10687     FILE *f;
10688     char buf[MSG_SIZ];
10689
10690     if (strcmp(filename, "-") == 0) {
10691         return SaveGame(stdout, 0, NULL);
10692     } else {
10693         f = fopen(filename, append ? "a" : "w");
10694         if (f == NULL) {
10695             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10696             DisplayError(buf, errno);
10697             return FALSE;
10698         } else {
10699             return SaveGame(f, 0, NULL);
10700         }
10701     }
10702 }
10703
10704 char *
10705 SavePart(str)
10706      char *str;
10707 {
10708     static char buf[MSG_SIZ];
10709     char *p;
10710     
10711     p = strchr(str, ' ');
10712     if (p == NULL) return str;
10713     strncpy(buf, str, p - str);
10714     buf[p - str] = NULLCHAR;
10715     return buf;
10716 }
10717
10718 #define PGN_MAX_LINE 75
10719
10720 #define PGN_SIDE_WHITE  0
10721 #define PGN_SIDE_BLACK  1
10722
10723 /* [AS] */
10724 static int FindFirstMoveOutOfBook( int side )
10725 {
10726     int result = -1;
10727
10728     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10729         int index = backwardMostMove;
10730         int has_book_hit = 0;
10731
10732         if( (index % 2) != side ) {
10733             index++;
10734         }
10735
10736         while( index < forwardMostMove ) {
10737             /* Check to see if engine is in book */
10738             int depth = pvInfoList[index].depth;
10739             int score = pvInfoList[index].score;
10740             int in_book = 0;
10741
10742             if( depth <= 2 ) {
10743                 in_book = 1;
10744             }
10745             else if( score == 0 && depth == 63 ) {
10746                 in_book = 1; /* Zappa */
10747             }
10748             else if( score == 2 && depth == 99 ) {
10749                 in_book = 1; /* Abrok */
10750             }
10751
10752             has_book_hit += in_book;
10753
10754             if( ! in_book ) {
10755                 result = index;
10756
10757                 break;
10758             }
10759
10760             index += 2;
10761         }
10762     }
10763
10764     return result;
10765 }
10766
10767 /* [AS] */
10768 void GetOutOfBookInfo( char * buf )
10769 {
10770     int oob[2];
10771     int i;
10772     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10773
10774     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10775     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10776
10777     *buf = '\0';
10778
10779     if( oob[0] >= 0 || oob[1] >= 0 ) {
10780         for( i=0; i<2; i++ ) {
10781             int idx = oob[i];
10782
10783             if( idx >= 0 ) {
10784                 if( i > 0 && oob[0] >= 0 ) {
10785                     strcat( buf, "   " );
10786                 }
10787
10788                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10789                 sprintf( buf+strlen(buf), "%s%.2f", 
10790                     pvInfoList[idx].score >= 0 ? "+" : "",
10791                     pvInfoList[idx].score / 100.0 );
10792             }
10793         }
10794     }
10795 }
10796
10797 /* Save game in PGN style and close the file */
10798 int
10799 SaveGamePGN(f)
10800      FILE *f;
10801 {
10802     int i, offset, linelen, newblock;
10803     time_t tm;
10804 //    char *movetext;
10805     char numtext[32];
10806     int movelen, numlen, blank;
10807     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10808
10809     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10810     
10811     tm = time((time_t *) NULL);
10812     
10813     PrintPGNTags(f, &gameInfo);
10814     
10815     if (backwardMostMove > 0 || startedFromSetupPosition) {
10816         char *fen = PositionToFEN(backwardMostMove, NULL);
10817         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10818         fprintf(f, "\n{--------------\n");
10819         PrintPosition(f, backwardMostMove);
10820         fprintf(f, "--------------}\n");
10821         free(fen);
10822     }
10823     else {
10824         /* [AS] Out of book annotation */
10825         if( appData.saveOutOfBookInfo ) {
10826             char buf[64];
10827
10828             GetOutOfBookInfo( buf );
10829
10830             if( buf[0] != '\0' ) {
10831                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10832             }
10833         }
10834
10835         fprintf(f, "\n");
10836     }
10837
10838     i = backwardMostMove;
10839     linelen = 0;
10840     newblock = TRUE;
10841
10842     while (i < forwardMostMove) {
10843         /* Print comments preceding this move */
10844         if (commentList[i] != NULL) {
10845             if (linelen > 0) fprintf(f, "\n");
10846             fprintf(f, "%s", commentList[i]);
10847             linelen = 0;
10848             newblock = TRUE;
10849         }
10850
10851         /* Format move number */
10852         if ((i % 2) == 0) {
10853             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10854         } else {
10855             if (newblock) {
10856                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10857             } else {
10858                 numtext[0] = NULLCHAR;
10859             }
10860         }
10861         numlen = strlen(numtext);
10862         newblock = FALSE;
10863
10864         /* Print move number */
10865         blank = linelen > 0 && numlen > 0;
10866         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10867             fprintf(f, "\n");
10868             linelen = 0;
10869             blank = 0;
10870         }
10871         if (blank) {
10872             fprintf(f, " ");
10873             linelen++;
10874         }
10875         fprintf(f, "%s", numtext);
10876         linelen += numlen;
10877
10878         /* Get move */
10879         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10880         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10881
10882         /* Print move */
10883         blank = linelen > 0 && movelen > 0;
10884         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10885             fprintf(f, "\n");
10886             linelen = 0;
10887             blank = 0;
10888         }
10889         if (blank) {
10890             fprintf(f, " ");
10891             linelen++;
10892         }
10893         fprintf(f, "%s", move_buffer);
10894         linelen += movelen;
10895
10896         /* [AS] Add PV info if present */
10897         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10898             /* [HGM] add time */
10899             char buf[MSG_SIZ]; int seconds;
10900
10901             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10902
10903             if( seconds <= 0) buf[0] = 0; else
10904             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10905                 seconds = (seconds + 4)/10; // round to full seconds
10906                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10907                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10908             }
10909
10910             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10911                 pvInfoList[i].score >= 0 ? "+" : "",
10912                 pvInfoList[i].score / 100.0,
10913                 pvInfoList[i].depth,
10914                 buf );
10915
10916             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10917
10918             /* Print score/depth */
10919             blank = linelen > 0 && movelen > 0;
10920             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10921                 fprintf(f, "\n");
10922                 linelen = 0;
10923                 blank = 0;
10924             }
10925             if (blank) {
10926                 fprintf(f, " ");
10927                 linelen++;
10928             }
10929             fprintf(f, "%s", move_buffer);
10930             linelen += movelen;
10931         }
10932
10933         i++;
10934     }
10935     
10936     /* Start a new line */
10937     if (linelen > 0) fprintf(f, "\n");
10938
10939     /* Print comments after last move */
10940     if (commentList[i] != NULL) {
10941         fprintf(f, "%s\n", commentList[i]);
10942     }
10943
10944     /* Print result */
10945     if (gameInfo.resultDetails != NULL &&
10946         gameInfo.resultDetails[0] != NULLCHAR) {
10947         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10948                 PGNResult(gameInfo.result));
10949     } else {
10950         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10951     }
10952
10953     fclose(f);
10954     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10955     return TRUE;
10956 }
10957
10958 /* Save game in old style and close the file */
10959 int
10960 SaveGameOldStyle(f)
10961      FILE *f;
10962 {
10963     int i, offset;
10964     time_t tm;
10965     
10966     tm = time((time_t *) NULL);
10967     
10968     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10969     PrintOpponents(f);
10970     
10971     if (backwardMostMove > 0 || startedFromSetupPosition) {
10972         fprintf(f, "\n[--------------\n");
10973         PrintPosition(f, backwardMostMove);
10974         fprintf(f, "--------------]\n");
10975     } else {
10976         fprintf(f, "\n");
10977     }
10978
10979     i = backwardMostMove;
10980     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10981
10982     while (i < forwardMostMove) {
10983         if (commentList[i] != NULL) {
10984             fprintf(f, "[%s]\n", commentList[i]);
10985         }
10986
10987         if ((i % 2) == 1) {
10988             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10989             i++;
10990         } else {
10991             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10992             i++;
10993             if (commentList[i] != NULL) {
10994                 fprintf(f, "\n");
10995                 continue;
10996             }
10997             if (i >= forwardMostMove) {
10998                 fprintf(f, "\n");
10999                 break;
11000             }
11001             fprintf(f, "%s\n", parseList[i]);
11002             i++;
11003         }
11004     }
11005     
11006     if (commentList[i] != NULL) {
11007         fprintf(f, "[%s]\n", commentList[i]);
11008     }
11009
11010     /* This isn't really the old style, but it's close enough */
11011     if (gameInfo.resultDetails != NULL &&
11012         gameInfo.resultDetails[0] != NULLCHAR) {
11013         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11014                 gameInfo.resultDetails);
11015     } else {
11016         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11017     }
11018
11019     fclose(f);
11020     return TRUE;
11021 }
11022
11023 /* Save the current game to open file f and close the file */
11024 int
11025 SaveGame(f, dummy, dummy2)
11026      FILE *f;
11027      int dummy;
11028      char *dummy2;
11029 {
11030     if (gameMode == EditPosition) EditPositionDone(TRUE);
11031     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11032     if (appData.oldSaveStyle)
11033       return SaveGameOldStyle(f);
11034     else
11035       return SaveGamePGN(f);
11036 }
11037
11038 /* Save the current position to the given file */
11039 int
11040 SavePositionToFile(filename)
11041      char *filename;
11042 {
11043     FILE *f;
11044     char buf[MSG_SIZ];
11045
11046     if (strcmp(filename, "-") == 0) {
11047         return SavePosition(stdout, 0, NULL);
11048     } else {
11049         f = fopen(filename, "a");
11050         if (f == NULL) {
11051             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11052             DisplayError(buf, errno);
11053             return FALSE;
11054         } else {
11055             SavePosition(f, 0, NULL);
11056             return TRUE;
11057         }
11058     }
11059 }
11060
11061 /* Save the current position to the given open file and close the file */
11062 int
11063 SavePosition(f, dummy, dummy2)
11064      FILE *f;
11065      int dummy;
11066      char *dummy2;
11067 {
11068     time_t tm;
11069     char *fen;
11070     
11071     if (gameMode == EditPosition) EditPositionDone(TRUE);
11072     if (appData.oldSaveStyle) {
11073         tm = time((time_t *) NULL);
11074     
11075         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11076         PrintOpponents(f);
11077         fprintf(f, "[--------------\n");
11078         PrintPosition(f, currentMove);
11079         fprintf(f, "--------------]\n");
11080     } else {
11081         fen = PositionToFEN(currentMove, NULL);
11082         fprintf(f, "%s\n", fen);
11083         free(fen);
11084     }
11085     fclose(f);
11086     return TRUE;
11087 }
11088
11089 void
11090 ReloadCmailMsgEvent(unregister)
11091      int unregister;
11092 {
11093 #if !WIN32
11094     static char *inFilename = NULL;
11095     static char *outFilename;
11096     int i;
11097     struct stat inbuf, outbuf;
11098     int status;
11099     
11100     /* Any registered moves are unregistered if unregister is set, */
11101     /* i.e. invoked by the signal handler */
11102     if (unregister) {
11103         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11104             cmailMoveRegistered[i] = FALSE;
11105             if (cmailCommentList[i] != NULL) {
11106                 free(cmailCommentList[i]);
11107                 cmailCommentList[i] = NULL;
11108             }
11109         }
11110         nCmailMovesRegistered = 0;
11111     }
11112
11113     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11114         cmailResult[i] = CMAIL_NOT_RESULT;
11115     }
11116     nCmailResults = 0;
11117
11118     if (inFilename == NULL) {
11119         /* Because the filenames are static they only get malloced once  */
11120         /* and they never get freed                                      */
11121         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11122         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11123
11124         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11125         sprintf(outFilename, "%s.out", appData.cmailGameName);
11126     }
11127     
11128     status = stat(outFilename, &outbuf);
11129     if (status < 0) {
11130         cmailMailedMove = FALSE;
11131     } else {
11132         status = stat(inFilename, &inbuf);
11133         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11134     }
11135     
11136     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11137        counts the games, notes how each one terminated, etc.
11138        
11139        It would be nice to remove this kludge and instead gather all
11140        the information while building the game list.  (And to keep it
11141        in the game list nodes instead of having a bunch of fixed-size
11142        parallel arrays.)  Note this will require getting each game's
11143        termination from the PGN tags, as the game list builder does
11144        not process the game moves.  --mann
11145        */
11146     cmailMsgLoaded = TRUE;
11147     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11148     
11149     /* Load first game in the file or popup game menu */
11150     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11151
11152 #endif /* !WIN32 */
11153     return;
11154 }
11155
11156 int
11157 RegisterMove()
11158 {
11159     FILE *f;
11160     char string[MSG_SIZ];
11161
11162     if (   cmailMailedMove
11163         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11164         return TRUE;            /* Allow free viewing  */
11165     }
11166
11167     /* Unregister move to ensure that we don't leave RegisterMove        */
11168     /* with the move registered when the conditions for registering no   */
11169     /* longer hold                                                       */
11170     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11171         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11172         nCmailMovesRegistered --;
11173
11174         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11175           {
11176               free(cmailCommentList[lastLoadGameNumber - 1]);
11177               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11178           }
11179     }
11180
11181     if (cmailOldMove == -1) {
11182         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11183         return FALSE;
11184     }
11185
11186     if (currentMove > cmailOldMove + 1) {
11187         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11188         return FALSE;
11189     }
11190
11191     if (currentMove < cmailOldMove) {
11192         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11193         return FALSE;
11194     }
11195
11196     if (forwardMostMove > currentMove) {
11197         /* Silently truncate extra moves */
11198         TruncateGame();
11199     }
11200
11201     if (   (currentMove == cmailOldMove + 1)
11202         || (   (currentMove == cmailOldMove)
11203             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11204                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11205         if (gameInfo.result != GameUnfinished) {
11206             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11207         }
11208
11209         if (commentList[currentMove] != NULL) {
11210             cmailCommentList[lastLoadGameNumber - 1]
11211               = StrSave(commentList[currentMove]);
11212         }
11213         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11214
11215         if (appData.debugMode)
11216           fprintf(debugFP, "Saving %s for game %d\n",
11217                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11218
11219         sprintf(string,
11220                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11221         
11222         f = fopen(string, "w");
11223         if (appData.oldSaveStyle) {
11224             SaveGameOldStyle(f); /* also closes the file */
11225             
11226             sprintf(string, "%s.pos.out", appData.cmailGameName);
11227             f = fopen(string, "w");
11228             SavePosition(f, 0, NULL); /* also closes the file */
11229         } else {
11230             fprintf(f, "{--------------\n");
11231             PrintPosition(f, currentMove);
11232             fprintf(f, "--------------}\n\n");
11233             
11234             SaveGame(f, 0, NULL); /* also closes the file*/
11235         }
11236         
11237         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11238         nCmailMovesRegistered ++;
11239     } else if (nCmailGames == 1) {
11240         DisplayError(_("You have not made a move yet"), 0);
11241         return FALSE;
11242     }
11243
11244     return TRUE;
11245 }
11246
11247 void
11248 MailMoveEvent()
11249 {
11250 #if !WIN32
11251     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11252     FILE *commandOutput;
11253     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11254     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11255     int nBuffers;
11256     int i;
11257     int archived;
11258     char *arcDir;
11259
11260     if (! cmailMsgLoaded) {
11261         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11262         return;
11263     }
11264
11265     if (nCmailGames == nCmailResults) {
11266         DisplayError(_("No unfinished games"), 0);
11267         return;
11268     }
11269
11270 #if CMAIL_PROHIBIT_REMAIL
11271     if (cmailMailedMove) {
11272         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);
11273         DisplayError(msg, 0);
11274         return;
11275     }
11276 #endif
11277
11278     if (! (cmailMailedMove || RegisterMove())) return;
11279     
11280     if (   cmailMailedMove
11281         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11282         sprintf(string, partCommandString,
11283                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11284         commandOutput = popen(string, "r");
11285
11286         if (commandOutput == NULL) {
11287             DisplayError(_("Failed to invoke cmail"), 0);
11288         } else {
11289             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11290                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11291             }
11292             if (nBuffers > 1) {
11293                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11294                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11295                 nBytes = MSG_SIZ - 1;
11296             } else {
11297                 (void) memcpy(msg, buffer, nBytes);
11298             }
11299             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11300
11301             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11302                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11303
11304                 archived = TRUE;
11305                 for (i = 0; i < nCmailGames; i ++) {
11306                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11307                         archived = FALSE;
11308                     }
11309                 }
11310                 if (   archived
11311                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11312                         != NULL)) {
11313                     sprintf(buffer, "%s/%s.%s.archive",
11314                             arcDir,
11315                             appData.cmailGameName,
11316                             gameInfo.date);
11317                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11318                     cmailMsgLoaded = FALSE;
11319                 }
11320             }
11321
11322             DisplayInformation(msg);
11323             pclose(commandOutput);
11324         }
11325     } else {
11326         if ((*cmailMsg) != '\0') {
11327             DisplayInformation(cmailMsg);
11328         }
11329     }
11330
11331     return;
11332 #endif /* !WIN32 */
11333 }
11334
11335 char *
11336 CmailMsg()
11337 {
11338 #if WIN32
11339     return NULL;
11340 #else
11341     int  prependComma = 0;
11342     char number[5];
11343     char string[MSG_SIZ];       /* Space for game-list */
11344     int  i;
11345     
11346     if (!cmailMsgLoaded) return "";
11347
11348     if (cmailMailedMove) {
11349         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11350     } else {
11351         /* Create a list of games left */
11352         sprintf(string, "[");
11353         for (i = 0; i < nCmailGames; i ++) {
11354             if (! (   cmailMoveRegistered[i]
11355                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11356                 if (prependComma) {
11357                     sprintf(number, ",%d", i + 1);
11358                 } else {
11359                     sprintf(number, "%d", i + 1);
11360                     prependComma = 1;
11361                 }
11362                 
11363                 strcat(string, number);
11364             }
11365         }
11366         strcat(string, "]");
11367
11368         if (nCmailMovesRegistered + nCmailResults == 0) {
11369             switch (nCmailGames) {
11370               case 1:
11371                 sprintf(cmailMsg,
11372                         _("Still need to make move for game\n"));
11373                 break;
11374                 
11375               case 2:
11376                 sprintf(cmailMsg,
11377                         _("Still need to make moves for both games\n"));
11378                 break;
11379                 
11380               default:
11381                 sprintf(cmailMsg,
11382                         _("Still need to make moves for all %d games\n"),
11383                         nCmailGames);
11384                 break;
11385             }
11386         } else {
11387             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11388               case 1:
11389                 sprintf(cmailMsg,
11390                         _("Still need to make a move for game %s\n"),
11391                         string);
11392                 break;
11393                 
11394               case 0:
11395                 if (nCmailResults == nCmailGames) {
11396                     sprintf(cmailMsg, _("No unfinished games\n"));
11397                 } else {
11398                     sprintf(cmailMsg, _("Ready to send mail\n"));
11399                 }
11400                 break;
11401                 
11402               default:
11403                 sprintf(cmailMsg,
11404                         _("Still need to make moves for games %s\n"),
11405                         string);
11406             }
11407         }
11408     }
11409     return cmailMsg;
11410 #endif /* WIN32 */
11411 }
11412
11413 void
11414 ResetGameEvent()
11415 {
11416     if (gameMode == Training)
11417       SetTrainingModeOff();
11418
11419     Reset(TRUE, TRUE);
11420     cmailMsgLoaded = FALSE;
11421     if (appData.icsActive) {
11422       SendToICS(ics_prefix);
11423       SendToICS("refresh\n");
11424     }
11425 }
11426
11427 void
11428 ExitEvent(status)
11429      int status;
11430 {
11431     exiting++;
11432     if (exiting > 2) {
11433       /* Give up on clean exit */
11434       exit(status);
11435     }
11436     if (exiting > 1) {
11437       /* Keep trying for clean exit */
11438       return;
11439     }
11440
11441     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11442
11443     if (telnetISR != NULL) {
11444       RemoveInputSource(telnetISR);
11445     }
11446     if (icsPR != NoProc) {
11447       DestroyChildProcess(icsPR, TRUE);
11448     }
11449
11450     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11451     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11452
11453     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11454     /* make sure this other one finishes before killing it!                  */
11455     if(endingGame) { int count = 0;
11456         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11457         while(endingGame && count++ < 10) DoSleep(1);
11458         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11459     }
11460
11461     /* Kill off chess programs */
11462     if (first.pr != NoProc) {
11463         ExitAnalyzeMode();
11464         
11465         DoSleep( appData.delayBeforeQuit );
11466         SendToProgram("quit\n", &first);
11467         DoSleep( appData.delayAfterQuit );
11468         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11469     }
11470     if (second.pr != NoProc) {
11471         DoSleep( appData.delayBeforeQuit );
11472         SendToProgram("quit\n", &second);
11473         DoSleep( appData.delayAfterQuit );
11474         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11475     }
11476     if (first.isr != NULL) {
11477         RemoveInputSource(first.isr);
11478     }
11479     if (second.isr != NULL) {
11480         RemoveInputSource(second.isr);
11481     }
11482
11483     ShutDownFrontEnd();
11484     exit(status);
11485 }
11486
11487 void
11488 PauseEvent()
11489 {
11490     if (appData.debugMode)
11491         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11492     if (pausing) {
11493         pausing = FALSE;
11494         ModeHighlight();
11495         if (gameMode == MachinePlaysWhite ||
11496             gameMode == MachinePlaysBlack) {
11497             StartClocks();
11498         } else {
11499             DisplayBothClocks();
11500         }
11501         if (gameMode == PlayFromGameFile) {
11502             if (appData.timeDelay >= 0) 
11503                 AutoPlayGameLoop();
11504         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11505             Reset(FALSE, TRUE);
11506             SendToICS(ics_prefix);
11507             SendToICS("refresh\n");
11508         } else if (currentMove < forwardMostMove) {
11509             ForwardInner(forwardMostMove);
11510         }
11511         pauseExamInvalid = FALSE;
11512     } else {
11513         switch (gameMode) {
11514           default:
11515             return;
11516           case IcsExamining:
11517             pauseExamForwardMostMove = forwardMostMove;
11518             pauseExamInvalid = FALSE;
11519             /* fall through */
11520           case IcsObserving:
11521           case IcsPlayingWhite:
11522           case IcsPlayingBlack:
11523             pausing = TRUE;
11524             ModeHighlight();
11525             return;
11526           case PlayFromGameFile:
11527             (void) StopLoadGameTimer();
11528             pausing = TRUE;
11529             ModeHighlight();
11530             break;
11531           case BeginningOfGame:
11532             if (appData.icsActive) return;
11533             /* else fall through */
11534           case MachinePlaysWhite:
11535           case MachinePlaysBlack:
11536           case TwoMachinesPlay:
11537             if (forwardMostMove == 0)
11538               return;           /* don't pause if no one has moved */
11539             if ((gameMode == MachinePlaysWhite &&
11540                  !WhiteOnMove(forwardMostMove)) ||
11541                 (gameMode == MachinePlaysBlack &&
11542                  WhiteOnMove(forwardMostMove))) {
11543                 StopClocks();
11544             }
11545             pausing = TRUE;
11546             ModeHighlight();
11547             break;
11548         }
11549     }
11550 }
11551
11552 void
11553 EditCommentEvent()
11554 {
11555     char title[MSG_SIZ];
11556
11557     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11558         strcpy(title, _("Edit comment"));
11559     } else {
11560         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11561                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11562                 parseList[currentMove - 1]);
11563     }
11564
11565     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11566 }
11567
11568
11569 void
11570 EditTagsEvent()
11571 {
11572     char *tags = PGNTags(&gameInfo);
11573     EditTagsPopUp(tags);
11574     free(tags);
11575 }
11576
11577 void
11578 AnalyzeModeEvent()
11579 {
11580     if (appData.noChessProgram || gameMode == AnalyzeMode)
11581       return;
11582
11583     if (gameMode != AnalyzeFile) {
11584         if (!appData.icsEngineAnalyze) {
11585                EditGameEvent();
11586                if (gameMode != EditGame) return;
11587         }
11588         ResurrectChessProgram();
11589         SendToProgram("analyze\n", &first);
11590         first.analyzing = TRUE;
11591         /*first.maybeThinking = TRUE;*/
11592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11593         EngineOutputPopUp();
11594     }
11595     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11596     pausing = FALSE;
11597     ModeHighlight();
11598     SetGameInfo();
11599
11600     StartAnalysisClock();
11601     GetTimeMark(&lastNodeCountTime);
11602     lastNodeCount = 0;
11603 }
11604
11605 void
11606 AnalyzeFileEvent()
11607 {
11608     if (appData.noChessProgram || gameMode == AnalyzeFile)
11609       return;
11610
11611     if (gameMode != AnalyzeMode) {
11612         EditGameEvent();
11613         if (gameMode != EditGame) return;
11614         ResurrectChessProgram();
11615         SendToProgram("analyze\n", &first);
11616         first.analyzing = TRUE;
11617         /*first.maybeThinking = TRUE;*/
11618         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11619         EngineOutputPopUp();
11620     }
11621     gameMode = AnalyzeFile;
11622     pausing = FALSE;
11623     ModeHighlight();
11624     SetGameInfo();
11625
11626     StartAnalysisClock();
11627     GetTimeMark(&lastNodeCountTime);
11628     lastNodeCount = 0;
11629 }
11630
11631 void
11632 MachineWhiteEvent()
11633 {
11634     char buf[MSG_SIZ];
11635     char *bookHit = NULL;
11636
11637     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11638       return;
11639
11640
11641     if (gameMode == PlayFromGameFile || 
11642         gameMode == TwoMachinesPlay  || 
11643         gameMode == Training         || 
11644         gameMode == AnalyzeMode      || 
11645         gameMode == EndOfGame)
11646         EditGameEvent();
11647
11648     if (gameMode == EditPosition) 
11649         EditPositionDone(TRUE);
11650
11651     if (!WhiteOnMove(currentMove)) {
11652         DisplayError(_("It is not White's turn"), 0);
11653         return;
11654     }
11655   
11656     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11657       ExitAnalyzeMode();
11658
11659     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11660         gameMode == AnalyzeFile)
11661         TruncateGame();
11662
11663     ResurrectChessProgram();    /* in case it isn't running */
11664     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11665         gameMode = MachinePlaysWhite;
11666         ResetClocks();
11667     } else
11668     gameMode = MachinePlaysWhite;
11669     pausing = FALSE;
11670     ModeHighlight();
11671     SetGameInfo();
11672     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11673     DisplayTitle(buf);
11674     if (first.sendName) {
11675       sprintf(buf, "name %s\n", gameInfo.black);
11676       SendToProgram(buf, &first);
11677     }
11678     if (first.sendTime) {
11679       if (first.useColors) {
11680         SendToProgram("black\n", &first); /*gnu kludge*/
11681       }
11682       SendTimeRemaining(&first, TRUE);
11683     }
11684     if (first.useColors) {
11685       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11686     }
11687     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11688     SetMachineThinkingEnables();
11689     first.maybeThinking = TRUE;
11690     StartClocks();
11691     firstMove = FALSE;
11692
11693     if (appData.autoFlipView && !flipView) {
11694       flipView = !flipView;
11695       DrawPosition(FALSE, NULL);
11696       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11697     }
11698
11699     if(bookHit) { // [HGM] book: simulate book reply
11700         static char bookMove[MSG_SIZ]; // a bit generous?
11701
11702         programStats.nodes = programStats.depth = programStats.time = 
11703         programStats.score = programStats.got_only_move = 0;
11704         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11705
11706         strcpy(bookMove, "move ");
11707         strcat(bookMove, bookHit);
11708         HandleMachineMove(bookMove, &first);
11709     }
11710 }
11711
11712 void
11713 MachineBlackEvent()
11714 {
11715     char buf[MSG_SIZ];
11716    char *bookHit = NULL;
11717
11718     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11719         return;
11720
11721
11722     if (gameMode == PlayFromGameFile || 
11723         gameMode == TwoMachinesPlay  || 
11724         gameMode == Training         || 
11725         gameMode == AnalyzeMode      || 
11726         gameMode == EndOfGame)
11727         EditGameEvent();
11728
11729     if (gameMode == EditPosition) 
11730         EditPositionDone(TRUE);
11731
11732     if (WhiteOnMove(currentMove)) {
11733         DisplayError(_("It is not Black's turn"), 0);
11734         return;
11735     }
11736     
11737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11738       ExitAnalyzeMode();
11739
11740     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11741         gameMode == AnalyzeFile)
11742         TruncateGame();
11743
11744     ResurrectChessProgram();    /* in case it isn't running */
11745     gameMode = MachinePlaysBlack;
11746     pausing = FALSE;
11747     ModeHighlight();
11748     SetGameInfo();
11749     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11750     DisplayTitle(buf);
11751     if (first.sendName) {
11752       sprintf(buf, "name %s\n", gameInfo.white);
11753       SendToProgram(buf, &first);
11754     }
11755     if (first.sendTime) {
11756       if (first.useColors) {
11757         SendToProgram("white\n", &first); /*gnu kludge*/
11758       }
11759       SendTimeRemaining(&first, FALSE);
11760     }
11761     if (first.useColors) {
11762       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11763     }
11764     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11765     SetMachineThinkingEnables();
11766     first.maybeThinking = TRUE;
11767     StartClocks();
11768
11769     if (appData.autoFlipView && flipView) {
11770       flipView = !flipView;
11771       DrawPosition(FALSE, NULL);
11772       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11773     }
11774     if(bookHit) { // [HGM] book: simulate book reply
11775         static char bookMove[MSG_SIZ]; // a bit generous?
11776
11777         programStats.nodes = programStats.depth = programStats.time = 
11778         programStats.score = programStats.got_only_move = 0;
11779         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11780
11781         strcpy(bookMove, "move ");
11782         strcat(bookMove, bookHit);
11783         HandleMachineMove(bookMove, &first);
11784     }
11785 }
11786
11787
11788 void
11789 DisplayTwoMachinesTitle()
11790 {
11791     char buf[MSG_SIZ];
11792     if (appData.matchGames > 0) {
11793         if (first.twoMachinesColor[0] == 'w') {
11794             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11795                     gameInfo.white, gameInfo.black,
11796                     first.matchWins, second.matchWins,
11797                     matchGame - 1 - (first.matchWins + second.matchWins));
11798         } else {
11799             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11800                     gameInfo.white, gameInfo.black,
11801                     second.matchWins, first.matchWins,
11802                     matchGame - 1 - (first.matchWins + second.matchWins));
11803         }
11804     } else {
11805         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11806     }
11807     DisplayTitle(buf);
11808 }
11809
11810 void
11811 TwoMachinesEvent P((void))
11812 {
11813     int i;
11814     char buf[MSG_SIZ];
11815     ChessProgramState *onmove;
11816     char *bookHit = NULL;
11817     
11818     if (appData.noChessProgram) return;
11819
11820     switch (gameMode) {
11821       case TwoMachinesPlay:
11822         return;
11823       case MachinePlaysWhite:
11824       case MachinePlaysBlack:
11825         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11826             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11827             return;
11828         }
11829         /* fall through */
11830       case BeginningOfGame:
11831       case PlayFromGameFile:
11832       case EndOfGame:
11833         EditGameEvent();
11834         if (gameMode != EditGame) return;
11835         break;
11836       case EditPosition:
11837         EditPositionDone(TRUE);
11838         break;
11839       case AnalyzeMode:
11840       case AnalyzeFile:
11841         ExitAnalyzeMode();
11842         break;
11843       case EditGame:
11844       default:
11845         break;
11846     }
11847
11848 //    forwardMostMove = currentMove;
11849     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11850     ResurrectChessProgram();    /* in case first program isn't running */
11851
11852     if (second.pr == NULL) {
11853         StartChessProgram(&second);
11854         if (second.protocolVersion == 1) {
11855           TwoMachinesEventIfReady();
11856         } else {
11857           /* kludge: allow timeout for initial "feature" command */
11858           FreezeUI();
11859           DisplayMessage("", _("Starting second chess program"));
11860           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11861         }
11862         return;
11863     }
11864     DisplayMessage("", "");
11865     InitChessProgram(&second, FALSE);
11866     SendToProgram("force\n", &second);
11867     if (startedFromSetupPosition) {
11868         SendBoard(&second, backwardMostMove);
11869     if (appData.debugMode) {
11870         fprintf(debugFP, "Two Machines\n");
11871     }
11872     }
11873     for (i = backwardMostMove; i < forwardMostMove; i++) {
11874         SendMoveToProgram(i, &second);
11875     }
11876
11877     gameMode = TwoMachinesPlay;
11878     pausing = FALSE;
11879     ModeHighlight();
11880     SetGameInfo();
11881     DisplayTwoMachinesTitle();
11882     firstMove = TRUE;
11883     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11884         onmove = &first;
11885     } else {
11886         onmove = &second;
11887     }
11888
11889     SendToProgram(first.computerString, &first);
11890     if (first.sendName) {
11891       sprintf(buf, "name %s\n", second.tidy);
11892       SendToProgram(buf, &first);
11893     }
11894     SendToProgram(second.computerString, &second);
11895     if (second.sendName) {
11896       sprintf(buf, "name %s\n", first.tidy);
11897       SendToProgram(buf, &second);
11898     }
11899
11900     ResetClocks();
11901     if (!first.sendTime || !second.sendTime) {
11902         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11903         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11904     }
11905     if (onmove->sendTime) {
11906       if (onmove->useColors) {
11907         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11908       }
11909       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11910     }
11911     if (onmove->useColors) {
11912       SendToProgram(onmove->twoMachinesColor, onmove);
11913     }
11914     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11915 //    SendToProgram("go\n", onmove);
11916     onmove->maybeThinking = TRUE;
11917     SetMachineThinkingEnables();
11918
11919     StartClocks();
11920
11921     if(bookHit) { // [HGM] book: simulate book reply
11922         static char bookMove[MSG_SIZ]; // a bit generous?
11923
11924         programStats.nodes = programStats.depth = programStats.time = 
11925         programStats.score = programStats.got_only_move = 0;
11926         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11927
11928         strcpy(bookMove, "move ");
11929         strcat(bookMove, bookHit);
11930         savedMessage = bookMove; // args for deferred call
11931         savedState = onmove;
11932         ScheduleDelayedEvent(DeferredBookMove, 1);
11933     }
11934 }
11935
11936 void
11937 TrainingEvent()
11938 {
11939     if (gameMode == Training) {
11940       SetTrainingModeOff();
11941       gameMode = PlayFromGameFile;
11942       DisplayMessage("", _("Training mode off"));
11943     } else {
11944       gameMode = Training;
11945       animateTraining = appData.animate;
11946
11947       /* make sure we are not already at the end of the game */
11948       if (currentMove < forwardMostMove) {
11949         SetTrainingModeOn();
11950         DisplayMessage("", _("Training mode on"));
11951       } else {
11952         gameMode = PlayFromGameFile;
11953         DisplayError(_("Already at end of game"), 0);
11954       }
11955     }
11956     ModeHighlight();
11957 }
11958
11959 void
11960 IcsClientEvent()
11961 {
11962     if (!appData.icsActive) return;
11963     switch (gameMode) {
11964       case IcsPlayingWhite:
11965       case IcsPlayingBlack:
11966       case IcsObserving:
11967       case IcsIdle:
11968       case BeginningOfGame:
11969       case IcsExamining:
11970         return;
11971
11972       case EditGame:
11973         break;
11974
11975       case EditPosition:
11976         EditPositionDone(TRUE);
11977         break;
11978
11979       case AnalyzeMode:
11980       case AnalyzeFile:
11981         ExitAnalyzeMode();
11982         break;
11983         
11984       default:
11985         EditGameEvent();
11986         break;
11987     }
11988
11989     gameMode = IcsIdle;
11990     ModeHighlight();
11991     return;
11992 }
11993
11994
11995 void
11996 EditGameEvent()
11997 {
11998     int i;
11999
12000     switch (gameMode) {
12001       case Training:
12002         SetTrainingModeOff();
12003         break;
12004       case MachinePlaysWhite:
12005       case MachinePlaysBlack:
12006       case BeginningOfGame:
12007         SendToProgram("force\n", &first);
12008         SetUserThinkingEnables();
12009         break;
12010       case PlayFromGameFile:
12011         (void) StopLoadGameTimer();
12012         if (gameFileFP != NULL) {
12013             gameFileFP = NULL;
12014         }
12015         break;
12016       case EditPosition:
12017         EditPositionDone(TRUE);
12018         break;
12019       case AnalyzeMode:
12020       case AnalyzeFile:
12021         ExitAnalyzeMode();
12022         SendToProgram("force\n", &first);
12023         break;
12024       case TwoMachinesPlay:
12025         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12026         ResurrectChessProgram();
12027         SetUserThinkingEnables();
12028         break;
12029       case EndOfGame:
12030         ResurrectChessProgram();
12031         break;
12032       case IcsPlayingBlack:
12033       case IcsPlayingWhite:
12034         DisplayError(_("Warning: You are still playing a game"), 0);
12035         break;
12036       case IcsObserving:
12037         DisplayError(_("Warning: You are still observing a game"), 0);
12038         break;
12039       case IcsExamining:
12040         DisplayError(_("Warning: You are still examining a game"), 0);
12041         break;
12042       case IcsIdle:
12043         break;
12044       case EditGame:
12045       default:
12046         return;
12047     }
12048     
12049     pausing = FALSE;
12050     StopClocks();
12051     first.offeredDraw = second.offeredDraw = 0;
12052
12053     if (gameMode == PlayFromGameFile) {
12054         whiteTimeRemaining = timeRemaining[0][currentMove];
12055         blackTimeRemaining = timeRemaining[1][currentMove];
12056         DisplayTitle("");
12057     }
12058
12059     if (gameMode == MachinePlaysWhite ||
12060         gameMode == MachinePlaysBlack ||
12061         gameMode == TwoMachinesPlay ||
12062         gameMode == EndOfGame) {
12063         i = forwardMostMove;
12064         while (i > currentMove) {
12065             SendToProgram("undo\n", &first);
12066             i--;
12067         }
12068         whiteTimeRemaining = timeRemaining[0][currentMove];
12069         blackTimeRemaining = timeRemaining[1][currentMove];
12070         DisplayBothClocks();
12071         if (whiteFlag || blackFlag) {
12072             whiteFlag = blackFlag = 0;
12073         }
12074         DisplayTitle("");
12075     }           
12076     
12077     gameMode = EditGame;
12078     ModeHighlight();
12079     SetGameInfo();
12080 }
12081
12082
12083 void
12084 EditPositionEvent()
12085 {
12086     if (gameMode == EditPosition) {
12087         EditGameEvent();
12088         return;
12089     }
12090     
12091     EditGameEvent();
12092     if (gameMode != EditGame) return;
12093     
12094     gameMode = EditPosition;
12095     ModeHighlight();
12096     SetGameInfo();
12097     if (currentMove > 0)
12098       CopyBoard(boards[0], boards[currentMove]);
12099     
12100     blackPlaysFirst = !WhiteOnMove(currentMove);
12101     ResetClocks();
12102     currentMove = forwardMostMove = backwardMostMove = 0;
12103     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12104     DisplayMove(-1);
12105 }
12106
12107 void
12108 ExitAnalyzeMode()
12109 {
12110     /* [DM] icsEngineAnalyze - possible call from other functions */
12111     if (appData.icsEngineAnalyze) {
12112         appData.icsEngineAnalyze = FALSE;
12113
12114         DisplayMessage("",_("Close ICS engine analyze..."));
12115     }
12116     if (first.analysisSupport && first.analyzing) {
12117       SendToProgram("exit\n", &first);
12118       first.analyzing = FALSE;
12119     }
12120     thinkOutput[0] = NULLCHAR;
12121 }
12122
12123 void
12124 EditPositionDone(Boolean fakeRights)
12125 {
12126     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12127
12128     startedFromSetupPosition = TRUE;
12129     InitChessProgram(&first, FALSE);
12130     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12131       boards[0][EP_STATUS] = EP_NONE;
12132       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12133     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12134         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12135         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12136       } else boards[0][CASTLING][2] = NoRights;
12137     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12138         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12139         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12140       } else boards[0][CASTLING][5] = NoRights;
12141     }
12142     SendToProgram("force\n", &first);
12143     if (blackPlaysFirst) {
12144         strcpy(moveList[0], "");
12145         strcpy(parseList[0], "");
12146         currentMove = forwardMostMove = backwardMostMove = 1;
12147         CopyBoard(boards[1], boards[0]);
12148     } else {
12149         currentMove = forwardMostMove = backwardMostMove = 0;
12150     }
12151     SendBoard(&first, forwardMostMove);
12152     if (appData.debugMode) {
12153         fprintf(debugFP, "EditPosDone\n");
12154     }
12155     DisplayTitle("");
12156     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12157     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12158     gameMode = EditGame;
12159     ModeHighlight();
12160     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12161     ClearHighlights(); /* [AS] */
12162 }
12163
12164 /* Pause for `ms' milliseconds */
12165 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12166 void
12167 TimeDelay(ms)
12168      long ms;
12169 {
12170     TimeMark m1, m2;
12171
12172     GetTimeMark(&m1);
12173     do {
12174         GetTimeMark(&m2);
12175     } while (SubtractTimeMarks(&m2, &m1) < ms);
12176 }
12177
12178 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12179 void
12180 SendMultiLineToICS(buf)
12181      char *buf;
12182 {
12183     char temp[MSG_SIZ+1], *p;
12184     int len;
12185
12186     len = strlen(buf);
12187     if (len > MSG_SIZ)
12188       len = MSG_SIZ;
12189   
12190     strncpy(temp, buf, len);
12191     temp[len] = 0;
12192
12193     p = temp;
12194     while (*p) {
12195         if (*p == '\n' || *p == '\r')
12196           *p = ' ';
12197         ++p;
12198     }
12199
12200     strcat(temp, "\n");
12201     SendToICS(temp);
12202     SendToPlayer(temp, strlen(temp));
12203 }
12204
12205 void
12206 SetWhiteToPlayEvent()
12207 {
12208     if (gameMode == EditPosition) {
12209         blackPlaysFirst = FALSE;
12210         DisplayBothClocks();    /* works because currentMove is 0 */
12211     } else if (gameMode == IcsExamining) {
12212         SendToICS(ics_prefix);
12213         SendToICS("tomove white\n");
12214     }
12215 }
12216
12217 void
12218 SetBlackToPlayEvent()
12219 {
12220     if (gameMode == EditPosition) {
12221         blackPlaysFirst = TRUE;
12222         currentMove = 1;        /* kludge */
12223         DisplayBothClocks();
12224         currentMove = 0;
12225     } else if (gameMode == IcsExamining) {
12226         SendToICS(ics_prefix);
12227         SendToICS("tomove black\n");
12228     }
12229 }
12230
12231 void
12232 EditPositionMenuEvent(selection, x, y)
12233      ChessSquare selection;
12234      int x, y;
12235 {
12236     char buf[MSG_SIZ];
12237     ChessSquare piece = boards[0][y][x];
12238
12239     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12240
12241     switch (selection) {
12242       case ClearBoard:
12243         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12244             SendToICS(ics_prefix);
12245             SendToICS("bsetup clear\n");
12246         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12247             SendToICS(ics_prefix);
12248             SendToICS("clearboard\n");
12249         } else {
12250             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12251                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12252                 for (y = 0; y < BOARD_HEIGHT; y++) {
12253                     if (gameMode == IcsExamining) {
12254                         if (boards[currentMove][y][x] != EmptySquare) {
12255                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12256                                     AAA + x, ONE + y);
12257                             SendToICS(buf);
12258                         }
12259                     } else {
12260                         boards[0][y][x] = p;
12261                     }
12262                 }
12263             }
12264         }
12265         if (gameMode == EditPosition) {
12266             DrawPosition(FALSE, boards[0]);
12267         }
12268         break;
12269
12270       case WhitePlay:
12271         SetWhiteToPlayEvent();
12272         break;
12273
12274       case BlackPlay:
12275         SetBlackToPlayEvent();
12276         break;
12277
12278       case EmptySquare:
12279         if (gameMode == IcsExamining) {
12280             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12281             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12282             SendToICS(buf);
12283         } else {
12284             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12285                 if(x == BOARD_LEFT-2) {
12286                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12287                     boards[0][y][1] = 0;
12288                 } else
12289                 if(x == BOARD_RGHT+1) {
12290                     if(y >= gameInfo.holdingsSize) break;
12291                     boards[0][y][BOARD_WIDTH-2] = 0;
12292                 } else break;
12293             }
12294             boards[0][y][x] = EmptySquare;
12295             DrawPosition(FALSE, boards[0]);
12296         }
12297         break;
12298
12299       case PromotePiece:
12300         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12301            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12302             selection = (ChessSquare) (PROMOTED piece);
12303         } else if(piece == EmptySquare) selection = WhiteSilver;
12304         else selection = (ChessSquare)((int)piece - 1);
12305         goto defaultlabel;
12306
12307       case DemotePiece:
12308         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12309            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12310             selection = (ChessSquare) (DEMOTED piece);
12311         } else if(piece == EmptySquare) selection = BlackSilver;
12312         else selection = (ChessSquare)((int)piece + 1);       
12313         goto defaultlabel;
12314
12315       case WhiteQueen:
12316       case BlackQueen:
12317         if(gameInfo.variant == VariantShatranj ||
12318            gameInfo.variant == VariantXiangqi  ||
12319            gameInfo.variant == VariantCourier  ||
12320            gameInfo.variant == VariantMakruk     )
12321             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12322         goto defaultlabel;
12323
12324       case WhiteKing:
12325       case BlackKing:
12326         if(gameInfo.variant == VariantXiangqi)
12327             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12328         if(gameInfo.variant == VariantKnightmate)
12329             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12330       default:
12331         defaultlabel:
12332         if (gameMode == IcsExamining) {
12333             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12334             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12335                     PieceToChar(selection), AAA + x, ONE + y);
12336             SendToICS(buf);
12337         } else {
12338             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12339                 int n;
12340                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12341                     n = PieceToNumber(selection - BlackPawn);
12342                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12343                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12344                     boards[0][BOARD_HEIGHT-1-n][1]++;
12345                 } else
12346                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12347                     n = PieceToNumber(selection);
12348                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12349                     boards[0][n][BOARD_WIDTH-1] = selection;
12350                     boards[0][n][BOARD_WIDTH-2]++;
12351                 }
12352             } else
12353             boards[0][y][x] = selection;
12354             DrawPosition(TRUE, boards[0]);
12355         }
12356         break;
12357     }
12358 }
12359
12360
12361 void
12362 DropMenuEvent(selection, x, y)
12363      ChessSquare selection;
12364      int x, y;
12365 {
12366     ChessMove moveType;
12367
12368     switch (gameMode) {
12369       case IcsPlayingWhite:
12370       case MachinePlaysBlack:
12371         if (!WhiteOnMove(currentMove)) {
12372             DisplayMoveError(_("It is Black's turn"));
12373             return;
12374         }
12375         moveType = WhiteDrop;
12376         break;
12377       case IcsPlayingBlack:
12378       case MachinePlaysWhite:
12379         if (WhiteOnMove(currentMove)) {
12380             DisplayMoveError(_("It is White's turn"));
12381             return;
12382         }
12383         moveType = BlackDrop;
12384         break;
12385       case EditGame:
12386         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12387         break;
12388       default:
12389         return;
12390     }
12391
12392     if (moveType == BlackDrop && selection < BlackPawn) {
12393       selection = (ChessSquare) ((int) selection
12394                                  + (int) BlackPawn - (int) WhitePawn);
12395     }
12396     if (boards[currentMove][y][x] != EmptySquare) {
12397         DisplayMoveError(_("That square is occupied"));
12398         return;
12399     }
12400
12401     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12402 }
12403
12404 void
12405 AcceptEvent()
12406 {
12407     /* Accept a pending offer of any kind from opponent */
12408     
12409     if (appData.icsActive) {
12410         SendToICS(ics_prefix);
12411         SendToICS("accept\n");
12412     } else if (cmailMsgLoaded) {
12413         if (currentMove == cmailOldMove &&
12414             commentList[cmailOldMove] != NULL &&
12415             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12416                    "Black offers a draw" : "White offers a draw")) {
12417             TruncateGame();
12418             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12419             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12420         } else {
12421             DisplayError(_("There is no pending offer on this move"), 0);
12422             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12423         }
12424     } else {
12425         /* Not used for offers from chess program */
12426     }
12427 }
12428
12429 void
12430 DeclineEvent()
12431 {
12432     /* Decline a pending offer of any kind from opponent */
12433     
12434     if (appData.icsActive) {
12435         SendToICS(ics_prefix);
12436         SendToICS("decline\n");
12437     } else if (cmailMsgLoaded) {
12438         if (currentMove == cmailOldMove &&
12439             commentList[cmailOldMove] != NULL &&
12440             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12441                    "Black offers a draw" : "White offers a draw")) {
12442 #ifdef NOTDEF
12443             AppendComment(cmailOldMove, "Draw declined", TRUE);
12444             DisplayComment(cmailOldMove - 1, "Draw declined");
12445 #endif /*NOTDEF*/
12446         } else {
12447             DisplayError(_("There is no pending offer on this move"), 0);
12448         }
12449     } else {
12450         /* Not used for offers from chess program */
12451     }
12452 }
12453
12454 void
12455 RematchEvent()
12456 {
12457     /* Issue ICS rematch command */
12458     if (appData.icsActive) {
12459         SendToICS(ics_prefix);
12460         SendToICS("rematch\n");
12461     }
12462 }
12463
12464 void
12465 CallFlagEvent()
12466 {
12467     /* Call your opponent's flag (claim a win on time) */
12468     if (appData.icsActive) {
12469         SendToICS(ics_prefix);
12470         SendToICS("flag\n");
12471     } else {
12472         switch (gameMode) {
12473           default:
12474             return;
12475           case MachinePlaysWhite:
12476             if (whiteFlag) {
12477                 if (blackFlag)
12478                   GameEnds(GameIsDrawn, "Both players ran out of time",
12479                            GE_PLAYER);
12480                 else
12481                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12482             } else {
12483                 DisplayError(_("Your opponent is not out of time"), 0);
12484             }
12485             break;
12486           case MachinePlaysBlack:
12487             if (blackFlag) {
12488                 if (whiteFlag)
12489                   GameEnds(GameIsDrawn, "Both players ran out of time",
12490                            GE_PLAYER);
12491                 else
12492                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12493             } else {
12494                 DisplayError(_("Your opponent is not out of time"), 0);
12495             }
12496             break;
12497         }
12498     }
12499 }
12500
12501 void
12502 DrawEvent()
12503 {
12504     /* Offer draw or accept pending draw offer from opponent */
12505     
12506     if (appData.icsActive) {
12507         /* Note: tournament rules require draw offers to be
12508            made after you make your move but before you punch
12509            your clock.  Currently ICS doesn't let you do that;
12510            instead, you immediately punch your clock after making
12511            a move, but you can offer a draw at any time. */
12512         
12513         SendToICS(ics_prefix);
12514         SendToICS("draw\n");
12515         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12516     } else if (cmailMsgLoaded) {
12517         if (currentMove == cmailOldMove &&
12518             commentList[cmailOldMove] != NULL &&
12519             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12520                    "Black offers a draw" : "White offers a draw")) {
12521             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12522             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12523         } else if (currentMove == cmailOldMove + 1) {
12524             char *offer = WhiteOnMove(cmailOldMove) ?
12525               "White offers a draw" : "Black offers a draw";
12526             AppendComment(currentMove, offer, TRUE);
12527             DisplayComment(currentMove - 1, offer);
12528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12529         } else {
12530             DisplayError(_("You must make your move before offering a draw"), 0);
12531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12532         }
12533     } else if (first.offeredDraw) {
12534         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12535     } else {
12536         if (first.sendDrawOffers) {
12537             SendToProgram("draw\n", &first);
12538             userOfferedDraw = TRUE;
12539         }
12540     }
12541 }
12542
12543 void
12544 AdjournEvent()
12545 {
12546     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12547     
12548     if (appData.icsActive) {
12549         SendToICS(ics_prefix);
12550         SendToICS("adjourn\n");
12551     } else {
12552         /* Currently GNU Chess doesn't offer or accept Adjourns */
12553     }
12554 }
12555
12556
12557 void
12558 AbortEvent()
12559 {
12560     /* Offer Abort or accept pending Abort offer from opponent */
12561     
12562     if (appData.icsActive) {
12563         SendToICS(ics_prefix);
12564         SendToICS("abort\n");
12565     } else {
12566         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12567     }
12568 }
12569
12570 void
12571 ResignEvent()
12572 {
12573     /* Resign.  You can do this even if it's not your turn. */
12574     
12575     if (appData.icsActive) {
12576         SendToICS(ics_prefix);
12577         SendToICS("resign\n");
12578     } else {
12579         switch (gameMode) {
12580           case MachinePlaysWhite:
12581             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12582             break;
12583           case MachinePlaysBlack:
12584             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12585             break;
12586           case EditGame:
12587             if (cmailMsgLoaded) {
12588                 TruncateGame();
12589                 if (WhiteOnMove(cmailOldMove)) {
12590                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12591                 } else {
12592                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12593                 }
12594                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12595             }
12596             break;
12597           default:
12598             break;
12599         }
12600     }
12601 }
12602
12603
12604 void
12605 StopObservingEvent()
12606 {
12607     /* Stop observing current games */
12608     SendToICS(ics_prefix);
12609     SendToICS("unobserve\n");
12610 }
12611
12612 void
12613 StopExaminingEvent()
12614 {
12615     /* Stop observing current game */
12616     SendToICS(ics_prefix);
12617     SendToICS("unexamine\n");
12618 }
12619
12620 void
12621 ForwardInner(target)
12622      int target;
12623 {
12624     int limit;
12625
12626     if (appData.debugMode)
12627         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12628                 target, currentMove, forwardMostMove);
12629
12630     if (gameMode == EditPosition)
12631       return;
12632
12633     if (gameMode == PlayFromGameFile && !pausing)
12634       PauseEvent();
12635     
12636     if (gameMode == IcsExamining && pausing)
12637       limit = pauseExamForwardMostMove;
12638     else
12639       limit = forwardMostMove;
12640     
12641     if (target > limit) target = limit;
12642
12643     if (target > 0 && moveList[target - 1][0]) {
12644         int fromX, fromY, toX, toY;
12645         toX = moveList[target - 1][2] - AAA;
12646         toY = moveList[target - 1][3] - ONE;
12647         if (moveList[target - 1][1] == '@') {
12648             if (appData.highlightLastMove) {
12649                 SetHighlights(-1, -1, toX, toY);
12650             }
12651         } else {
12652             fromX = moveList[target - 1][0] - AAA;
12653             fromY = moveList[target - 1][1] - ONE;
12654             if (target == currentMove + 1) {
12655                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12656             }
12657             if (appData.highlightLastMove) {
12658                 SetHighlights(fromX, fromY, toX, toY);
12659             }
12660         }
12661     }
12662     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12663         gameMode == Training || gameMode == PlayFromGameFile || 
12664         gameMode == AnalyzeFile) {
12665         while (currentMove < target) {
12666             SendMoveToProgram(currentMove++, &first);
12667         }
12668     } else {
12669         currentMove = target;
12670     }
12671     
12672     if (gameMode == EditGame || gameMode == EndOfGame) {
12673         whiteTimeRemaining = timeRemaining[0][currentMove];
12674         blackTimeRemaining = timeRemaining[1][currentMove];
12675     }
12676     DisplayBothClocks();
12677     DisplayMove(currentMove - 1);
12678     DrawPosition(FALSE, boards[currentMove]);
12679     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12680     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12681         DisplayComment(currentMove - 1, commentList[currentMove]);
12682     }
12683 }
12684
12685
12686 void
12687 ForwardEvent()
12688 {
12689     if (gameMode == IcsExamining && !pausing) {
12690         SendToICS(ics_prefix);
12691         SendToICS("forward\n");
12692     } else {
12693         ForwardInner(currentMove + 1);
12694     }
12695 }
12696
12697 void
12698 ToEndEvent()
12699 {
12700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12701         /* to optimze, we temporarily turn off analysis mode while we feed
12702          * the remaining moves to the engine. Otherwise we get analysis output
12703          * after each move.
12704          */ 
12705         if (first.analysisSupport) {
12706           SendToProgram("exit\nforce\n", &first);
12707           first.analyzing = FALSE;
12708         }
12709     }
12710         
12711     if (gameMode == IcsExamining && !pausing) {
12712         SendToICS(ics_prefix);
12713         SendToICS("forward 999999\n");
12714     } else {
12715         ForwardInner(forwardMostMove);
12716     }
12717
12718     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12719         /* we have fed all the moves, so reactivate analysis mode */
12720         SendToProgram("analyze\n", &first);
12721         first.analyzing = TRUE;
12722         /*first.maybeThinking = TRUE;*/
12723         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12724     }
12725 }
12726
12727 void
12728 BackwardInner(target)
12729      int target;
12730 {
12731     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12732
12733     if (appData.debugMode)
12734         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12735                 target, currentMove, forwardMostMove);
12736
12737     if (gameMode == EditPosition) return;
12738     if (currentMove <= backwardMostMove) {
12739         ClearHighlights();
12740         DrawPosition(full_redraw, boards[currentMove]);
12741         return;
12742     }
12743     if (gameMode == PlayFromGameFile && !pausing)
12744       PauseEvent();
12745     
12746     if (moveList[target][0]) {
12747         int fromX, fromY, toX, toY;
12748         toX = moveList[target][2] - AAA;
12749         toY = moveList[target][3] - ONE;
12750         if (moveList[target][1] == '@') {
12751             if (appData.highlightLastMove) {
12752                 SetHighlights(-1, -1, toX, toY);
12753             }
12754         } else {
12755             fromX = moveList[target][0] - AAA;
12756             fromY = moveList[target][1] - ONE;
12757             if (target == currentMove - 1) {
12758                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12759             }
12760             if (appData.highlightLastMove) {
12761                 SetHighlights(fromX, fromY, toX, toY);
12762             }
12763         }
12764     }
12765     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12766         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12767         while (currentMove > target) {
12768             SendToProgram("undo\n", &first);
12769             currentMove--;
12770         }
12771     } else {
12772         currentMove = target;
12773     }
12774     
12775     if (gameMode == EditGame || gameMode == EndOfGame) {
12776         whiteTimeRemaining = timeRemaining[0][currentMove];
12777         blackTimeRemaining = timeRemaining[1][currentMove];
12778     }
12779     DisplayBothClocks();
12780     DisplayMove(currentMove - 1);
12781     DrawPosition(full_redraw, boards[currentMove]);
12782     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12783     // [HGM] PV info: routine tests if comment empty
12784     DisplayComment(currentMove - 1, commentList[currentMove]);
12785 }
12786
12787 void
12788 BackwardEvent()
12789 {
12790     if (gameMode == IcsExamining && !pausing) {
12791         SendToICS(ics_prefix);
12792         SendToICS("backward\n");
12793     } else {
12794         BackwardInner(currentMove - 1);
12795     }
12796 }
12797
12798 void
12799 ToStartEvent()
12800 {
12801     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12802         /* to optimize, we temporarily turn off analysis mode while we undo
12803          * all the moves. Otherwise we get analysis output after each undo.
12804          */ 
12805         if (first.analysisSupport) {
12806           SendToProgram("exit\nforce\n", &first);
12807           first.analyzing = FALSE;
12808         }
12809     }
12810
12811     if (gameMode == IcsExamining && !pausing) {
12812         SendToICS(ics_prefix);
12813         SendToICS("backward 999999\n");
12814     } else {
12815         BackwardInner(backwardMostMove);
12816     }
12817
12818     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12819         /* we have fed all the moves, so reactivate analysis mode */
12820         SendToProgram("analyze\n", &first);
12821         first.analyzing = TRUE;
12822         /*first.maybeThinking = TRUE;*/
12823         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12824     }
12825 }
12826
12827 void
12828 ToNrEvent(int to)
12829 {
12830   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12831   if (to >= forwardMostMove) to = forwardMostMove;
12832   if (to <= backwardMostMove) to = backwardMostMove;
12833   if (to < currentMove) {
12834     BackwardInner(to);
12835   } else {
12836     ForwardInner(to);
12837   }
12838 }
12839
12840 void
12841 RevertEvent(Boolean annotate)
12842 {
12843     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12844         return;
12845     }
12846     if (gameMode != IcsExamining) {
12847         DisplayError(_("You are not examining a game"), 0);
12848         return;
12849     }
12850     if (pausing) {
12851         DisplayError(_("You can't revert while pausing"), 0);
12852         return;
12853     }
12854     SendToICS(ics_prefix);
12855     SendToICS("revert\n");
12856 }
12857
12858 void
12859 RetractMoveEvent()
12860 {
12861     switch (gameMode) {
12862       case MachinePlaysWhite:
12863       case MachinePlaysBlack:
12864         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12865             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12866             return;
12867         }
12868         if (forwardMostMove < 2) return;
12869         currentMove = forwardMostMove = forwardMostMove - 2;
12870         whiteTimeRemaining = timeRemaining[0][currentMove];
12871         blackTimeRemaining = timeRemaining[1][currentMove];
12872         DisplayBothClocks();
12873         DisplayMove(currentMove - 1);
12874         ClearHighlights();/*!! could figure this out*/
12875         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12876         SendToProgram("remove\n", &first);
12877         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12878         break;
12879
12880       case BeginningOfGame:
12881       default:
12882         break;
12883
12884       case IcsPlayingWhite:
12885       case IcsPlayingBlack:
12886         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12887             SendToICS(ics_prefix);
12888             SendToICS("takeback 2\n");
12889         } else {
12890             SendToICS(ics_prefix);
12891             SendToICS("takeback 1\n");
12892         }
12893         break;
12894     }
12895 }
12896
12897 void
12898 MoveNowEvent()
12899 {
12900     ChessProgramState *cps;
12901
12902     switch (gameMode) {
12903       case MachinePlaysWhite:
12904         if (!WhiteOnMove(forwardMostMove)) {
12905             DisplayError(_("It is your turn"), 0);
12906             return;
12907         }
12908         cps = &first;
12909         break;
12910       case MachinePlaysBlack:
12911         if (WhiteOnMove(forwardMostMove)) {
12912             DisplayError(_("It is your turn"), 0);
12913             return;
12914         }
12915         cps = &first;
12916         break;
12917       case TwoMachinesPlay:
12918         if (WhiteOnMove(forwardMostMove) ==
12919             (first.twoMachinesColor[0] == 'w')) {
12920             cps = &first;
12921         } else {
12922             cps = &second;
12923         }
12924         break;
12925       case BeginningOfGame:
12926       default:
12927         return;
12928     }
12929     SendToProgram("?\n", cps);
12930 }
12931
12932 void
12933 TruncateGameEvent()
12934 {
12935     EditGameEvent();
12936     if (gameMode != EditGame) return;
12937     TruncateGame();
12938 }
12939
12940 void
12941 TruncateGame()
12942 {
12943     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12944     if (forwardMostMove > currentMove) {
12945         if (gameInfo.resultDetails != NULL) {
12946             free(gameInfo.resultDetails);
12947             gameInfo.resultDetails = NULL;
12948             gameInfo.result = GameUnfinished;
12949         }
12950         forwardMostMove = currentMove;
12951         HistorySet(parseList, backwardMostMove, forwardMostMove,
12952                    currentMove-1);
12953     }
12954 }
12955
12956 void
12957 HintEvent()
12958 {
12959     if (appData.noChessProgram) return;
12960     switch (gameMode) {
12961       case MachinePlaysWhite:
12962         if (WhiteOnMove(forwardMostMove)) {
12963             DisplayError(_("Wait until your turn"), 0);
12964             return;
12965         }
12966         break;
12967       case BeginningOfGame:
12968       case MachinePlaysBlack:
12969         if (!WhiteOnMove(forwardMostMove)) {
12970             DisplayError(_("Wait until your turn"), 0);
12971             return;
12972         }
12973         break;
12974       default:
12975         DisplayError(_("No hint available"), 0);
12976         return;
12977     }
12978     SendToProgram("hint\n", &first);
12979     hintRequested = TRUE;
12980 }
12981
12982 void
12983 BookEvent()
12984 {
12985     if (appData.noChessProgram) return;
12986     switch (gameMode) {
12987       case MachinePlaysWhite:
12988         if (WhiteOnMove(forwardMostMove)) {
12989             DisplayError(_("Wait until your turn"), 0);
12990             return;
12991         }
12992         break;
12993       case BeginningOfGame:
12994       case MachinePlaysBlack:
12995         if (!WhiteOnMove(forwardMostMove)) {
12996             DisplayError(_("Wait until your turn"), 0);
12997             return;
12998         }
12999         break;
13000       case EditPosition:
13001         EditPositionDone(TRUE);
13002         break;
13003       case TwoMachinesPlay:
13004         return;
13005       default:
13006         break;
13007     }
13008     SendToProgram("bk\n", &first);
13009     bookOutput[0] = NULLCHAR;
13010     bookRequested = TRUE;
13011 }
13012
13013 void
13014 AboutGameEvent()
13015 {
13016     char *tags = PGNTags(&gameInfo);
13017     TagsPopUp(tags, CmailMsg());
13018     free(tags);
13019 }
13020
13021 /* end button procedures */
13022
13023 void
13024 PrintPosition(fp, move)
13025      FILE *fp;
13026      int move;
13027 {
13028     int i, j;
13029     
13030     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13031         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13032             char c = PieceToChar(boards[move][i][j]);
13033             fputc(c == 'x' ? '.' : c, fp);
13034             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13035         }
13036     }
13037     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13038       fprintf(fp, "white to play\n");
13039     else
13040       fprintf(fp, "black to play\n");
13041 }
13042
13043 void
13044 PrintOpponents(fp)
13045      FILE *fp;
13046 {
13047     if (gameInfo.white != NULL) {
13048         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13049     } else {
13050         fprintf(fp, "\n");
13051     }
13052 }
13053
13054 /* Find last component of program's own name, using some heuristics */
13055 void
13056 TidyProgramName(prog, host, buf)
13057      char *prog, *host, buf[MSG_SIZ];
13058 {
13059     char *p, *q;
13060     int local = (strcmp(host, "localhost") == 0);
13061     while (!local && (p = strchr(prog, ';')) != NULL) {
13062         p++;
13063         while (*p == ' ') p++;
13064         prog = p;
13065     }
13066     if (*prog == '"' || *prog == '\'') {
13067         q = strchr(prog + 1, *prog);
13068     } else {
13069         q = strchr(prog, ' ');
13070     }
13071     if (q == NULL) q = prog + strlen(prog);
13072     p = q;
13073     while (p >= prog && *p != '/' && *p != '\\') p--;
13074     p++;
13075     if(p == prog && *p == '"') p++;
13076     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13077     memcpy(buf, p, q - p);
13078     buf[q - p] = NULLCHAR;
13079     if (!local) {
13080         strcat(buf, "@");
13081         strcat(buf, host);
13082     }
13083 }
13084
13085 char *
13086 TimeControlTagValue()
13087 {
13088     char buf[MSG_SIZ];
13089     if (!appData.clockMode) {
13090         strcpy(buf, "-");
13091     } else if (movesPerSession > 0) {
13092         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13093     } else if (timeIncrement == 0) {
13094         sprintf(buf, "%ld", timeControl/1000);
13095     } else {
13096         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13097     }
13098     return StrSave(buf);
13099 }
13100
13101 void
13102 SetGameInfo()
13103 {
13104     /* This routine is used only for certain modes */
13105     VariantClass v = gameInfo.variant;
13106     ChessMove r = GameUnfinished;
13107     char *p = NULL;
13108
13109     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13110         r = gameInfo.result; 
13111         p = gameInfo.resultDetails; 
13112         gameInfo.resultDetails = NULL;
13113     }
13114     ClearGameInfo(&gameInfo);
13115     gameInfo.variant = v;
13116
13117     switch (gameMode) {
13118       case MachinePlaysWhite:
13119         gameInfo.event = StrSave( appData.pgnEventHeader );
13120         gameInfo.site = StrSave(HostName());
13121         gameInfo.date = PGNDate();
13122         gameInfo.round = StrSave("-");
13123         gameInfo.white = StrSave(first.tidy);
13124         gameInfo.black = StrSave(UserName());
13125         gameInfo.timeControl = TimeControlTagValue();
13126         break;
13127
13128       case MachinePlaysBlack:
13129         gameInfo.event = StrSave( appData.pgnEventHeader );
13130         gameInfo.site = StrSave(HostName());
13131         gameInfo.date = PGNDate();
13132         gameInfo.round = StrSave("-");
13133         gameInfo.white = StrSave(UserName());
13134         gameInfo.black = StrSave(first.tidy);
13135         gameInfo.timeControl = TimeControlTagValue();
13136         break;
13137
13138       case TwoMachinesPlay:
13139         gameInfo.event = StrSave( appData.pgnEventHeader );
13140         gameInfo.site = StrSave(HostName());
13141         gameInfo.date = PGNDate();
13142         if (matchGame > 0) {
13143             char buf[MSG_SIZ];
13144             sprintf(buf, "%d", matchGame);
13145             gameInfo.round = StrSave(buf);
13146         } else {
13147             gameInfo.round = StrSave("-");
13148         }
13149         if (first.twoMachinesColor[0] == 'w') {
13150             gameInfo.white = StrSave(first.tidy);
13151             gameInfo.black = StrSave(second.tidy);
13152         } else {
13153             gameInfo.white = StrSave(second.tidy);
13154             gameInfo.black = StrSave(first.tidy);
13155         }
13156         gameInfo.timeControl = TimeControlTagValue();
13157         break;
13158
13159       case EditGame:
13160         gameInfo.event = StrSave("Edited game");
13161         gameInfo.site = StrSave(HostName());
13162         gameInfo.date = PGNDate();
13163         gameInfo.round = StrSave("-");
13164         gameInfo.white = StrSave("-");
13165         gameInfo.black = StrSave("-");
13166         gameInfo.result = r;
13167         gameInfo.resultDetails = p;
13168         break;
13169
13170       case EditPosition:
13171         gameInfo.event = StrSave("Edited position");
13172         gameInfo.site = StrSave(HostName());
13173         gameInfo.date = PGNDate();
13174         gameInfo.round = StrSave("-");
13175         gameInfo.white = StrSave("-");
13176         gameInfo.black = StrSave("-");
13177         break;
13178
13179       case IcsPlayingWhite:
13180       case IcsPlayingBlack:
13181       case IcsObserving:
13182       case IcsExamining:
13183         break;
13184
13185       case PlayFromGameFile:
13186         gameInfo.event = StrSave("Game from non-PGN file");
13187         gameInfo.site = StrSave(HostName());
13188         gameInfo.date = PGNDate();
13189         gameInfo.round = StrSave("-");
13190         gameInfo.white = StrSave("?");
13191         gameInfo.black = StrSave("?");
13192         break;
13193
13194       default:
13195         break;
13196     }
13197 }
13198
13199 void
13200 ReplaceComment(index, text)
13201      int index;
13202      char *text;
13203 {
13204     int len;
13205
13206     while (*text == '\n') text++;
13207     len = strlen(text);
13208     while (len > 0 && text[len - 1] == '\n') len--;
13209
13210     if (commentList[index] != NULL)
13211       free(commentList[index]);
13212
13213     if (len == 0) {
13214         commentList[index] = NULL;
13215         return;
13216     }
13217   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13218       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13219       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13220     commentList[index] = (char *) malloc(len + 2);
13221     strncpy(commentList[index], text, len);
13222     commentList[index][len] = '\n';
13223     commentList[index][len + 1] = NULLCHAR;
13224   } else { 
13225     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13226     char *p;
13227     commentList[index] = (char *) malloc(len + 6);
13228     strcpy(commentList[index], "{\n");
13229     strncpy(commentList[index]+2, text, len);
13230     commentList[index][len+2] = NULLCHAR;
13231     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13232     strcat(commentList[index], "\n}\n");
13233   }
13234 }
13235
13236 void
13237 CrushCRs(text)
13238      char *text;
13239 {
13240   char *p = text;
13241   char *q = text;
13242   char ch;
13243
13244   do {
13245     ch = *p++;
13246     if (ch == '\r') continue;
13247     *q++ = ch;
13248   } while (ch != '\0');
13249 }
13250
13251 void
13252 AppendComment(index, text, addBraces)
13253      int index;
13254      char *text;
13255      Boolean addBraces; // [HGM] braces: tells if we should add {}
13256 {
13257     int oldlen, len;
13258     char *old;
13259
13260 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13261     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13262
13263     CrushCRs(text);
13264     while (*text == '\n') text++;
13265     len = strlen(text);
13266     while (len > 0 && text[len - 1] == '\n') len--;
13267
13268     if (len == 0) return;
13269
13270     if (commentList[index] != NULL) {
13271         old = commentList[index];
13272         oldlen = strlen(old);
13273         while(commentList[index][oldlen-1] ==  '\n')
13274           commentList[index][--oldlen] = NULLCHAR;
13275         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13276         strcpy(commentList[index], old);
13277         free(old);
13278         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13279         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13280           if(addBraces) addBraces = FALSE; else { text++; len--; }
13281           while (*text == '\n') { text++; len--; }
13282           commentList[index][--oldlen] = NULLCHAR;
13283       }
13284         if(addBraces) strcat(commentList[index], "\n{\n");
13285         else          strcat(commentList[index], "\n");
13286         strcat(commentList[index], text);
13287         if(addBraces) strcat(commentList[index], "\n}\n");
13288         else          strcat(commentList[index], "\n");
13289     } else {
13290         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13291         if(addBraces)
13292              strcpy(commentList[index], "{\n");
13293         else commentList[index][0] = NULLCHAR;
13294         strcat(commentList[index], text);
13295         strcat(commentList[index], "\n");
13296         if(addBraces) strcat(commentList[index], "}\n");
13297     }
13298 }
13299
13300 static char * FindStr( char * text, char * sub_text )
13301 {
13302     char * result = strstr( text, sub_text );
13303
13304     if( result != NULL ) {
13305         result += strlen( sub_text );
13306     }
13307
13308     return result;
13309 }
13310
13311 /* [AS] Try to extract PV info from PGN comment */
13312 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13313 char *GetInfoFromComment( int index, char * text )
13314 {
13315     char * sep = text;
13316
13317     if( text != NULL && index > 0 ) {
13318         int score = 0;
13319         int depth = 0;
13320         int time = -1, sec = 0, deci;
13321         char * s_eval = FindStr( text, "[%eval " );
13322         char * s_emt = FindStr( text, "[%emt " );
13323
13324         if( s_eval != NULL || s_emt != NULL ) {
13325             /* New style */
13326             char delim;
13327
13328             if( s_eval != NULL ) {
13329                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13330                     return text;
13331                 }
13332
13333                 if( delim != ']' ) {
13334                     return text;
13335                 }
13336             }
13337
13338             if( s_emt != NULL ) {
13339             }
13340                 return text;
13341         }
13342         else {
13343             /* We expect something like: [+|-]nnn.nn/dd */
13344             int score_lo = 0;
13345
13346             if(*text != '{') return text; // [HGM] braces: must be normal comment
13347
13348             sep = strchr( text, '/' );
13349             if( sep == NULL || sep < (text+4) ) {
13350                 return text;
13351             }
13352
13353             time = -1; sec = -1; deci = -1;
13354             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13355                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13356                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13357                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13358                 return text;
13359             }
13360
13361             if( score_lo < 0 || score_lo >= 100 ) {
13362                 return text;
13363             }
13364
13365             if(sec >= 0) time = 600*time + 10*sec; else
13366             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13367
13368             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13369
13370             /* [HGM] PV time: now locate end of PV info */
13371             while( *++sep >= '0' && *sep <= '9'); // strip depth
13372             if(time >= 0)
13373             while( *++sep >= '0' && *sep <= '9'); // strip time
13374             if(sec >= 0)
13375             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13376             if(deci >= 0)
13377             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13378             while(*sep == ' ') sep++;
13379         }
13380
13381         if( depth <= 0 ) {
13382             return text;
13383         }
13384
13385         if( time < 0 ) {
13386             time = -1;
13387         }
13388
13389         pvInfoList[index-1].depth = depth;
13390         pvInfoList[index-1].score = score;
13391         pvInfoList[index-1].time  = 10*time; // centi-sec
13392         if(*sep == '}') *sep = 0; else *--sep = '{';
13393     }
13394     return sep;
13395 }
13396
13397 void
13398 SendToProgram(message, cps)
13399      char *message;
13400      ChessProgramState *cps;
13401 {
13402     int count, outCount, error;
13403     char buf[MSG_SIZ];
13404
13405     if (cps->pr == NULL) return;
13406     Attention(cps);
13407     
13408     if (appData.debugMode) {
13409         TimeMark now;
13410         GetTimeMark(&now);
13411         fprintf(debugFP, "%ld >%-6s: %s", 
13412                 SubtractTimeMarks(&now, &programStartTime),
13413                 cps->which, message);
13414     }
13415     
13416     count = strlen(message);
13417     outCount = OutputToProcess(cps->pr, message, count, &error);
13418     if (outCount < count && !exiting 
13419                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13420         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13421         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13422             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13423                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13424                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13425             } else {
13426                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13427             }
13428             gameInfo.resultDetails = StrSave(buf);
13429         }
13430         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13431     }
13432 }
13433
13434 void
13435 ReceiveFromProgram(isr, closure, message, count, error)
13436      InputSourceRef isr;
13437      VOIDSTAR closure;
13438      char *message;
13439      int count;
13440      int error;
13441 {
13442     char *end_str;
13443     char buf[MSG_SIZ];
13444     ChessProgramState *cps = (ChessProgramState *)closure;
13445
13446     if (isr != cps->isr) return; /* Killed intentionally */
13447     if (count <= 0) {
13448         if (count == 0) {
13449             sprintf(buf,
13450                     _("Error: %s chess program (%s) exited unexpectedly"),
13451                     cps->which, cps->program);
13452         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13453                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13454                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13455                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13456                 } else {
13457                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13458                 }
13459                 gameInfo.resultDetails = StrSave(buf);
13460             }
13461             RemoveInputSource(cps->isr);
13462             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13463         } else {
13464             sprintf(buf,
13465                     _("Error reading from %s chess program (%s)"),
13466                     cps->which, cps->program);
13467             RemoveInputSource(cps->isr);
13468
13469             /* [AS] Program is misbehaving badly... kill it */
13470             if( count == -2 ) {
13471                 DestroyChildProcess( cps->pr, 9 );
13472                 cps->pr = NoProc;
13473             }
13474
13475             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13476         }
13477         return;
13478     }
13479     
13480     if ((end_str = strchr(message, '\r')) != NULL)
13481       *end_str = NULLCHAR;
13482     if ((end_str = strchr(message, '\n')) != NULL)
13483       *end_str = NULLCHAR;
13484     
13485     if (appData.debugMode) {
13486         TimeMark now; int print = 1;
13487         char *quote = ""; char c; int i;
13488
13489         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13490                 char start = message[0];
13491                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13492                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13493                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13494                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13495                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13496                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13497                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13498                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13499                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13500                     print = (appData.engineComments >= 2);
13501                 }
13502                 message[0] = start; // restore original message
13503         }
13504         if(print) {
13505                 GetTimeMark(&now);
13506                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13507                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13508                         quote,
13509                         message);
13510         }
13511     }
13512
13513     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13514     if (appData.icsEngineAnalyze) {
13515         if (strstr(message, "whisper") != NULL ||
13516              strstr(message, "kibitz") != NULL || 
13517             strstr(message, "tellics") != NULL) return;
13518     }
13519
13520     HandleMachineMove(message, cps);
13521 }
13522
13523
13524 void
13525 SendTimeControl(cps, mps, tc, inc, sd, st)
13526      ChessProgramState *cps;
13527      int mps, inc, sd, st;
13528      long tc;
13529 {
13530     char buf[MSG_SIZ];
13531     int seconds;
13532
13533     if( timeControl_2 > 0 ) {
13534         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13535             tc = timeControl_2;
13536         }
13537     }
13538     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13539     inc /= cps->timeOdds;
13540     st  /= cps->timeOdds;
13541
13542     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13543
13544     if (st > 0) {
13545       /* Set exact time per move, normally using st command */
13546       if (cps->stKludge) {
13547         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13548         seconds = st % 60;
13549         if (seconds == 0) {
13550           sprintf(buf, "level 1 %d\n", st/60);
13551         } else {
13552           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13553         }
13554       } else {
13555         sprintf(buf, "st %d\n", st);
13556       }
13557     } else {
13558       /* Set conventional or incremental time control, using level command */
13559       if (seconds == 0) {
13560         /* Note old gnuchess bug -- minutes:seconds used to not work.
13561            Fixed in later versions, but still avoid :seconds
13562            when seconds is 0. */
13563         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13564       } else {
13565         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13566                 seconds, inc/1000);
13567       }
13568     }
13569     SendToProgram(buf, cps);
13570
13571     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13572     /* Orthogonally, limit search to given depth */
13573     if (sd > 0) {
13574       if (cps->sdKludge) {
13575         sprintf(buf, "depth\n%d\n", sd);
13576       } else {
13577         sprintf(buf, "sd %d\n", sd);
13578       }
13579       SendToProgram(buf, cps);
13580     }
13581
13582     if(cps->nps > 0) { /* [HGM] nps */
13583         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13584         else {
13585                 sprintf(buf, "nps %d\n", cps->nps);
13586               SendToProgram(buf, cps);
13587         }
13588     }
13589 }
13590
13591 ChessProgramState *WhitePlayer()
13592 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13593 {
13594     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13595        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13596         return &second;
13597     return &first;
13598 }
13599
13600 void
13601 SendTimeRemaining(cps, machineWhite)
13602      ChessProgramState *cps;
13603      int /*boolean*/ machineWhite;
13604 {
13605     char message[MSG_SIZ];
13606     long time, otime;
13607
13608     /* Note: this routine must be called when the clocks are stopped
13609        or when they have *just* been set or switched; otherwise
13610        it will be off by the time since the current tick started.
13611     */
13612     if (machineWhite) {
13613         time = whiteTimeRemaining / 10;
13614         otime = blackTimeRemaining / 10;
13615     } else {
13616         time = blackTimeRemaining / 10;
13617         otime = whiteTimeRemaining / 10;
13618     }
13619     /* [HGM] translate opponent's time by time-odds factor */
13620     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13621     if (appData.debugMode) {
13622         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13623     }
13624
13625     if (time <= 0) time = 1;
13626     if (otime <= 0) otime = 1;
13627     
13628     sprintf(message, "time %ld\n", time);
13629     SendToProgram(message, cps);
13630
13631     sprintf(message, "otim %ld\n", otime);
13632     SendToProgram(message, cps);
13633 }
13634
13635 int
13636 BoolFeature(p, name, loc, cps)
13637      char **p;
13638      char *name;
13639      int *loc;
13640      ChessProgramState *cps;
13641 {
13642   char buf[MSG_SIZ];
13643   int len = strlen(name);
13644   int val;
13645   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13646     (*p) += len + 1;
13647     sscanf(*p, "%d", &val);
13648     *loc = (val != 0);
13649     while (**p && **p != ' ') (*p)++;
13650     sprintf(buf, "accepted %s\n", name);
13651     SendToProgram(buf, cps);
13652     return TRUE;
13653   }
13654   return FALSE;
13655 }
13656
13657 int
13658 IntFeature(p, name, loc, cps)
13659      char **p;
13660      char *name;
13661      int *loc;
13662      ChessProgramState *cps;
13663 {
13664   char buf[MSG_SIZ];
13665   int len = strlen(name);
13666   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13667     (*p) += len + 1;
13668     sscanf(*p, "%d", loc);
13669     while (**p && **p != ' ') (*p)++;
13670     sprintf(buf, "accepted %s\n", name);
13671     SendToProgram(buf, cps);
13672     return TRUE;
13673   }
13674   return FALSE;
13675 }
13676
13677 int
13678 StringFeature(p, name, loc, cps)
13679      char **p;
13680      char *name;
13681      char loc[];
13682      ChessProgramState *cps;
13683 {
13684   char buf[MSG_SIZ];
13685   int len = strlen(name);
13686   if (strncmp((*p), name, len) == 0
13687       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13688     (*p) += len + 2;
13689     sscanf(*p, "%[^\"]", loc);
13690     while (**p && **p != '\"') (*p)++;
13691     if (**p == '\"') (*p)++;
13692     sprintf(buf, "accepted %s\n", name);
13693     SendToProgram(buf, cps);
13694     return TRUE;
13695   }
13696   return FALSE;
13697 }
13698
13699 int 
13700 ParseOption(Option *opt, ChessProgramState *cps)
13701 // [HGM] options: process the string that defines an engine option, and determine
13702 // name, type, default value, and allowed value range
13703 {
13704         char *p, *q, buf[MSG_SIZ];
13705         int n, min = (-1)<<31, max = 1<<31, def;
13706
13707         if(p = strstr(opt->name, " -spin ")) {
13708             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13709             if(max < min) max = min; // enforce consistency
13710             if(def < min) def = min;
13711             if(def > max) def = max;
13712             opt->value = def;
13713             opt->min = min;
13714             opt->max = max;
13715             opt->type = Spin;
13716         } else if((p = strstr(opt->name, " -slider "))) {
13717             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13718             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13719             if(max < min) max = min; // enforce consistency
13720             if(def < min) def = min;
13721             if(def > max) def = max;
13722             opt->value = def;
13723             opt->min = min;
13724             opt->max = max;
13725             opt->type = Spin; // Slider;
13726         } else if((p = strstr(opt->name, " -string "))) {
13727             opt->textValue = p+9;
13728             opt->type = TextBox;
13729         } else if((p = strstr(opt->name, " -file "))) {
13730             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13731             opt->textValue = p+7;
13732             opt->type = TextBox; // FileName;
13733         } else if((p = strstr(opt->name, " -path "))) {
13734             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13735             opt->textValue = p+7;
13736             opt->type = TextBox; // PathName;
13737         } else if(p = strstr(opt->name, " -check ")) {
13738             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13739             opt->value = (def != 0);
13740             opt->type = CheckBox;
13741         } else if(p = strstr(opt->name, " -combo ")) {
13742             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13743             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13744             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13745             opt->value = n = 0;
13746             while(q = StrStr(q, " /// ")) {
13747                 n++; *q = 0;    // count choices, and null-terminate each of them
13748                 q += 5;
13749                 if(*q == '*') { // remember default, which is marked with * prefix
13750                     q++;
13751                     opt->value = n;
13752                 }
13753                 cps->comboList[cps->comboCnt++] = q;
13754             }
13755             cps->comboList[cps->comboCnt++] = NULL;
13756             opt->max = n + 1;
13757             opt->type = ComboBox;
13758         } else if(p = strstr(opt->name, " -button")) {
13759             opt->type = Button;
13760         } else if(p = strstr(opt->name, " -save")) {
13761             opt->type = SaveButton;
13762         } else return FALSE;
13763         *p = 0; // terminate option name
13764         // now look if the command-line options define a setting for this engine option.
13765         if(cps->optionSettings && cps->optionSettings[0])
13766             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13767         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13768                 sprintf(buf, "option %s", p);
13769                 if(p = strstr(buf, ",")) *p = 0;
13770                 strcat(buf, "\n");
13771                 SendToProgram(buf, cps);
13772         }
13773         return TRUE;
13774 }
13775
13776 void
13777 FeatureDone(cps, val)
13778      ChessProgramState* cps;
13779      int val;
13780 {
13781   DelayedEventCallback cb = GetDelayedEvent();
13782   if ((cb == InitBackEnd3 && cps == &first) ||
13783       (cb == TwoMachinesEventIfReady && cps == &second)) {
13784     CancelDelayedEvent();
13785     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13786   }
13787   cps->initDone = val;
13788 }
13789
13790 /* Parse feature command from engine */
13791 void
13792 ParseFeatures(args, cps)
13793      char* args;
13794      ChessProgramState *cps;  
13795 {
13796   char *p = args;
13797   char *q;
13798   int val;
13799   char buf[MSG_SIZ];
13800
13801   for (;;) {
13802     while (*p == ' ') p++;
13803     if (*p == NULLCHAR) return;
13804
13805     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13806     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13807     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13808     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13809     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13810     if (BoolFeature(&p, "reuse", &val, cps)) {
13811       /* Engine can disable reuse, but can't enable it if user said no */
13812       if (!val) cps->reuse = FALSE;
13813       continue;
13814     }
13815     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13816     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13817       if (gameMode == TwoMachinesPlay) {
13818         DisplayTwoMachinesTitle();
13819       } else {
13820         DisplayTitle("");
13821       }
13822       continue;
13823     }
13824     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13825     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13826     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13827     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13828     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13829     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13830     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13831     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13832     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13833     if (IntFeature(&p, "done", &val, cps)) {
13834       FeatureDone(cps, val);
13835       continue;
13836     }
13837     /* Added by Tord: */
13838     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13839     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13840     /* End of additions by Tord */
13841
13842     /* [HGM] added features: */
13843     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13844     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13845     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13846     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13847     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13848     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13849     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13850         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13851             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13852             SendToProgram(buf, cps);
13853             continue;
13854         }
13855         if(cps->nrOptions >= MAX_OPTIONS) {
13856             cps->nrOptions--;
13857             sprintf(buf, "%s engine has too many options\n", cps->which);
13858             DisplayError(buf, 0);
13859         }
13860         continue;
13861     }
13862     /* End of additions by HGM */
13863
13864     /* unknown feature: complain and skip */
13865     q = p;
13866     while (*q && *q != '=') q++;
13867     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13868     SendToProgram(buf, cps);
13869     p = q;
13870     if (*p == '=') {
13871       p++;
13872       if (*p == '\"') {
13873         p++;
13874         while (*p && *p != '\"') p++;
13875         if (*p == '\"') p++;
13876       } else {
13877         while (*p && *p != ' ') p++;
13878       }
13879     }
13880   }
13881
13882 }
13883
13884 void
13885 PeriodicUpdatesEvent(newState)
13886      int newState;
13887 {
13888     if (newState == appData.periodicUpdates)
13889       return;
13890
13891     appData.periodicUpdates=newState;
13892
13893     /* Display type changes, so update it now */
13894 //    DisplayAnalysis();
13895
13896     /* Get the ball rolling again... */
13897     if (newState) {
13898         AnalysisPeriodicEvent(1);
13899         StartAnalysisClock();
13900     }
13901 }
13902
13903 void
13904 PonderNextMoveEvent(newState)
13905      int newState;
13906 {
13907     if (newState == appData.ponderNextMove) return;
13908     if (gameMode == EditPosition) EditPositionDone(TRUE);
13909     if (newState) {
13910         SendToProgram("hard\n", &first);
13911         if (gameMode == TwoMachinesPlay) {
13912             SendToProgram("hard\n", &second);
13913         }
13914     } else {
13915         SendToProgram("easy\n", &first);
13916         thinkOutput[0] = NULLCHAR;
13917         if (gameMode == TwoMachinesPlay) {
13918             SendToProgram("easy\n", &second);
13919         }
13920     }
13921     appData.ponderNextMove = newState;
13922 }
13923
13924 void
13925 NewSettingEvent(option, feature, command, value)
13926      char *command;
13927      int option, value, *feature;
13928 {
13929     char buf[MSG_SIZ];
13930
13931     if (gameMode == EditPosition) EditPositionDone(TRUE);
13932     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13933     if(feature == NULL || *feature) SendToProgram(buf, &first);
13934     if (gameMode == TwoMachinesPlay) {
13935         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13936     }
13937 }
13938
13939 void
13940 ShowThinkingEvent()
13941 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13942 {
13943     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13944     int newState = appData.showThinking
13945         // [HGM] thinking: other features now need thinking output as well
13946         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13947     
13948     if (oldState == newState) return;
13949     oldState = newState;
13950     if (gameMode == EditPosition) EditPositionDone(TRUE);
13951     if (oldState) {
13952         SendToProgram("post\n", &first);
13953         if (gameMode == TwoMachinesPlay) {
13954             SendToProgram("post\n", &second);
13955         }
13956     } else {
13957         SendToProgram("nopost\n", &first);
13958         thinkOutput[0] = NULLCHAR;
13959         if (gameMode == TwoMachinesPlay) {
13960             SendToProgram("nopost\n", &second);
13961         }
13962     }
13963 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13964 }
13965
13966 void
13967 AskQuestionEvent(title, question, replyPrefix, which)
13968      char *title; char *question; char *replyPrefix; char *which;
13969 {
13970   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13971   if (pr == NoProc) return;
13972   AskQuestion(title, question, replyPrefix, pr);
13973 }
13974
13975 void
13976 DisplayMove(moveNumber)
13977      int moveNumber;
13978 {
13979     char message[MSG_SIZ];
13980     char res[MSG_SIZ];
13981     char cpThinkOutput[MSG_SIZ];
13982
13983     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13984     
13985     if (moveNumber == forwardMostMove - 1 || 
13986         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13987
13988         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13989
13990         if (strchr(cpThinkOutput, '\n')) {
13991             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13992         }
13993     } else {
13994         *cpThinkOutput = NULLCHAR;
13995     }
13996
13997     /* [AS] Hide thinking from human user */
13998     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13999         *cpThinkOutput = NULLCHAR;
14000         if( thinkOutput[0] != NULLCHAR ) {
14001             int i;
14002
14003             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14004                 cpThinkOutput[i] = '.';
14005             }
14006             cpThinkOutput[i] = NULLCHAR;
14007             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14008         }
14009     }
14010
14011     if (moveNumber == forwardMostMove - 1 &&
14012         gameInfo.resultDetails != NULL) {
14013         if (gameInfo.resultDetails[0] == NULLCHAR) {
14014             sprintf(res, " %s", PGNResult(gameInfo.result));
14015         } else {
14016             sprintf(res, " {%s} %s",
14017                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14018         }
14019     } else {
14020         res[0] = NULLCHAR;
14021     }
14022
14023     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14024         DisplayMessage(res, cpThinkOutput);
14025     } else {
14026         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14027                 WhiteOnMove(moveNumber) ? " " : ".. ",
14028                 parseList[moveNumber], res);
14029         DisplayMessage(message, cpThinkOutput);
14030     }
14031 }
14032
14033 void
14034 DisplayComment(moveNumber, text)
14035      int moveNumber;
14036      char *text;
14037 {
14038     char title[MSG_SIZ];
14039     char buf[8000]; // comment can be long!
14040     int score, depth;
14041     
14042     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14043       strcpy(title, "Comment");
14044     } else {
14045       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14046               WhiteOnMove(moveNumber) ? " " : ".. ",
14047               parseList[moveNumber]);
14048     }
14049     // [HGM] PV info: display PV info together with (or as) comment
14050     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14051       if(text == NULL) text = "";                                           
14052       score = pvInfoList[moveNumber].score;
14053       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14054               depth, (pvInfoList[moveNumber].time+50)/100, text);
14055       text = buf;
14056     }
14057     if (text != NULL && (appData.autoDisplayComment || commentUp))
14058         CommentPopUp(title, text);
14059 }
14060
14061 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14062  * might be busy thinking or pondering.  It can be omitted if your
14063  * gnuchess is configured to stop thinking immediately on any user
14064  * input.  However, that gnuchess feature depends on the FIONREAD
14065  * ioctl, which does not work properly on some flavors of Unix.
14066  */
14067 void
14068 Attention(cps)
14069      ChessProgramState *cps;
14070 {
14071 #if ATTENTION
14072     if (!cps->useSigint) return;
14073     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14074     switch (gameMode) {
14075       case MachinePlaysWhite:
14076       case MachinePlaysBlack:
14077       case TwoMachinesPlay:
14078       case IcsPlayingWhite:
14079       case IcsPlayingBlack:
14080       case AnalyzeMode:
14081       case AnalyzeFile:
14082         /* Skip if we know it isn't thinking */
14083         if (!cps->maybeThinking) return;
14084         if (appData.debugMode)
14085           fprintf(debugFP, "Interrupting %s\n", cps->which);
14086         InterruptChildProcess(cps->pr);
14087         cps->maybeThinking = FALSE;
14088         break;
14089       default:
14090         break;
14091     }
14092 #endif /*ATTENTION*/
14093 }
14094
14095 int
14096 CheckFlags()
14097 {
14098     if (whiteTimeRemaining <= 0) {
14099         if (!whiteFlag) {
14100             whiteFlag = TRUE;
14101             if (appData.icsActive) {
14102                 if (appData.autoCallFlag &&
14103                     gameMode == IcsPlayingBlack && !blackFlag) {
14104                   SendToICS(ics_prefix);
14105                   SendToICS("flag\n");
14106                 }
14107             } else {
14108                 if (blackFlag) {
14109                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14110                 } else {
14111                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14112                     if (appData.autoCallFlag) {
14113                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14114                         return TRUE;
14115                     }
14116                 }
14117             }
14118         }
14119     }
14120     if (blackTimeRemaining <= 0) {
14121         if (!blackFlag) {
14122             blackFlag = TRUE;
14123             if (appData.icsActive) {
14124                 if (appData.autoCallFlag &&
14125                     gameMode == IcsPlayingWhite && !whiteFlag) {
14126                   SendToICS(ics_prefix);
14127                   SendToICS("flag\n");
14128                 }
14129             } else {
14130                 if (whiteFlag) {
14131                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14132                 } else {
14133                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14134                     if (appData.autoCallFlag) {
14135                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14136                         return TRUE;
14137                     }
14138                 }
14139             }
14140         }
14141     }
14142     return FALSE;
14143 }
14144
14145 void
14146 CheckTimeControl()
14147 {
14148     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14149         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14150
14151     /*
14152      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14153      */
14154     if ( !WhiteOnMove(forwardMostMove) )
14155         /* White made time control */
14156         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14157         /* [HGM] time odds: correct new time quota for time odds! */
14158                                             / WhitePlayer()->timeOdds;
14159       else
14160         /* Black made time control */
14161         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14162                                             / WhitePlayer()->other->timeOdds;
14163 }
14164
14165 void
14166 DisplayBothClocks()
14167 {
14168     int wom = gameMode == EditPosition ?
14169       !blackPlaysFirst : WhiteOnMove(currentMove);
14170     DisplayWhiteClock(whiteTimeRemaining, wom);
14171     DisplayBlackClock(blackTimeRemaining, !wom);
14172 }
14173
14174
14175 /* Timekeeping seems to be a portability nightmare.  I think everyone
14176    has ftime(), but I'm really not sure, so I'm including some ifdefs
14177    to use other calls if you don't.  Clocks will be less accurate if
14178    you have neither ftime nor gettimeofday.
14179 */
14180
14181 /* VS 2008 requires the #include outside of the function */
14182 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14183 #include <sys/timeb.h>
14184 #endif
14185
14186 /* Get the current time as a TimeMark */
14187 void
14188 GetTimeMark(tm)
14189      TimeMark *tm;
14190 {
14191 #if HAVE_GETTIMEOFDAY
14192
14193     struct timeval timeVal;
14194     struct timezone timeZone;
14195
14196     gettimeofday(&timeVal, &timeZone);
14197     tm->sec = (long) timeVal.tv_sec; 
14198     tm->ms = (int) (timeVal.tv_usec / 1000L);
14199
14200 #else /*!HAVE_GETTIMEOFDAY*/
14201 #if HAVE_FTIME
14202
14203 // include <sys/timeb.h> / moved to just above start of function
14204     struct timeb timeB;
14205
14206     ftime(&timeB);
14207     tm->sec = (long) timeB.time;
14208     tm->ms = (int) timeB.millitm;
14209
14210 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14211     tm->sec = (long) time(NULL);
14212     tm->ms = 0;
14213 #endif
14214 #endif
14215 }
14216
14217 /* Return the difference in milliseconds between two
14218    time marks.  We assume the difference will fit in a long!
14219 */
14220 long
14221 SubtractTimeMarks(tm2, tm1)
14222      TimeMark *tm2, *tm1;
14223 {
14224     return 1000L*(tm2->sec - tm1->sec) +
14225            (long) (tm2->ms - tm1->ms);
14226 }
14227
14228
14229 /*
14230  * Code to manage the game clocks.
14231  *
14232  * In tournament play, black starts the clock and then white makes a move.
14233  * We give the human user a slight advantage if he is playing white---the
14234  * clocks don't run until he makes his first move, so it takes zero time.
14235  * Also, we don't account for network lag, so we could get out of sync
14236  * with GNU Chess's clock -- but then, referees are always right.  
14237  */
14238
14239 static TimeMark tickStartTM;
14240 static long intendedTickLength;
14241
14242 long
14243 NextTickLength(timeRemaining)
14244      long timeRemaining;
14245 {
14246     long nominalTickLength, nextTickLength;
14247
14248     if (timeRemaining > 0L && timeRemaining <= 10000L)
14249       nominalTickLength = 100L;
14250     else
14251       nominalTickLength = 1000L;
14252     nextTickLength = timeRemaining % nominalTickLength;
14253     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14254
14255     return nextTickLength;
14256 }
14257
14258 /* Adjust clock one minute up or down */
14259 void
14260 AdjustClock(Boolean which, int dir)
14261 {
14262     if(which) blackTimeRemaining += 60000*dir;
14263     else      whiteTimeRemaining += 60000*dir;
14264     DisplayBothClocks();
14265 }
14266
14267 /* Stop clocks and reset to a fresh time control */
14268 void
14269 ResetClocks() 
14270 {
14271     (void) StopClockTimer();
14272     if (appData.icsActive) {
14273         whiteTimeRemaining = blackTimeRemaining = 0;
14274     } else if (searchTime) {
14275         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14276         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14277     } else { /* [HGM] correct new time quote for time odds */
14278         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14279         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14280     }
14281     if (whiteFlag || blackFlag) {
14282         DisplayTitle("");
14283         whiteFlag = blackFlag = FALSE;
14284     }
14285     DisplayBothClocks();
14286 }
14287
14288 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14289
14290 /* Decrement running clock by amount of time that has passed */
14291 void
14292 DecrementClocks()
14293 {
14294     long timeRemaining;
14295     long lastTickLength, fudge;
14296     TimeMark now;
14297
14298     if (!appData.clockMode) return;
14299     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14300         
14301     GetTimeMark(&now);
14302
14303     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14304
14305     /* Fudge if we woke up a little too soon */
14306     fudge = intendedTickLength - lastTickLength;
14307     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14308
14309     if (WhiteOnMove(forwardMostMove)) {
14310         if(whiteNPS >= 0) lastTickLength = 0;
14311         timeRemaining = whiteTimeRemaining -= lastTickLength;
14312         DisplayWhiteClock(whiteTimeRemaining - fudge,
14313                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14314     } else {
14315         if(blackNPS >= 0) lastTickLength = 0;
14316         timeRemaining = blackTimeRemaining -= lastTickLength;
14317         DisplayBlackClock(blackTimeRemaining - fudge,
14318                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14319     }
14320
14321     if (CheckFlags()) return;
14322         
14323     tickStartTM = now;
14324     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14325     StartClockTimer(intendedTickLength);
14326
14327     /* if the time remaining has fallen below the alarm threshold, sound the
14328      * alarm. if the alarm has sounded and (due to a takeback or time control
14329      * with increment) the time remaining has increased to a level above the
14330      * threshold, reset the alarm so it can sound again. 
14331      */
14332     
14333     if (appData.icsActive && appData.icsAlarm) {
14334
14335         /* make sure we are dealing with the user's clock */
14336         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14337                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14338            )) return;
14339
14340         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14341             alarmSounded = FALSE;
14342         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14343             PlayAlarmSound();
14344             alarmSounded = TRUE;
14345         }
14346     }
14347 }
14348
14349
14350 /* A player has just moved, so stop the previously running
14351    clock and (if in clock mode) start the other one.
14352    We redisplay both clocks in case we're in ICS mode, because
14353    ICS gives us an update to both clocks after every move.
14354    Note that this routine is called *after* forwardMostMove
14355    is updated, so the last fractional tick must be subtracted
14356    from the color that is *not* on move now.
14357 */
14358 void
14359 SwitchClocks(int newMoveNr)
14360 {
14361     long lastTickLength;
14362     TimeMark now;
14363     int flagged = FALSE;
14364
14365     GetTimeMark(&now);
14366
14367     if (StopClockTimer() && appData.clockMode) {
14368         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14369         if (!WhiteOnMove(forwardMostMove)) {
14370             if(blackNPS >= 0) lastTickLength = 0;
14371             blackTimeRemaining -= lastTickLength;
14372            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14373 //         if(pvInfoList[forwardMostMove-1].time == -1)
14374                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14375                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14376         } else {
14377            if(whiteNPS >= 0) lastTickLength = 0;
14378            whiteTimeRemaining -= lastTickLength;
14379            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14380 //         if(pvInfoList[forwardMostMove-1].time == -1)
14381                  pvInfoList[forwardMostMove-1].time = 
14382                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14383         }
14384         flagged = CheckFlags();
14385     }
14386     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14387     CheckTimeControl();
14388
14389     if (flagged || !appData.clockMode) return;
14390
14391     switch (gameMode) {
14392       case MachinePlaysBlack:
14393       case MachinePlaysWhite:
14394       case BeginningOfGame:
14395         if (pausing) return;
14396         break;
14397
14398       case EditGame:
14399       case PlayFromGameFile:
14400       case IcsExamining:
14401         return;
14402
14403       default:
14404         break;
14405     }
14406
14407     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14408         if(WhiteOnMove(forwardMostMove))
14409              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14410         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14411     }
14412
14413     tickStartTM = now;
14414     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14415       whiteTimeRemaining : blackTimeRemaining);
14416     StartClockTimer(intendedTickLength);
14417 }
14418         
14419
14420 /* Stop both clocks */
14421 void
14422 StopClocks()
14423 {       
14424     long lastTickLength;
14425     TimeMark now;
14426
14427     if (!StopClockTimer()) return;
14428     if (!appData.clockMode) return;
14429
14430     GetTimeMark(&now);
14431
14432     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14433     if (WhiteOnMove(forwardMostMove)) {
14434         if(whiteNPS >= 0) lastTickLength = 0;
14435         whiteTimeRemaining -= lastTickLength;
14436         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14437     } else {
14438         if(blackNPS >= 0) lastTickLength = 0;
14439         blackTimeRemaining -= lastTickLength;
14440         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14441     }
14442     CheckFlags();
14443 }
14444         
14445 /* Start clock of player on move.  Time may have been reset, so
14446    if clock is already running, stop and restart it. */
14447 void
14448 StartClocks()
14449 {
14450     (void) StopClockTimer(); /* in case it was running already */
14451     DisplayBothClocks();
14452     if (CheckFlags()) return;
14453
14454     if (!appData.clockMode) return;
14455     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14456
14457     GetTimeMark(&tickStartTM);
14458     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14459       whiteTimeRemaining : blackTimeRemaining);
14460
14461    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14462     whiteNPS = blackNPS = -1; 
14463     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14464        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14465         whiteNPS = first.nps;
14466     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14467        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14468         blackNPS = first.nps;
14469     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14470         whiteNPS = second.nps;
14471     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14472         blackNPS = second.nps;
14473     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14474
14475     StartClockTimer(intendedTickLength);
14476 }
14477
14478 char *
14479 TimeString(ms)
14480      long ms;
14481 {
14482     long second, minute, hour, day;
14483     char *sign = "";
14484     static char buf[32];
14485     
14486     if (ms > 0 && ms <= 9900) {
14487       /* convert milliseconds to tenths, rounding up */
14488       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14489
14490       sprintf(buf, " %03.1f ", tenths/10.0);
14491       return buf;
14492     }
14493
14494     /* convert milliseconds to seconds, rounding up */
14495     /* use floating point to avoid strangeness of integer division
14496        with negative dividends on many machines */
14497     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14498
14499     if (second < 0) {
14500         sign = "-";
14501         second = -second;
14502     }
14503     
14504     day = second / (60 * 60 * 24);
14505     second = second % (60 * 60 * 24);
14506     hour = second / (60 * 60);
14507     second = second % (60 * 60);
14508     minute = second / 60;
14509     second = second % 60;
14510     
14511     if (day > 0)
14512       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14513               sign, day, hour, minute, second);
14514     else if (hour > 0)
14515       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14516     else
14517       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14518     
14519     return buf;
14520 }
14521
14522
14523 /*
14524  * This is necessary because some C libraries aren't ANSI C compliant yet.
14525  */
14526 char *
14527 StrStr(string, match)
14528      char *string, *match;
14529 {
14530     int i, length;
14531     
14532     length = strlen(match);
14533     
14534     for (i = strlen(string) - length; i >= 0; i--, string++)
14535       if (!strncmp(match, string, length))
14536         return string;
14537     
14538     return NULL;
14539 }
14540
14541 char *
14542 StrCaseStr(string, match)
14543      char *string, *match;
14544 {
14545     int i, j, length;
14546     
14547     length = strlen(match);
14548     
14549     for (i = strlen(string) - length; i >= 0; i--, string++) {
14550         for (j = 0; j < length; j++) {
14551             if (ToLower(match[j]) != ToLower(string[j]))
14552               break;
14553         }
14554         if (j == length) return string;
14555     }
14556
14557     return NULL;
14558 }
14559
14560 #ifndef _amigados
14561 int
14562 StrCaseCmp(s1, s2)
14563      char *s1, *s2;
14564 {
14565     char c1, c2;
14566     
14567     for (;;) {
14568         c1 = ToLower(*s1++);
14569         c2 = ToLower(*s2++);
14570         if (c1 > c2) return 1;
14571         if (c1 < c2) return -1;
14572         if (c1 == NULLCHAR) return 0;
14573     }
14574 }
14575
14576
14577 int
14578 ToLower(c)
14579      int c;
14580 {
14581     return isupper(c) ? tolower(c) : c;
14582 }
14583
14584
14585 int
14586 ToUpper(c)
14587      int c;
14588 {
14589     return islower(c) ? toupper(c) : c;
14590 }
14591 #endif /* !_amigados    */
14592
14593 char *
14594 StrSave(s)
14595      char *s;
14596 {
14597     char *ret;
14598
14599     if ((ret = (char *) malloc(strlen(s) + 1))) {
14600         strcpy(ret, s);
14601     }
14602     return ret;
14603 }
14604
14605 char *
14606 StrSavePtr(s, savePtr)
14607      char *s, **savePtr;
14608 {
14609     if (*savePtr) {
14610         free(*savePtr);
14611     }
14612     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14613         strcpy(*savePtr, s);
14614     }
14615     return(*savePtr);
14616 }
14617
14618 char *
14619 PGNDate()
14620 {
14621     time_t clock;
14622     struct tm *tm;
14623     char buf[MSG_SIZ];
14624
14625     clock = time((time_t *)NULL);
14626     tm = localtime(&clock);
14627     sprintf(buf, "%04d.%02d.%02d",
14628             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14629     return StrSave(buf);
14630 }
14631
14632
14633 char *
14634 PositionToFEN(move, overrideCastling)
14635      int move;
14636      char *overrideCastling;
14637 {
14638     int i, j, fromX, fromY, toX, toY;
14639     int whiteToPlay;
14640     char buf[128];
14641     char *p, *q;
14642     int emptycount;
14643     ChessSquare piece;
14644
14645     whiteToPlay = (gameMode == EditPosition) ?
14646       !blackPlaysFirst : (move % 2 == 0);
14647     p = buf;
14648
14649     /* Piece placement data */
14650     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14651         emptycount = 0;
14652         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14653             if (boards[move][i][j] == EmptySquare) {
14654                 emptycount++;
14655             } else { ChessSquare piece = boards[move][i][j];
14656                 if (emptycount > 0) {
14657                     if(emptycount<10) /* [HGM] can be >= 10 */
14658                         *p++ = '0' + emptycount;
14659                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14660                     emptycount = 0;
14661                 }
14662                 if(PieceToChar(piece) == '+') {
14663                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14664                     *p++ = '+';
14665                     piece = (ChessSquare)(DEMOTED piece);
14666                 } 
14667                 *p++ = PieceToChar(piece);
14668                 if(p[-1] == '~') {
14669                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14670                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14671                     *p++ = '~';
14672                 }
14673             }
14674         }
14675         if (emptycount > 0) {
14676             if(emptycount<10) /* [HGM] can be >= 10 */
14677                 *p++ = '0' + emptycount;
14678             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14679             emptycount = 0;
14680         }
14681         *p++ = '/';
14682     }
14683     *(p - 1) = ' ';
14684
14685     /* [HGM] print Crazyhouse or Shogi holdings */
14686     if( gameInfo.holdingsWidth ) {
14687         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14688         q = p;
14689         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14690             piece = boards[move][i][BOARD_WIDTH-1];
14691             if( piece != EmptySquare )
14692               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14693                   *p++ = PieceToChar(piece);
14694         }
14695         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14696             piece = boards[move][BOARD_HEIGHT-i-1][0];
14697             if( piece != EmptySquare )
14698               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14699                   *p++ = PieceToChar(piece);
14700         }
14701
14702         if( q == p ) *p++ = '-';
14703         *p++ = ']';
14704         *p++ = ' ';
14705     }
14706
14707     /* Active color */
14708     *p++ = whiteToPlay ? 'w' : 'b';
14709     *p++ = ' ';
14710
14711   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14712     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14713   } else {
14714   if(nrCastlingRights) {
14715      q = p;
14716      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14717        /* [HGM] write directly from rights */
14718            if(boards[move][CASTLING][2] != NoRights &&
14719               boards[move][CASTLING][0] != NoRights   )
14720                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14721            if(boards[move][CASTLING][2] != NoRights &&
14722               boards[move][CASTLING][1] != NoRights   )
14723                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14724            if(boards[move][CASTLING][5] != NoRights &&
14725               boards[move][CASTLING][3] != NoRights   )
14726                 *p++ = boards[move][CASTLING][3] + AAA;
14727            if(boards[move][CASTLING][5] != NoRights &&
14728               boards[move][CASTLING][4] != NoRights   )
14729                 *p++ = boards[move][CASTLING][4] + AAA;
14730      } else {
14731
14732         /* [HGM] write true castling rights */
14733         if( nrCastlingRights == 6 ) {
14734             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14735                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14736             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14737                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14738             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14739                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14740             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14741                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14742         }
14743      }
14744      if (q == p) *p++ = '-'; /* No castling rights */
14745      *p++ = ' ';
14746   }
14747
14748   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14749      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14750     /* En passant target square */
14751     if (move > backwardMostMove) {
14752         fromX = moveList[move - 1][0] - AAA;
14753         fromY = moveList[move - 1][1] - ONE;
14754         toX = moveList[move - 1][2] - AAA;
14755         toY = moveList[move - 1][3] - ONE;
14756         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14757             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14758             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14759             fromX == toX) {
14760             /* 2-square pawn move just happened */
14761             *p++ = toX + AAA;
14762             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14763         } else {
14764             *p++ = '-';
14765         }
14766     } else if(move == backwardMostMove) {
14767         // [HGM] perhaps we should always do it like this, and forget the above?
14768         if((signed char)boards[move][EP_STATUS] >= 0) {
14769             *p++ = boards[move][EP_STATUS] + AAA;
14770             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14771         } else {
14772             *p++ = '-';
14773         }
14774     } else {
14775         *p++ = '-';
14776     }
14777     *p++ = ' ';
14778   }
14779   }
14780
14781     /* [HGM] find reversible plies */
14782     {   int i = 0, j=move;
14783
14784         if (appData.debugMode) { int k;
14785             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14786             for(k=backwardMostMove; k<=forwardMostMove; k++)
14787                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14788
14789         }
14790
14791         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14792         if( j == backwardMostMove ) i += initialRulePlies;
14793         sprintf(p, "%d ", i);
14794         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14795     }
14796     /* Fullmove number */
14797     sprintf(p, "%d", (move / 2) + 1);
14798     
14799     return StrSave(buf);
14800 }
14801
14802 Boolean
14803 ParseFEN(board, blackPlaysFirst, fen)
14804     Board board;
14805      int *blackPlaysFirst;
14806      char *fen;
14807 {
14808     int i, j;
14809     char *p, c;
14810     int emptycount;
14811     ChessSquare piece;
14812
14813     p = fen;
14814
14815     /* [HGM] by default clear Crazyhouse holdings, if present */
14816     if(gameInfo.holdingsWidth) {
14817        for(i=0; i<BOARD_HEIGHT; i++) {
14818            board[i][0]             = EmptySquare; /* black holdings */
14819            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14820            board[i][1]             = (ChessSquare) 0; /* black counts */
14821            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14822        }
14823     }
14824
14825     /* Piece placement data */
14826     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14827         j = 0;
14828         for (;;) {
14829             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14830                 if (*p == '/') p++;
14831                 emptycount = gameInfo.boardWidth - j;
14832                 while (emptycount--)
14833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14834                 break;
14835 #if(BOARD_FILES >= 10)
14836             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14837                 p++; emptycount=10;
14838                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14839                 while (emptycount--)
14840                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14841 #endif
14842             } else if (isdigit(*p)) {
14843                 emptycount = *p++ - '0';
14844                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14845                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14846                 while (emptycount--)
14847                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14848             } else if (*p == '+' || isalpha(*p)) {
14849                 if (j >= gameInfo.boardWidth) return FALSE;
14850                 if(*p=='+') {
14851                     piece = CharToPiece(*++p);
14852                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14853                     piece = (ChessSquare) (PROMOTED piece ); p++;
14854                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14855                 } else piece = CharToPiece(*p++);
14856
14857                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14858                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14859                     piece = (ChessSquare) (PROMOTED piece);
14860                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14861                     p++;
14862                 }
14863                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14864             } else {
14865                 return FALSE;
14866             }
14867         }
14868     }
14869     while (*p == '/' || *p == ' ') p++;
14870
14871     /* [HGM] look for Crazyhouse holdings here */
14872     while(*p==' ') p++;
14873     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14874         if(*p == '[') p++;
14875         if(*p == '-' ) *p++; /* empty holdings */ else {
14876             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14877             /* if we would allow FEN reading to set board size, we would   */
14878             /* have to add holdings and shift the board read so far here   */
14879             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14880                 *p++;
14881                 if((int) piece >= (int) BlackPawn ) {
14882                     i = (int)piece - (int)BlackPawn;
14883                     i = PieceToNumber((ChessSquare)i);
14884                     if( i >= gameInfo.holdingsSize ) return FALSE;
14885                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14886                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14887                 } else {
14888                     i = (int)piece - (int)WhitePawn;
14889                     i = PieceToNumber((ChessSquare)i);
14890                     if( i >= gameInfo.holdingsSize ) return FALSE;
14891                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14892                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14893                 }
14894             }
14895         }
14896         if(*p == ']') *p++;
14897     }
14898
14899     while(*p == ' ') p++;
14900
14901     /* Active color */
14902     c = *p++;
14903     if(appData.colorNickNames) {
14904       if( c == appData.colorNickNames[0] ) c = 'w'; else
14905       if( c == appData.colorNickNames[1] ) c = 'b';
14906     }
14907     switch (c) {
14908       case 'w':
14909         *blackPlaysFirst = FALSE;
14910         break;
14911       case 'b': 
14912         *blackPlaysFirst = TRUE;
14913         break;
14914       default:
14915         return FALSE;
14916     }
14917
14918     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14919     /* return the extra info in global variiables             */
14920
14921     /* set defaults in case FEN is incomplete */
14922     board[EP_STATUS] = EP_UNKNOWN;
14923     for(i=0; i<nrCastlingRights; i++ ) {
14924         board[CASTLING][i] =
14925             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14926     }   /* assume possible unless obviously impossible */
14927     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14928     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14929     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14930                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14931     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14932     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14933     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14934                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14935     FENrulePlies = 0;
14936
14937     while(*p==' ') p++;
14938     if(nrCastlingRights) {
14939       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14940           /* castling indicator present, so default becomes no castlings */
14941           for(i=0; i<nrCastlingRights; i++ ) {
14942                  board[CASTLING][i] = NoRights;
14943           }
14944       }
14945       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14946              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14947              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14948              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14949         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14950
14951         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14952             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14953             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14954         }
14955         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14956             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14957         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14958                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14959         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14960                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14961         switch(c) {
14962           case'K':
14963               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14964               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14965               board[CASTLING][2] = whiteKingFile;
14966               break;
14967           case'Q':
14968               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14969               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14970               board[CASTLING][2] = whiteKingFile;
14971               break;
14972           case'k':
14973               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14974               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14975               board[CASTLING][5] = blackKingFile;
14976               break;
14977           case'q':
14978               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14979               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14980               board[CASTLING][5] = blackKingFile;
14981           case '-':
14982               break;
14983           default: /* FRC castlings */
14984               if(c >= 'a') { /* black rights */
14985                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14986                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14987                   if(i == BOARD_RGHT) break;
14988                   board[CASTLING][5] = i;
14989                   c -= AAA;
14990                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14991                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14992                   if(c > i)
14993                       board[CASTLING][3] = c;
14994                   else
14995                       board[CASTLING][4] = c;
14996               } else { /* white rights */
14997                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14998                     if(board[0][i] == WhiteKing) break;
14999                   if(i == BOARD_RGHT) break;
15000                   board[CASTLING][2] = i;
15001                   c -= AAA - 'a' + 'A';
15002                   if(board[0][c] >= WhiteKing) break;
15003                   if(c > i)
15004                       board[CASTLING][0] = c;
15005                   else
15006                       board[CASTLING][1] = c;
15007               }
15008         }
15009       }
15010       for(i=0; i<nrCastlingRights; i++)
15011         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15012     if (appData.debugMode) {
15013         fprintf(debugFP, "FEN castling rights:");
15014         for(i=0; i<nrCastlingRights; i++)
15015         fprintf(debugFP, " %d", board[CASTLING][i]);
15016         fprintf(debugFP, "\n");
15017     }
15018
15019       while(*p==' ') p++;
15020     }
15021
15022     /* read e.p. field in games that know e.p. capture */
15023     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15024        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15025       if(*p=='-') {
15026         p++; board[EP_STATUS] = EP_NONE;
15027       } else {
15028          char c = *p++ - AAA;
15029
15030          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15031          if(*p >= '0' && *p <='9') *p++;
15032          board[EP_STATUS] = c;
15033       }
15034     }
15035
15036
15037     if(sscanf(p, "%d", &i) == 1) {
15038         FENrulePlies = i; /* 50-move ply counter */
15039         /* (The move number is still ignored)    */
15040     }
15041
15042     return TRUE;
15043 }
15044       
15045 void
15046 EditPositionPasteFEN(char *fen)
15047 {
15048   if (fen != NULL) {
15049     Board initial_position;
15050
15051     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15052       DisplayError(_("Bad FEN position in clipboard"), 0);
15053       return ;
15054     } else {
15055       int savedBlackPlaysFirst = blackPlaysFirst;
15056       EditPositionEvent();
15057       blackPlaysFirst = savedBlackPlaysFirst;
15058       CopyBoard(boards[0], initial_position);
15059       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15060       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15061       DisplayBothClocks();
15062       DrawPosition(FALSE, boards[currentMove]);
15063     }
15064   }
15065 }
15066
15067 static char cseq[12] = "\\   ";
15068
15069 Boolean set_cont_sequence(char *new_seq)
15070 {
15071     int len;
15072     Boolean ret;
15073
15074     // handle bad attempts to set the sequence
15075         if (!new_seq)
15076                 return 0; // acceptable error - no debug
15077
15078     len = strlen(new_seq);
15079     ret = (len > 0) && (len < sizeof(cseq));
15080     if (ret)
15081         strcpy(cseq, new_seq);
15082     else if (appData.debugMode)
15083         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15084     return ret;
15085 }
15086
15087 /*
15088     reformat a source message so words don't cross the width boundary.  internal
15089     newlines are not removed.  returns the wrapped size (no null character unless
15090     included in source message).  If dest is NULL, only calculate the size required
15091     for the dest buffer.  lp argument indicats line position upon entry, and it's
15092     passed back upon exit.
15093 */
15094 int wrap(char *dest, char *src, int count, int width, int *lp)
15095 {
15096     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15097
15098     cseq_len = strlen(cseq);
15099     old_line = line = *lp;
15100     ansi = len = clen = 0;
15101
15102     for (i=0; i < count; i++)
15103     {
15104         if (src[i] == '\033')
15105             ansi = 1;
15106
15107         // if we hit the width, back up
15108         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15109         {
15110             // store i & len in case the word is too long
15111             old_i = i, old_len = len;
15112
15113             // find the end of the last word
15114             while (i && src[i] != ' ' && src[i] != '\n')
15115             {
15116                 i--;
15117                 len--;
15118             }
15119
15120             // word too long?  restore i & len before splitting it
15121             if ((old_i-i+clen) >= width)
15122             {
15123                 i = old_i;
15124                 len = old_len;
15125             }
15126
15127             // extra space?
15128             if (i && src[i-1] == ' ')
15129                 len--;
15130
15131             if (src[i] != ' ' && src[i] != '\n')
15132             {
15133                 i--;
15134                 if (len)
15135                     len--;
15136             }
15137
15138             // now append the newline and continuation sequence
15139             if (dest)
15140                 dest[len] = '\n';
15141             len++;
15142             if (dest)
15143                 strncpy(dest+len, cseq, cseq_len);
15144             len += cseq_len;
15145             line = cseq_len;
15146             clen = cseq_len;
15147             continue;
15148         }
15149
15150         if (dest)
15151             dest[len] = src[i];
15152         len++;
15153         if (!ansi)
15154             line++;
15155         if (src[i] == '\n')
15156             line = 0;
15157         if (src[i] == 'm')
15158             ansi = 0;
15159     }
15160     if (dest && appData.debugMode)
15161     {
15162         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15163             count, width, line, len, *lp);
15164         show_bytes(debugFP, src, count);
15165         fprintf(debugFP, "\ndest: ");
15166         show_bytes(debugFP, dest, len);
15167         fprintf(debugFP, "\n");
15168     }
15169     *lp = dest ? line : old_line;
15170
15171     return len;
15172 }
15173
15174 // [HGM] vari: routines for shelving variations
15175
15176 void 
15177 PushTail(int firstMove, int lastMove)
15178 {
15179         int i, j, nrMoves = lastMove - firstMove;
15180
15181         if(appData.icsActive) { // only in local mode
15182                 forwardMostMove = currentMove; // mimic old ICS behavior
15183                 return;
15184         }
15185         if(storedGames >= MAX_VARIATIONS-1) return;
15186
15187         // push current tail of game on stack
15188         savedResult[storedGames] = gameInfo.result;
15189         savedDetails[storedGames] = gameInfo.resultDetails;
15190         gameInfo.resultDetails = NULL;
15191         savedFirst[storedGames] = firstMove;
15192         savedLast [storedGames] = lastMove;
15193         savedFramePtr[storedGames] = framePtr;
15194         framePtr -= nrMoves; // reserve space for the boards
15195         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15196             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15197             for(j=0; j<MOVE_LEN; j++)
15198                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15199             for(j=0; j<2*MOVE_LEN; j++)
15200                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15201             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15202             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15203             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15204             pvInfoList[firstMove+i-1].depth = 0;
15205             commentList[framePtr+i] = commentList[firstMove+i];
15206             commentList[firstMove+i] = NULL;
15207         }
15208
15209         storedGames++;
15210         forwardMostMove = firstMove; // truncate game so we can start variation
15211         if(storedGames == 1) GreyRevert(FALSE);
15212 }
15213
15214 Boolean
15215 PopTail(Boolean annotate)
15216 {
15217         int i, j, nrMoves;
15218         char buf[8000], moveBuf[20];
15219
15220         if(appData.icsActive) return FALSE; // only in local mode
15221         if(!storedGames) return FALSE; // sanity
15222         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15223
15224         storedGames--;
15225         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15226         nrMoves = savedLast[storedGames] - currentMove;
15227         if(annotate) {
15228                 int cnt = 10;
15229                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15230                 else strcpy(buf, "(");
15231                 for(i=currentMove; i<forwardMostMove; i++) {
15232                         if(WhiteOnMove(i))
15233                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15234                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15235                         strcat(buf, moveBuf);
15236                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15237                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15238                 }
15239                 strcat(buf, ")");
15240         }
15241         for(i=1; i<=nrMoves; i++) { // copy last variation back
15242             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15243             for(j=0; j<MOVE_LEN; j++)
15244                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15245             for(j=0; j<2*MOVE_LEN; j++)
15246                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15247             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15248             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15249             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15250             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15251             commentList[currentMove+i] = commentList[framePtr+i];
15252             commentList[framePtr+i] = NULL;
15253         }
15254         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15255         framePtr = savedFramePtr[storedGames];
15256         gameInfo.result = savedResult[storedGames];
15257         if(gameInfo.resultDetails != NULL) {
15258             free(gameInfo.resultDetails);
15259       }
15260         gameInfo.resultDetails = savedDetails[storedGames];
15261         forwardMostMove = currentMove + nrMoves;
15262         if(storedGames == 0) GreyRevert(TRUE);
15263         return TRUE;
15264 }
15265
15266 void 
15267 CleanupTail()
15268 {       // remove all shelved variations
15269         int i;
15270         for(i=0; i<storedGames; i++) {
15271             if(savedDetails[i])
15272                 free(savedDetails[i]);
15273             savedDetails[i] = NULL;
15274         }
15275         for(i=framePtr; i<MAX_MOVES; i++) {
15276                 if(commentList[i]) free(commentList[i]);
15277                 commentList[i] = NULL;
15278         }
15279         framePtr = MAX_MOVES-1;
15280         storedGames = 0;
15281 }
15282
15283 void
15284 LoadVariation(int index, char *text)
15285 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15286         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15287         int level = 0, move;
15288
15289         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15290         // first find outermost bracketing variation
15291         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15292             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15293                 if(*p == '{') wait = '}'; else
15294                 if(*p == '[') wait = ']'; else
15295                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15296                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15297             }
15298             if(*p == wait) wait = NULLCHAR; // closing ]} found
15299             p++;
15300         }
15301         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15302         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15303         end[1] = NULLCHAR; // clip off comment beyond variation
15304         ToNrEvent(currentMove-1);
15305         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15306         // kludge: use ParsePV() to append variation to game
15307         move = currentMove;
15308         ParsePV(start, TRUE);
15309         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15310         ClearPremoveHighlights();
15311         CommentPopDown();
15312         ToNrEvent(currentMove+1);
15313 }
15314