Do not translate game-end messages in PGN
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # ifdef WIN32
136 #   define _(s) T_(s)
137 #   define N_(s) s
138 # else
139 #   define _(s) (s) 
140 #   define N_(s) s 
141 # endif
142 #endif 
143
144
145 /* A point in time */
146 typedef struct {
147     long sec;  /* Assuming this is >= 32 bits */
148     int ms;    /* Assuming this is >= 16 bits */
149 } TimeMark;
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void ics_printf P((char *format, ...));
157 void SendToICS P((char *s));
158 void SendToICSDelayed P((char *s, long msdelay));
159 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
160                       int toX, int toY));
161 void HandleMachineMove P((char *message, ChessProgramState *cps));
162 int AutoPlayOneMove P((void));
163 int LoadGameOneMove P((ChessMove readAhead));
164 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
165 int LoadPositionFromFile P((char *filename, int n, char *title));
166 int SavePositionToFile P((char *filename));
167 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
168                                                                                 Board board));
169 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
170 void ShowMove P((int fromX, int fromY, int toX, int toY));
171 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
172                    /*char*/int promoChar));
173 void BackwardInner P((int target));
174 void ForwardInner P((int target));
175 int Adjudicate P((ChessProgramState *cps));
176 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
177 void EditPositionDone P((Boolean fakeRights));
178 void PrintOpponents P((FILE *fp));
179 void PrintPosition P((FILE *fp, int move));
180 void StartChessProgram P((ChessProgramState *cps));
181 void SendToProgram P((char *message, ChessProgramState *cps));
182 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
183 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
184                            char *buf, int count, int error));
185 void SendTimeControl P((ChessProgramState *cps,
186                         int mps, long tc, int inc, int sd, int st));
187 char *TimeControlTagValue P((void));
188 void Attention P((ChessProgramState *cps));
189 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
190 void ResurrectChessProgram P((void));
191 void DisplayComment P((int moveNumber, char *text));
192 void DisplayMove P((int moveNumber));
193
194 void ParseGameHistory P((char *game));
195 void ParseBoard12 P((char *string));
196 void KeepAlive P((void));
197 void StartClocks P((void));
198 void SwitchClocks P((int nr));
199 void StopClocks P((void));
200 void ResetClocks P((void));
201 char *PGNDate P((void));
202 void SetGameInfo P((void));
203 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
204 int RegisterMove P((void));
205 void MakeRegisteredMove P((void));
206 void TruncateGame P((void));
207 int looking_at P((char *, int *, char *));
208 void CopyPlayerNameIntoFileName P((char **, char *));
209 char *SavePart P((char *));
210 int SaveGameOldStyle P((FILE *));
211 int SaveGamePGN P((FILE *));
212 void GetTimeMark P((TimeMark *));
213 long SubtractTimeMarks P((TimeMark *, TimeMark *));
214 int CheckFlags P((void));
215 long NextTickLength P((long));
216 void CheckTimeControl P((void));
217 void show_bytes P((FILE *, char *, int));
218 int string_to_rating P((char *str));
219 void ParseFeatures P((char* args, ChessProgramState *cps));
220 void InitBackEnd3 P((void));
221 void FeatureDone P((ChessProgramState* cps, int val));
222 void InitChessProgram P((ChessProgramState *cps, int setup));
223 void OutputKibitz(int window, char *text);
224 int PerpetualChase(int first, int last);
225 int EngineOutputIsUp();
226 void InitDrawingSizes(int x, int y);
227
228 #ifdef WIN32
229        extern void ConsoleCreate();
230 #endif
231
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
235
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269
270 /* States for ics_getting_history */
271 #define H_FALSE 0
272 #define H_REQUESTED 1
273 #define H_GOT_REQ_HEADER 2
274 #define H_GOT_UNREQ_HEADER 3
275 #define H_GETTING_MOVES 4
276 #define H_GOT_UNWANTED_HEADER 5
277
278 /* whosays values for GameEnds */
279 #define GE_ICS 0
280 #define GE_ENGINE 1
281 #define GE_PLAYER 2
282 #define GE_FILE 3
283 #define GE_XBOARD 4
284 #define GE_ENGINE1 5
285 #define GE_ENGINE2 6
286
287 /* Maximum number of games in a cmail message */
288 #define CMAIL_MAX_GAMES 20
289
290 /* Different types of move when calling RegisterMove */
291 #define CMAIL_MOVE   0
292 #define CMAIL_RESIGN 1
293 #define CMAIL_DRAW   2
294 #define CMAIL_ACCEPT 3
295
296 /* Different types of result to remember for each game */
297 #define CMAIL_NOT_RESULT 0
298 #define CMAIL_OLD_RESULT 1
299 #define CMAIL_NEW_RESULT 2
300
301 /* Telnet protocol constants */
302 #define TN_WILL 0373
303 #define TN_WONT 0374
304 #define TN_DO   0375
305 #define TN_DONT 0376
306 #define TN_IAC  0377
307 #define TN_ECHO 0001
308 #define TN_SGA  0003
309 #define TN_PORT 23
310
311 /* [AS] */
312 static char * safeStrCpy( char * dst, const char * src, size_t count )
313 {
314     assert( dst != NULL );
315     assert( src != NULL );
316     assert( count > 0 );
317
318     strncpy( dst, src, count );
319     dst[ count-1 ] = '\0';
320     return dst;
321 }
322
323 /* Some compiler can't cast u64 to double
324  * This function do the job for us:
325
326  * We use the highest bit for cast, this only
327  * works if the highest bit is not
328  * in use (This should not happen)
329  *
330  * We used this for all compiler
331  */
332 double
333 u64ToDouble(u64 value)
334 {
335   double r;
336   u64 tmp = value & u64Const(0x7fffffffffffffff);
337   r = (double)(s64)tmp;
338   if (value & u64Const(0x8000000000000000))
339        r +=  9.2233720368547758080e18; /* 2^63 */
340  return r;
341 }
342
343 /* Fake up flags for now, as we aren't keeping track of castling
344    availability yet. [HGM] Change of logic: the flag now only
345    indicates the type of castlings allowed by the rule of the game.
346    The actual rights themselves are maintained in the array
347    castlingRights, as part of the game history, and are not probed
348    by this function.
349  */
350 int
351 PosFlags(index)
352 {
353   int flags = F_ALL_CASTLE_OK;
354   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
355   switch (gameInfo.variant) {
356   case VariantSuicide:
357     flags &= ~F_ALL_CASTLE_OK;
358   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
359     flags |= F_IGNORE_CHECK;
360   case VariantLosers:
361     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
362     break;
363   case VariantAtomic:
364     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365     break;
366   case VariantKriegspiel:
367     flags |= F_KRIEGSPIEL_CAPTURE;
368     break;
369   case VariantCapaRandom: 
370   case VariantFischeRandom:
371     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
372   case VariantNoCastle:
373   case VariantShatranj:
374   case VariantCourier:
375   case VariantMakruk:
376     flags &= ~F_ALL_CASTLE_OK;
377     break;
378   default:
379     break;
380   }
381   return flags;
382 }
383
384 FILE *gameFileFP, *debugFP;
385
386 /* 
387     [AS] Note: sometimes, the sscanf() function is used to parse the input
388     into a fixed-size buffer. Because of this, we must be prepared to
389     receive strings as long as the size of the input buffer, which is currently
390     set to 4K for Windows and 8K for the rest.
391     So, we must either allocate sufficiently large buffers here, or
392     reduce the size of the input buffer in the input reading part.
393 */
394
395 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
396 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
397 char thinkOutput1[MSG_SIZ*10];
398
399 ChessProgramState first, second;
400
401 /* premove variables */
402 int premoveToX = 0;
403 int premoveToY = 0;
404 int premoveFromX = 0;
405 int premoveFromY = 0;
406 int premovePromoChar = 0;
407 int gotPremove = 0;
408 Boolean alarmSounded;
409 /* end premove variables */
410
411 char *ics_prefix = "$";
412 int ics_type = ICS_GENERIC;
413
414 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
415 int pauseExamForwardMostMove = 0;
416 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
417 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
418 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
419 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
420 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
421 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
422 int whiteFlag = FALSE, blackFlag = FALSE;
423 int userOfferedDraw = FALSE;
424 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
425 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
426 int cmailMoveType[CMAIL_MAX_GAMES];
427 long ics_clock_paused = 0;
428 ProcRef icsPR = NoProc, cmailPR = NoProc;
429 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
430 GameMode gameMode = BeginningOfGame;
431 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
432 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
433 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
434 int hiddenThinkOutputState = 0; /* [AS] */
435 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
436 int adjudicateLossPlies = 6;
437 char white_holding[64], black_holding[64];
438 TimeMark lastNodeCountTime;
439 long lastNodeCount=0;
440 int have_sent_ICS_logon = 0;
441 int movesPerSession;
442 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
443 long timeControl_2; /* [AS] Allow separate time controls */
444 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
445 long timeRemaining[2][MAX_MOVES];
446 int matchGame = 0;
447 TimeMark programStartTime;
448 char ics_handle[MSG_SIZ];
449 int have_set_title = 0;
450
451 /* animateTraining preserves the state of appData.animate
452  * when Training mode is activated. This allows the
453  * response to be animated when appData.animate == TRUE and
454  * appData.animateDragging == TRUE.
455  */
456 Boolean animateTraining;
457
458 GameInfo gameInfo;
459
460 AppData appData;
461
462 Board boards[MAX_MOVES];
463 /* [HGM] Following 7 needed for accurate legality tests: */
464 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
465 signed char  initialRights[BOARD_FILES];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
469 int loadFlag = 0; 
470 int shuffleOpenings;
471 int mute; // mute all sounds
472
473 // [HGM] vari: next 12 to save and restore variations
474 #define MAX_VARIATIONS 10
475 int framePtr = MAX_MOVES-1; // points to free stack entry
476 int storedGames = 0;
477 int savedFirst[MAX_VARIATIONS];
478 int savedLast[MAX_VARIATIONS];
479 int savedFramePtr[MAX_VARIATIONS];
480 char *savedDetails[MAX_VARIATIONS];
481 ChessMove savedResult[MAX_VARIATIONS];
482
483 void PushTail P((int firstMove, int lastMove));
484 Boolean PopTail P((Boolean annotate));
485 void CleanupTail P((void));
486
487 ChessSquare  FIDEArray[2][BOARD_FILES] = {
488     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
489         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
490     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
491         BlackKing, BlackBishop, BlackKnight, BlackRook }
492 };
493
494 ChessSquare twoKingsArray[2][BOARD_FILES] = {
495     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
496         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
497     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
498         BlackKing, BlackKing, BlackKnight, BlackRook }
499 };
500
501 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
503         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
504     { BlackRook, BlackMan, BlackBishop, BlackQueen,
505         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
506 };
507
508 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
512         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
513 };
514
515 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
516     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
517         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
519         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
520 };
521
522 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
523     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
524         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackMan, BlackFerz,
526         BlackKing, BlackMan, BlackKnight, BlackRook }
527 };
528
529
530 #if (BOARD_FILES>=10)
531 ChessSquare ShogiArray[2][BOARD_FILES] = {
532     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
533         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
534     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
535         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
536 };
537
538 ChessSquare XiangqiArray[2][BOARD_FILES] = {
539     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
540         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
541     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
542         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 };
544
545 ChessSquare CapablancaArray[2][BOARD_FILES] = {
546     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
547         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
549         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
550 };
551
552 ChessSquare GreatArray[2][BOARD_FILES] = {
553     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
554         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
555     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
556         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
557 };
558
559 ChessSquare JanusArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
561         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
562     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
563         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
564 };
565
566 #ifdef GOTHIC
567 ChessSquare GothicArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
569         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
571         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !GOTHIC
574 #define GothicArray CapablancaArray
575 #endif // !GOTHIC
576
577 #ifdef FALCON
578 ChessSquare FalconArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
580         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
582         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
583 };
584 #else // !FALCON
585 #define FalconArray CapablancaArray
586 #endif // !FALCON
587
588 #else // !(BOARD_FILES>=10)
589 #define XiangqiPosition FIDEArray
590 #define CapablancaArray FIDEArray
591 #define GothicArray FIDEArray
592 #define GreatArray FIDEArray
593 #endif // !(BOARD_FILES>=10)
594
595 #if (BOARD_FILES>=12)
596 ChessSquare CourierArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
598         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
600         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
601 };
602 #else // !(BOARD_FILES>=12)
603 #define CourierArray CapablancaArray
604 #endif // !(BOARD_FILES>=12)
605
606
607 Board initialPosition;
608
609
610 /* Convert str to a rating. Checks for special cases of "----",
611
612    "++++", etc. Also strips ()'s */
613 int
614 string_to_rating(str)
615   char *str;
616 {
617   while(*str && !isdigit(*str)) ++str;
618   if (!*str)
619     return 0;   /* One of the special "no rating" cases */
620   else
621     return atoi(str);
622 }
623
624 void
625 ClearProgramStats()
626 {
627     /* Init programStats */
628     programStats.movelist[0] = 0;
629     programStats.depth = 0;
630     programStats.nr_moves = 0;
631     programStats.moves_left = 0;
632     programStats.nodes = 0;
633     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
634     programStats.score = 0;
635     programStats.got_only_move = 0;
636     programStats.got_fail = 0;
637     programStats.line_is_book = 0;
638 }
639
640 void
641 InitBackEnd1()
642 {
643     int matched, min, sec;
644
645     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
646     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
647
648     GetTimeMark(&programStartTime);
649     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
650
651     ClearProgramStats();
652     programStats.ok_to_send = 1;
653     programStats.seen_stat = 0;
654
655     /*
656      * Initialize game list
657      */
658     ListNew(&gameList);
659
660
661     /*
662      * Internet chess server status
663      */
664     if (appData.icsActive) {
665         appData.matchMode = FALSE;
666         appData.matchGames = 0;
667 #if ZIPPY       
668         appData.noChessProgram = !appData.zippyPlay;
669 #else
670         appData.zippyPlay = FALSE;
671         appData.zippyTalk = FALSE;
672         appData.noChessProgram = TRUE;
673 #endif
674         if (*appData.icsHelper != NULLCHAR) {
675             appData.useTelnet = TRUE;
676             appData.telnetProgram = appData.icsHelper;
677         }
678     } else {
679         appData.zippyTalk = appData.zippyPlay = FALSE;
680     }
681
682     /* [AS] Initialize pv info list [HGM] and game state */
683     {
684         int i, j;
685
686         for( i=0; i<=framePtr; i++ ) {
687             pvInfoList[i].depth = -1;
688             boards[i][EP_STATUS] = EP_NONE;
689             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
690         }
691     }
692
693     /*
694      * Parse timeControl resource
695      */
696     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
697                           appData.movesPerSession)) {
698         char buf[MSG_SIZ];
699         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
700         DisplayFatalError(buf, 0, 2);
701     }
702
703     /*
704      * Parse searchTime resource
705      */
706     if (*appData.searchTime != NULLCHAR) {
707         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
708         if (matched == 1) {
709             searchTime = min * 60;
710         } else if (matched == 2) {
711             searchTime = min * 60 + sec;
712         } else {
713             char buf[MSG_SIZ];
714             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
715             DisplayFatalError(buf, 0, 2);
716         }
717     }
718
719     /* [AS] Adjudication threshold */
720     adjudicateLossThreshold = appData.adjudicateLossThreshold;
721     
722     first.which = _("first");
723     second.which = _("second");
724     first.maybeThinking = second.maybeThinking = FALSE;
725     first.pr = second.pr = NoProc;
726     first.isr = second.isr = NULL;
727     first.sendTime = second.sendTime = 2;
728     first.sendDrawOffers = 1;
729     if (appData.firstPlaysBlack) {
730         first.twoMachinesColor = "black\n";
731         second.twoMachinesColor = "white\n";
732     } else {
733         first.twoMachinesColor = "white\n";
734         second.twoMachinesColor = "black\n";
735     }
736     first.program = appData.firstChessProgram;
737     second.program = appData.secondChessProgram;
738     first.host = appData.firstHost;
739     second.host = appData.secondHost;
740     first.dir = appData.firstDirectory;
741     second.dir = appData.secondDirectory;
742     first.other = &second;
743     second.other = &first;
744     first.initString = appData.initString;
745     second.initString = appData.secondInitString;
746     first.computerString = appData.firstComputerString;
747     second.computerString = appData.secondComputerString;
748     first.useSigint = second.useSigint = TRUE;
749     first.useSigterm = second.useSigterm = TRUE;
750     first.reuse = appData.reuseFirst;
751     second.reuse = appData.reuseSecond;
752     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
753     second.nps = appData.secondNPS;
754     first.useSetboard = second.useSetboard = FALSE;
755     first.useSAN = second.useSAN = FALSE;
756     first.usePing = second.usePing = FALSE;
757     first.lastPing = second.lastPing = 0;
758     first.lastPong = second.lastPong = 0;
759     first.usePlayother = second.usePlayother = FALSE;
760     first.useColors = second.useColors = TRUE;
761     first.useUsermove = second.useUsermove = FALSE;
762     first.sendICS = second.sendICS = FALSE;
763     first.sendName = second.sendName = appData.icsActive;
764     first.sdKludge = second.sdKludge = FALSE;
765     first.stKludge = second.stKludge = FALSE;
766     TidyProgramName(first.program, first.host, first.tidy);
767     TidyProgramName(second.program, second.host, second.tidy);
768     first.matchWins = second.matchWins = 0;
769     strcpy(first.variants, appData.variant);
770     strcpy(second.variants, appData.variant);
771     first.analysisSupport = second.analysisSupport = 2; /* detect */
772     first.analyzing = second.analyzing = FALSE;
773     first.initDone = second.initDone = FALSE;
774
775     /* New features added by Tord: */
776     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
777     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
778     /* End of new features added by Tord. */
779     first.fenOverride  = appData.fenOverride1;
780     second.fenOverride = appData.fenOverride2;
781
782     /* [HGM] time odds: set factor for each machine */
783     first.timeOdds  = appData.firstTimeOdds;
784     second.timeOdds = appData.secondTimeOdds;
785     { float norm = 1;
786         if(appData.timeOddsMode) {
787             norm = first.timeOdds;
788             if(norm > second.timeOdds) norm = second.timeOdds;
789         }
790         first.timeOdds /= norm;
791         second.timeOdds /= norm;
792     }
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     first.accumulateTC = appData.firstAccumulateTC;
796     second.accumulateTC = appData.secondAccumulateTC;
797     first.maxNrOfSessions = second.maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     first.debug = second.debug = FALSE;
801     first.supportsNPS = second.supportsNPS = UNKNOWN;
802
803     /* [HGM] options */
804     first.optionSettings  = appData.firstOptions;
805     second.optionSettings = appData.secondOptions;
806
807     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
808     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
809     first.isUCI = appData.firstIsUCI; /* [AS] */
810     second.isUCI = appData.secondIsUCI; /* [AS] */
811     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
812     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
813
814     if (appData.firstProtocolVersion > PROTOVER ||
815         appData.firstProtocolVersion < 1) {
816       char buf[MSG_SIZ];
817       sprintf(buf, _("protocol version %d not supported"),
818               appData.firstProtocolVersion);
819       DisplayFatalError(buf, 0, 2);
820     } else {
821       first.protocolVersion = appData.firstProtocolVersion;
822     }
823
824     if (appData.secondProtocolVersion > PROTOVER ||
825         appData.secondProtocolVersion < 1) {
826       char buf[MSG_SIZ];
827       sprintf(buf, _("protocol version %d not supported"),
828               appData.secondProtocolVersion);
829       DisplayFatalError(buf, 0, 2);
830     } else {
831       second.protocolVersion = appData.secondProtocolVersion;
832     }
833
834     if (appData.icsActive) {
835         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
836 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
837     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
838         appData.clockMode = FALSE;
839         first.sendTime = second.sendTime = 0;
840     }
841     
842 #if ZIPPY
843     /* Override some settings from environment variables, for backward
844        compatibility.  Unfortunately it's not feasible to have the env
845        vars just set defaults, at least in xboard.  Ugh.
846     */
847     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
848       ZippyInit();
849     }
850 #endif
851     
852     if (appData.noChessProgram) {
853         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
854         sprintf(programVersion, "%s", PACKAGE_STRING);
855     } else {
856       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
857       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
858       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
859     }
860
861     if (!appData.icsActive) {
862       char buf[MSG_SIZ];
863       /* Check for variants that are supported only in ICS mode,
864          or not at all.  Some that are accepted here nevertheless
865          have bugs; see comments below.
866       */
867       VariantClass variant = StringToVariant(appData.variant);
868       switch (variant) {
869       case VariantBughouse:     /* need four players and two boards */
870       case VariantKriegspiel:   /* need to hide pieces and move details */
871       /* case VariantFischeRandom: (Fabien: moved below) */
872         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantUnknown:
877       case VariantLoadable:
878       case Variant29:
879       case Variant30:
880       case Variant31:
881       case Variant32:
882       case Variant33:
883       case Variant34:
884       case Variant35:
885       case Variant36:
886       default:
887         sprintf(buf, _("Unknown variant name %s"), appData.variant);
888         DisplayFatalError(buf, 0, 2);
889         return;
890
891       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
892       case VariantFairy:      /* [HGM] TestLegality definitely off! */
893       case VariantGothic:     /* [HGM] should work */
894       case VariantCapablanca: /* [HGM] should work */
895       case VariantCourier:    /* [HGM] initial forced moves not implemented */
896       case VariantShogi:      /* [HGM] drops not tested for legality */
897       case VariantKnightmate: /* [HGM] should work */
898       case VariantCylinder:   /* [HGM] untested */
899       case VariantFalcon:     /* [HGM] untested */
900       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
901                                  offboard interposition not understood */
902       case VariantNormal:     /* definitely works! */
903       case VariantWildCastle: /* pieces not automatically shuffled */
904       case VariantNoCastle:   /* pieces not automatically shuffled */
905       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
906       case VariantLosers:     /* should work except for win condition,
907                                  and doesn't know captures are mandatory */
908       case VariantSuicide:    /* should work except for win condition,
909                                  and doesn't know captures are mandatory */
910       case VariantGiveaway:   /* should work except for win condition,
911                                  and doesn't know captures are mandatory */
912       case VariantTwoKings:   /* should work */
913       case VariantAtomic:     /* should work except for win condition */
914       case Variant3Check:     /* should work except for win condition */
915       case VariantShatranj:   /* should work except for all win conditions */
916       case VariantMakruk:     /* should work except for daw countdown */
917       case VariantBerolina:   /* might work if TestLegality is off */
918       case VariantCapaRandom: /* should work */
919       case VariantJanus:      /* should work */
920       case VariantSuper:      /* experimental */
921       case VariantGreat:      /* experimental, requires legality testing to be off */
922         break;
923       }
924     }
925
926     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
927     InitEngineUCI( installDir, &second );
928 }
929
930 int NextIntegerFromString( char ** str, long * value )
931 {
932     int result = -1;
933     char * s = *str;
934
935     while( *s == ' ' || *s == '\t' ) {
936         s++;
937     }
938
939     *value = 0;
940
941     if( *s >= '0' && *s <= '9' ) {
942         while( *s >= '0' && *s <= '9' ) {
943             *value = *value * 10 + (*s - '0');
944             s++;
945         }
946
947         result = 0;
948     }
949
950     *str = s;
951
952     return result;
953 }
954
955 int NextTimeControlFromString( char ** str, long * value )
956 {
957     long temp;
958     int result = NextIntegerFromString( str, &temp );
959
960     if( result == 0 ) {
961         *value = temp * 60; /* Minutes */
962         if( **str == ':' ) {
963             (*str)++;
964             result = NextIntegerFromString( str, &temp );
965             *value += temp; /* Seconds */
966         }
967     }
968
969     return result;
970 }
971
972 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
973 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
974     int result = -1; long temp, temp2;
975
976     if(**str != '+') return -1; // old params remain in force!
977     (*str)++;
978     if( NextTimeControlFromString( str, &temp ) ) return -1;
979
980     if(**str != '/') {
981         /* time only: incremental or sudden-death time control */
982         if(**str == '+') { /* increment follows; read it */
983             (*str)++;
984             if(result = NextIntegerFromString( str, &temp2)) return -1;
985             *inc = temp2 * 1000;
986         } else *inc = 0;
987         *moves = 0; *tc = temp * 1000; 
988         return 0;
989     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
990
991     (*str)++; /* classical time control */
992     result = NextTimeControlFromString( str, &temp2);
993     if(result == 0) {
994         *moves = temp/60;
995         *tc    = temp2 * 1000;
996         *inc   = 0;
997     }
998     return result;
999 }
1000
1001 int GetTimeQuota(int movenr)
1002 {   /* [HGM] get time to add from the multi-session time-control string */
1003     int moves=1; /* kludge to force reading of first session */
1004     long time, increment;
1005     char *s = fullTimeControlString;
1006
1007     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1008     do {
1009         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1010         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1011         if(movenr == -1) return time;    /* last move before new session     */
1012         if(!moves) return increment;     /* current session is incremental   */
1013         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1014     } while(movenr >= -1);               /* try again for next session       */
1015
1016     return 0; // no new time quota on this move
1017 }
1018
1019 int
1020 ParseTimeControl(tc, ti, mps)
1021      char *tc;
1022      int ti;
1023      int mps;
1024 {
1025   long tc1;
1026   long tc2;
1027   char buf[MSG_SIZ];
1028   
1029   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1030   if(ti > 0) {
1031     if(mps)
1032       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033     else sprintf(buf, "+%s+%d", tc, ti);
1034   } else {
1035     if(mps)
1036              sprintf(buf, "+%d/%s", mps, tc);
1037     else sprintf(buf, "+%s", tc);
1038   }
1039   fullTimeControlString = StrSave(buf);
1040   
1041   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042     return FALSE;
1043   }
1044   
1045   if( *tc == '/' ) {
1046     /* Parse second time control */
1047     tc++;
1048     
1049     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050       return FALSE;
1051     }
1052     
1053     if( tc2 == 0 ) {
1054       return FALSE;
1055     }
1056     
1057     timeControl_2 = tc2 * 1000;
1058   }
1059   else {
1060     timeControl_2 = 0;
1061   }
1062   
1063   if( tc1 == 0 ) {
1064     return FALSE;
1065   }
1066   
1067   timeControl = tc1 * 1000;
1068   
1069   if (ti >= 0) {
1070     timeIncrement = ti * 1000;  /* convert to ms */
1071     movesPerSession = 0;
1072   } else {
1073     timeIncrement = 0;
1074     movesPerSession = mps;
1075   }
1076   return TRUE;
1077 }
1078
1079 void
1080 InitBackEnd2()
1081 {
1082     if (appData.debugMode) {
1083         fprintf(debugFP, "%s\n", programVersion);
1084     }
1085
1086     set_cont_sequence(appData.wrapContSeq);
1087     if (appData.matchGames > 0) {
1088         appData.matchMode = TRUE;
1089     } else if (appData.matchMode) {
1090         appData.matchGames = 1;
1091     }
1092     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093         appData.matchGames = appData.sameColorGames;
1094     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1097     }
1098     Reset(TRUE, FALSE);
1099     if (appData.noChessProgram || first.protocolVersion == 1) {
1100       InitBackEnd3();
1101     } else {
1102       /* kludge: allow timeout for initial "feature" commands */
1103       FreezeUI();
1104       DisplayMessage("", _("Starting chess program"));
1105       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106     }
1107 }
1108
1109 void
1110 InitBackEnd3 P((void))
1111 {
1112     GameMode initialMode;
1113     char buf[MSG_SIZ];
1114     int err;
1115
1116     InitChessProgram(&first, startedFromSetupPosition);
1117
1118     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1119         free(programVersion);
1120         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1121         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1122     }
1123
1124     if (appData.icsActive) {
1125 #ifdef WIN32
1126         /* [DM] Make a console window if needed [HGM] merged ifs */
1127         ConsoleCreate(); 
1128 #endif
1129         err = establish();
1130         if (err != 0) {
1131             if (*appData.icsCommPort != NULLCHAR) {
1132                 sprintf(buf, _("Could not open comm port %s"),  
1133                         appData.icsCommPort);
1134             } else {
1135                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1136                         appData.icsHost, appData.icsPort);
1137             }
1138             DisplayFatalError(buf, err, 1);
1139             return;
1140         }
1141         SetICSMode();
1142         telnetISR =
1143           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1144         fromUserISR =
1145           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1146         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1147             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1148     } else if (appData.noChessProgram) {
1149         SetNCPMode();
1150     } else {
1151         SetGNUMode();
1152     }
1153
1154     if (*appData.cmailGameName != NULLCHAR) {
1155         SetCmailMode();
1156         OpenLoopback(&cmailPR);
1157         cmailISR =
1158           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1159     }
1160     
1161     ThawUI();
1162     DisplayMessage("", "");
1163     if (StrCaseCmp(appData.initialMode, "") == 0) {
1164       initialMode = BeginningOfGame;
1165     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1166       initialMode = TwoMachinesPlay;
1167     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1168       initialMode = AnalyzeFile; 
1169     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1170       initialMode = AnalyzeMode;
1171     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1172       initialMode = MachinePlaysWhite;
1173     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1174       initialMode = MachinePlaysBlack;
1175     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1176       initialMode = EditGame;
1177     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1178       initialMode = EditPosition;
1179     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1180       initialMode = Training;
1181     } else {
1182       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1183       DisplayFatalError(buf, 0, 2);
1184       return;
1185     }
1186
1187     if (appData.matchMode) {
1188         /* Set up machine vs. machine match */
1189         if (appData.noChessProgram) {
1190             DisplayFatalError(_("Can't have a match with no chess programs"),
1191                               0, 2);
1192             return;
1193         }
1194         matchMode = TRUE;
1195         matchGame = 1;
1196         if (*appData.loadGameFile != NULLCHAR) {
1197             int index = appData.loadGameIndex; // [HGM] autoinc
1198             if(index<0) lastIndex = index = 1;
1199             if (!LoadGameFromFile(appData.loadGameFile,
1200                                   index,
1201                                   appData.loadGameFile, FALSE)) {
1202                 DisplayFatalError(_("Bad game file"), 0, 1);
1203                 return;
1204             }
1205         } else if (*appData.loadPositionFile != NULLCHAR) {
1206             int index = appData.loadPositionIndex; // [HGM] autoinc
1207             if(index<0) lastIndex = index = 1;
1208             if (!LoadPositionFromFile(appData.loadPositionFile,
1209                                       index,
1210                                       appData.loadPositionFile)) {
1211                 DisplayFatalError(_("Bad position file"), 0, 1);
1212                 return;
1213             }
1214         }
1215         TwoMachinesEvent();
1216     } else if (*appData.cmailGameName != NULLCHAR) {
1217         /* Set up cmail mode */
1218         ReloadCmailMsgEvent(TRUE);
1219     } else {
1220         /* Set up other modes */
1221         if (initialMode == AnalyzeFile) {
1222           if (*appData.loadGameFile == NULLCHAR) {
1223             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1224             return;
1225           }
1226         }
1227         if (*appData.loadGameFile != NULLCHAR) {
1228             (void) LoadGameFromFile(appData.loadGameFile,
1229                                     appData.loadGameIndex,
1230                                     appData.loadGameFile, TRUE);
1231         } else if (*appData.loadPositionFile != NULLCHAR) {
1232             (void) LoadPositionFromFile(appData.loadPositionFile,
1233                                         appData.loadPositionIndex,
1234                                         appData.loadPositionFile);
1235             /* [HGM] try to make self-starting even after FEN load */
1236             /* to allow automatic setup of fairy variants with wtm */
1237             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1238                 gameMode = BeginningOfGame;
1239                 setboardSpoiledMachineBlack = 1;
1240             }
1241             /* [HGM] loadPos: make that every new game uses the setup */
1242             /* from file as long as we do not switch variant          */
1243             if(!blackPlaysFirst) {
1244                 startedFromPositionFile = TRUE;
1245                 CopyBoard(filePosition, boards[0]);
1246             }
1247         }
1248         if (initialMode == AnalyzeMode) {
1249           if (appData.noChessProgram) {
1250             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1251             return;
1252           }
1253           if (appData.icsActive) {
1254             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1255             return;
1256           }
1257           AnalyzeModeEvent();
1258         } else if (initialMode == AnalyzeFile) {
1259           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1260           ShowThinkingEvent();
1261           AnalyzeFileEvent();
1262           AnalysisPeriodicEvent(1);
1263         } else if (initialMode == MachinePlaysWhite) {
1264           if (appData.noChessProgram) {
1265             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1266                               0, 2);
1267             return;
1268           }
1269           if (appData.icsActive) {
1270             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1271                               0, 2);
1272             return;
1273           }
1274           MachineWhiteEvent();
1275         } else if (initialMode == MachinePlaysBlack) {
1276           if (appData.noChessProgram) {
1277             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1278                               0, 2);
1279             return;
1280           }
1281           if (appData.icsActive) {
1282             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1283                               0, 2);
1284             return;
1285           }
1286           MachineBlackEvent();
1287         } else if (initialMode == TwoMachinesPlay) {
1288           if (appData.noChessProgram) {
1289             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1290                               0, 2);
1291             return;
1292           }
1293           if (appData.icsActive) {
1294             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1295                               0, 2);
1296             return;
1297           }
1298           TwoMachinesEvent();
1299         } else if (initialMode == EditGame) {
1300           EditGameEvent();
1301         } else if (initialMode == EditPosition) {
1302           EditPositionEvent();
1303         } else if (initialMode == Training) {
1304           if (*appData.loadGameFile == NULLCHAR) {
1305             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1306             return;
1307           }
1308           TrainingEvent();
1309         }
1310     }
1311 }
1312
1313 /*
1314  * Establish will establish a contact to a remote host.port.
1315  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1316  *  used to talk to the host.
1317  * Returns 0 if okay, error code if not.
1318  */
1319 int
1320 establish()
1321 {
1322     char buf[MSG_SIZ];
1323
1324     if (*appData.icsCommPort != NULLCHAR) {
1325         /* Talk to the host through a serial comm port */
1326         return OpenCommPort(appData.icsCommPort, &icsPR);
1327
1328     } else if (*appData.gateway != NULLCHAR) {
1329         if (*appData.remoteShell == NULLCHAR) {
1330             /* Use the rcmd protocol to run telnet program on a gateway host */
1331             snprintf(buf, sizeof(buf), "%s %s %s",
1332                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1333             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1334
1335         } else {
1336             /* Use the rsh program to run telnet program on a gateway host */
1337             if (*appData.remoteUser == NULLCHAR) {
1338                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1339                         appData.gateway, appData.telnetProgram,
1340                         appData.icsHost, appData.icsPort);
1341             } else {
1342                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1343                         appData.remoteShell, appData.gateway, 
1344                         appData.remoteUser, appData.telnetProgram,
1345                         appData.icsHost, appData.icsPort);
1346             }
1347             return StartChildProcess(buf, "", &icsPR);
1348
1349         }
1350     } else if (appData.useTelnet) {
1351         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1352
1353     } else {
1354         /* TCP socket interface differs somewhat between
1355            Unix and NT; handle details in the front end.
1356            */
1357         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1358     }
1359 }
1360
1361 void EscapeExpand(char *p, char *q)
1362 {       // [HGM] initstring: routine to shape up string arguments
1363         while(*p++ = *q++) if(p[-1] == '\\')
1364             switch(*q++) {
1365                 case 'n': p[-1] = '\n'; break;
1366                 case 'r': p[-1] = '\r'; break;
1367                 case 't': p[-1] = '\t'; break;
1368                 case '\\': p[-1] = '\\'; break;
1369                 case 0: *p = 0; return;
1370                 default: p[-1] = q[-1]; break;
1371             }
1372 }
1373
1374 void
1375 show_bytes(fp, buf, count)
1376      FILE *fp;
1377      char *buf;
1378      int count;
1379 {
1380     while (count--) {
1381         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1382             fprintf(fp, "\\%03o", *buf & 0xff);
1383         } else {
1384             putc(*buf, fp);
1385         }
1386         buf++;
1387     }
1388     fflush(fp);
1389 }
1390
1391 /* Returns an errno value */
1392 int
1393 OutputMaybeTelnet(pr, message, count, outError)
1394      ProcRef pr;
1395      char *message;
1396      int count;
1397      int *outError;
1398 {
1399     char buf[8192], *p, *q, *buflim;
1400     int left, newcount, outcount;
1401
1402     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1403         *appData.gateway != NULLCHAR) {
1404         if (appData.debugMode) {
1405             fprintf(debugFP, ">ICS: ");
1406             show_bytes(debugFP, message, count);
1407             fprintf(debugFP, "\n");
1408         }
1409         return OutputToProcess(pr, message, count, outError);
1410     }
1411
1412     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1413     p = message;
1414     q = buf;
1415     left = count;
1416     newcount = 0;
1417     while (left) {
1418         if (q >= buflim) {
1419             if (appData.debugMode) {
1420                 fprintf(debugFP, ">ICS: ");
1421                 show_bytes(debugFP, buf, newcount);
1422                 fprintf(debugFP, "\n");
1423             }
1424             outcount = OutputToProcess(pr, buf, newcount, outError);
1425             if (outcount < newcount) return -1; /* to be sure */
1426             q = buf;
1427             newcount = 0;
1428         }
1429         if (*p == '\n') {
1430             *q++ = '\r';
1431             newcount++;
1432         } else if (((unsigned char) *p) == TN_IAC) {
1433             *q++ = (char) TN_IAC;
1434             newcount ++;
1435         }
1436         *q++ = *p++;
1437         newcount++;
1438         left--;
1439     }
1440     if (appData.debugMode) {
1441         fprintf(debugFP, ">ICS: ");
1442         show_bytes(debugFP, buf, newcount);
1443         fprintf(debugFP, "\n");
1444     }
1445     outcount = OutputToProcess(pr, buf, newcount, outError);
1446     if (outcount < newcount) return -1; /* to be sure */
1447     return count;
1448 }
1449
1450 void
1451 read_from_player(isr, closure, message, count, error)
1452      InputSourceRef isr;
1453      VOIDSTAR closure;
1454      char *message;
1455      int count;
1456      int error;
1457 {
1458     int outError, outCount;
1459     static int gotEof = 0;
1460
1461     /* Pass data read from player on to ICS */
1462     if (count > 0) {
1463         gotEof = 0;
1464         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1465         if (outCount < count) {
1466             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1467         }
1468     } else if (count < 0) {
1469         RemoveInputSource(isr);
1470         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1471     } else if (gotEof++ > 0) {
1472         RemoveInputSource(isr);
1473         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1474     }
1475 }
1476
1477 void
1478 KeepAlive()
1479 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1480     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1481     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1482     SendToICS("date\n");
1483     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1484 }
1485
1486 /* added routine for printf style output to ics */
1487 void ics_printf(char *format, ...)
1488 {
1489     char buffer[MSG_SIZ];
1490     va_list args;
1491
1492     va_start(args, format);
1493     vsnprintf(buffer, sizeof(buffer), format, args);
1494     buffer[sizeof(buffer)-1] = '\0';
1495     SendToICS(buffer);
1496     va_end(args);
1497 }
1498
1499 void
1500 SendToICS(s)
1501      char *s;
1502 {
1503     int count, outCount, outError;
1504
1505     if (icsPR == NULL) return;
1506
1507     count = strlen(s);
1508     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1509     if (outCount < count) {
1510         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1511     }
1512 }
1513
1514 /* This is used for sending logon scripts to the ICS. Sending
1515    without a delay causes problems when using timestamp on ICC
1516    (at least on my machine). */
1517 void
1518 SendToICSDelayed(s,msdelay)
1519      char *s;
1520      long msdelay;
1521 {
1522     int count, outCount, outError;
1523
1524     if (icsPR == NULL) return;
1525
1526     count = strlen(s);
1527     if (appData.debugMode) {
1528         fprintf(debugFP, ">ICS: ");
1529         show_bytes(debugFP, s, count);
1530         fprintf(debugFP, "\n");
1531     }
1532     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1533                                       msdelay);
1534     if (outCount < count) {
1535         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1536     }
1537 }
1538
1539
1540 /* Remove all highlighting escape sequences in s
1541    Also deletes any suffix starting with '(' 
1542    */
1543 char *
1544 StripHighlightAndTitle(s)
1545      char *s;
1546 {
1547     static char retbuf[MSG_SIZ];
1548     char *p = retbuf;
1549
1550     while (*s != NULLCHAR) {
1551         while (*s == '\033') {
1552             while (*s != NULLCHAR && !isalpha(*s)) s++;
1553             if (*s != NULLCHAR) s++;
1554         }
1555         while (*s != NULLCHAR && *s != '\033') {
1556             if (*s == '(' || *s == '[') {
1557                 *p = NULLCHAR;
1558                 return retbuf;
1559             }
1560             *p++ = *s++;
1561         }
1562     }
1563     *p = NULLCHAR;
1564     return retbuf;
1565 }
1566
1567 /* Remove all highlighting escape sequences in s */
1568 char *
1569 StripHighlight(s)
1570      char *s;
1571 {
1572     static char retbuf[MSG_SIZ];
1573     char *p = retbuf;
1574
1575     while (*s != NULLCHAR) {
1576         while (*s == '\033') {
1577             while (*s != NULLCHAR && !isalpha(*s)) s++;
1578             if (*s != NULLCHAR) s++;
1579         }
1580         while (*s != NULLCHAR && *s != '\033') {
1581             *p++ = *s++;
1582         }
1583     }
1584     *p = NULLCHAR;
1585     return retbuf;
1586 }
1587
1588 char *variantNames[] = VARIANT_NAMES;
1589 char *
1590 VariantName(v)
1591      VariantClass v;
1592 {
1593     return variantNames[v];
1594 }
1595
1596
1597 /* Identify a variant from the strings the chess servers use or the
1598    PGN Variant tag names we use. */
1599 VariantClass
1600 StringToVariant(e)
1601      char *e;
1602 {
1603     char *p;
1604     int wnum = -1;
1605     VariantClass v = VariantNormal;
1606     int i, found = FALSE;
1607     char buf[MSG_SIZ];
1608
1609     if (!e) return v;
1610
1611     /* [HGM] skip over optional board-size prefixes */
1612     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1613         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1614         while( *e++ != '_');
1615     }
1616
1617     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1618         v = VariantNormal;
1619         found = TRUE;
1620     } else
1621     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1622       if (StrCaseStr(e, variantNames[i])) {
1623         v = (VariantClass) i;
1624         found = TRUE;
1625         break;
1626       }
1627     }
1628
1629     if (!found) {
1630       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1631           || StrCaseStr(e, "wild/fr") 
1632           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1633         v = VariantFischeRandom;
1634       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1635                  (i = 1, p = StrCaseStr(e, "w"))) {
1636         p += i;
1637         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1638         if (isdigit(*p)) {
1639           wnum = atoi(p);
1640         } else {
1641           wnum = -1;
1642         }
1643         switch (wnum) {
1644         case 0: /* FICS only, actually */
1645         case 1:
1646           /* Castling legal even if K starts on d-file */
1647           v = VariantWildCastle;
1648           break;
1649         case 2:
1650         case 3:
1651         case 4:
1652           /* Castling illegal even if K & R happen to start in
1653              normal positions. */
1654           v = VariantNoCastle;
1655           break;
1656         case 5:
1657         case 7:
1658         case 8:
1659         case 10:
1660         case 11:
1661         case 12:
1662         case 13:
1663         case 14:
1664         case 15:
1665         case 18:
1666         case 19:
1667           /* Castling legal iff K & R start in normal positions */
1668           v = VariantNormal;
1669           break;
1670         case 6:
1671         case 20:
1672         case 21:
1673           /* Special wilds for position setup; unclear what to do here */
1674           v = VariantLoadable;
1675           break;
1676         case 9:
1677           /* Bizarre ICC game */
1678           v = VariantTwoKings;
1679           break;
1680         case 16:
1681           v = VariantKriegspiel;
1682           break;
1683         case 17:
1684           v = VariantLosers;
1685           break;
1686         case 22:
1687           v = VariantFischeRandom;
1688           break;
1689         case 23:
1690           v = VariantCrazyhouse;
1691           break;
1692         case 24:
1693           v = VariantBughouse;
1694           break;
1695         case 25:
1696           v = Variant3Check;
1697           break;
1698         case 26:
1699           /* Not quite the same as FICS suicide! */
1700           v = VariantGiveaway;
1701           break;
1702         case 27:
1703           v = VariantAtomic;
1704           break;
1705         case 28:
1706           v = VariantShatranj;
1707           break;
1708
1709         /* Temporary names for future ICC types.  The name *will* change in 
1710            the next xboard/WinBoard release after ICC defines it. */
1711         case 29:
1712           v = Variant29;
1713           break;
1714         case 30:
1715           v = Variant30;
1716           break;
1717         case 31:
1718           v = Variant31;
1719           break;
1720         case 32:
1721           v = Variant32;
1722           break;
1723         case 33:
1724           v = Variant33;
1725           break;
1726         case 34:
1727           v = Variant34;
1728           break;
1729         case 35:
1730           v = Variant35;
1731           break;
1732         case 36:
1733           v = Variant36;
1734           break;
1735         case 37:
1736           v = VariantShogi;
1737           break;
1738         case 38:
1739           v = VariantXiangqi;
1740           break;
1741         case 39:
1742           v = VariantCourier;
1743           break;
1744         case 40:
1745           v = VariantGothic;
1746           break;
1747         case 41:
1748           v = VariantCapablanca;
1749           break;
1750         case 42:
1751           v = VariantKnightmate;
1752           break;
1753         case 43:
1754           v = VariantFairy;
1755           break;
1756         case 44:
1757           v = VariantCylinder;
1758           break;
1759         case 45:
1760           v = VariantFalcon;
1761           break;
1762         case 46:
1763           v = VariantCapaRandom;
1764           break;
1765         case 47:
1766           v = VariantBerolina;
1767           break;
1768         case 48:
1769           v = VariantJanus;
1770           break;
1771         case 49:
1772           v = VariantSuper;
1773           break;
1774         case 50:
1775           v = VariantGreat;
1776           break;
1777         case -1:
1778           /* Found "wild" or "w" in the string but no number;
1779              must assume it's normal chess. */
1780           v = VariantNormal;
1781           break;
1782         default:
1783           sprintf(buf, _("Unknown wild type %d"), wnum);
1784           DisplayError(buf, 0);
1785           v = VariantUnknown;
1786           break;
1787         }
1788       }
1789     }
1790     if (appData.debugMode) {
1791       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1792               e, wnum, VariantName(v));
1793     }
1794     return v;
1795 }
1796
1797 static int leftover_start = 0, leftover_len = 0;
1798 char star_match[STAR_MATCH_N][MSG_SIZ];
1799
1800 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1801    advance *index beyond it, and set leftover_start to the new value of
1802    *index; else return FALSE.  If pattern contains the character '*', it
1803    matches any sequence of characters not containing '\r', '\n', or the
1804    character following the '*' (if any), and the matched sequence(s) are
1805    copied into star_match.
1806    */
1807 int
1808 looking_at(buf, index, pattern)
1809      char *buf;
1810      int *index;
1811      char *pattern;
1812 {
1813     char *bufp = &buf[*index], *patternp = pattern;
1814     int star_count = 0;
1815     char *matchp = star_match[0];
1816     
1817     for (;;) {
1818         if (*patternp == NULLCHAR) {
1819             *index = leftover_start = bufp - buf;
1820             *matchp = NULLCHAR;
1821             return TRUE;
1822         }
1823         if (*bufp == NULLCHAR) return FALSE;
1824         if (*patternp == '*') {
1825             if (*bufp == *(patternp + 1)) {
1826                 *matchp = NULLCHAR;
1827                 matchp = star_match[++star_count];
1828                 patternp += 2;
1829                 bufp++;
1830                 continue;
1831             } else if (*bufp == '\n' || *bufp == '\r') {
1832                 patternp++;
1833                 if (*patternp == NULLCHAR)
1834                   continue;
1835                 else
1836                   return FALSE;
1837             } else {
1838                 *matchp++ = *bufp++;
1839                 continue;
1840             }
1841         }
1842         if (*patternp != *bufp) return FALSE;
1843         patternp++;
1844         bufp++;
1845     }
1846 }
1847
1848 void
1849 SendToPlayer(data, length)
1850      char *data;
1851      int length;
1852 {
1853     int error, outCount;
1854     outCount = OutputToProcess(NoProc, data, length, &error);
1855     if (outCount < length) {
1856         DisplayFatalError(_("Error writing to display"), error, 1);
1857     }
1858 }
1859
1860 void
1861 PackHolding(packed, holding)
1862      char packed[];
1863      char *holding;
1864 {
1865     char *p = holding;
1866     char *q = packed;
1867     int runlength = 0;
1868     int curr = 9999;
1869     do {
1870         if (*p == curr) {
1871             runlength++;
1872         } else {
1873             switch (runlength) {
1874               case 0:
1875                 break;
1876               case 1:
1877                 *q++ = curr;
1878                 break;
1879               case 2:
1880                 *q++ = curr;
1881                 *q++ = curr;
1882                 break;
1883               default:
1884                 sprintf(q, "%d", runlength);
1885                 while (*q) q++;
1886                 *q++ = curr;
1887                 break;
1888             }
1889             runlength = 1;
1890             curr = *p;
1891         }
1892     } while (*p++);
1893     *q = NULLCHAR;
1894 }
1895
1896 /* Telnet protocol requests from the front end */
1897 void
1898 TelnetRequest(ddww, option)
1899      unsigned char ddww, option;
1900 {
1901     unsigned char msg[3];
1902     int outCount, outError;
1903
1904     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1905
1906     if (appData.debugMode) {
1907         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1908         switch (ddww) {
1909           case TN_DO:
1910             ddwwStr = "DO";
1911             break;
1912           case TN_DONT:
1913             ddwwStr = "DONT";
1914             break;
1915           case TN_WILL:
1916             ddwwStr = "WILL";
1917             break;
1918           case TN_WONT:
1919             ddwwStr = "WONT";
1920             break;
1921           default:
1922             ddwwStr = buf1;
1923             sprintf(buf1, "%d", ddww);
1924             break;
1925         }
1926         switch (option) {
1927           case TN_ECHO:
1928             optionStr = "ECHO";
1929             break;
1930           default:
1931             optionStr = buf2;
1932             sprintf(buf2, "%d", option);
1933             break;
1934         }
1935         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1936     }
1937     msg[0] = TN_IAC;
1938     msg[1] = ddww;
1939     msg[2] = option;
1940     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1941     if (outCount < 3) {
1942         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1943     }
1944 }
1945
1946 void
1947 DoEcho()
1948 {
1949     if (!appData.icsActive) return;
1950     TelnetRequest(TN_DO, TN_ECHO);
1951 }
1952
1953 void
1954 DontEcho()
1955 {
1956     if (!appData.icsActive) return;
1957     TelnetRequest(TN_DONT, TN_ECHO);
1958 }
1959
1960 void
1961 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1962 {
1963     /* put the holdings sent to us by the server on the board holdings area */
1964     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1965     char p;
1966     ChessSquare piece;
1967
1968     if(gameInfo.holdingsWidth < 2)  return;
1969     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1970         return; // prevent overwriting by pre-board holdings
1971
1972     if( (int)lowestPiece >= BlackPawn ) {
1973         holdingsColumn = 0;
1974         countsColumn = 1;
1975         holdingsStartRow = BOARD_HEIGHT-1;
1976         direction = -1;
1977     } else {
1978         holdingsColumn = BOARD_WIDTH-1;
1979         countsColumn = BOARD_WIDTH-2;
1980         holdingsStartRow = 0;
1981         direction = 1;
1982     }
1983
1984     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1985         board[i][holdingsColumn] = EmptySquare;
1986         board[i][countsColumn]   = (ChessSquare) 0;
1987     }
1988     while( (p=*holdings++) != NULLCHAR ) {
1989         piece = CharToPiece( ToUpper(p) );
1990         if(piece == EmptySquare) continue;
1991         /*j = (int) piece - (int) WhitePawn;*/
1992         j = PieceToNumber(piece);
1993         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1994         if(j < 0) continue;               /* should not happen */
1995         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1996         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1997         board[holdingsStartRow+j*direction][countsColumn]++;
1998     }
1999 }
2000
2001
2002 void
2003 VariantSwitch(Board board, VariantClass newVariant)
2004 {
2005    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2006    static Board oldBoard;
2007
2008    startedFromPositionFile = FALSE;
2009    if(gameInfo.variant == newVariant) return;
2010
2011    /* [HGM] This routine is called each time an assignment is made to
2012     * gameInfo.variant during a game, to make sure the board sizes
2013     * are set to match the new variant. If that means adding or deleting
2014     * holdings, we shift the playing board accordingly
2015     * This kludge is needed because in ICS observe mode, we get boards
2016     * of an ongoing game without knowing the variant, and learn about the
2017     * latter only later. This can be because of the move list we requested,
2018     * in which case the game history is refilled from the beginning anyway,
2019     * but also when receiving holdings of a crazyhouse game. In the latter
2020     * case we want to add those holdings to the already received position.
2021     */
2022
2023    
2024    if (appData.debugMode) {
2025      fprintf(debugFP, "Switch board from %s to %s\n",
2026              VariantName(gameInfo.variant), VariantName(newVariant));
2027      setbuf(debugFP, NULL);
2028    }
2029    shuffleOpenings = 0;       /* [HGM] shuffle */
2030    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2031    switch(newVariant) 
2032      {
2033      case VariantShogi:
2034        newWidth = 9;  newHeight = 9;
2035        gameInfo.holdingsSize = 7;
2036      case VariantBughouse:
2037      case VariantCrazyhouse:
2038        newHoldingsWidth = 2; break;
2039      case VariantGreat:
2040        newWidth = 10;
2041      case VariantSuper:
2042        newHoldingsWidth = 2;
2043        gameInfo.holdingsSize = 8;
2044        break;
2045      case VariantGothic:
2046      case VariantCapablanca:
2047      case VariantCapaRandom:
2048        newWidth = 10;
2049      default:
2050        newHoldingsWidth = gameInfo.holdingsSize = 0;
2051      };
2052    
2053    if(newWidth  != gameInfo.boardWidth  ||
2054       newHeight != gameInfo.boardHeight ||
2055       newHoldingsWidth != gameInfo.holdingsWidth ) {
2056      
2057      /* shift position to new playing area, if needed */
2058      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2059        for(i=0; i<BOARD_HEIGHT; i++) 
2060          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2061            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2062              board[i][j];
2063        for(i=0; i<newHeight; i++) {
2064          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2065          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2066        }
2067      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2068        for(i=0; i<BOARD_HEIGHT; i++)
2069          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2070            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2071              board[i][j];
2072      }
2073      gameInfo.boardWidth  = newWidth;
2074      gameInfo.boardHeight = newHeight;
2075      gameInfo.holdingsWidth = newHoldingsWidth;
2076      gameInfo.variant = newVariant;
2077      InitDrawingSizes(-2, 0);
2078    } else gameInfo.variant = newVariant;
2079    CopyBoard(oldBoard, board);   // remember correctly formatted board
2080      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2081    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2082 }
2083
2084 static int loggedOn = FALSE;
2085
2086 /*-- Game start info cache: --*/
2087 int gs_gamenum;
2088 char gs_kind[MSG_SIZ];
2089 static char player1Name[128] = "";
2090 static char player2Name[128] = "";
2091 static char cont_seq[] = "\n\\   ";
2092 static int player1Rating = -1;
2093 static int player2Rating = -1;
2094 /*----------------------------*/
2095
2096 ColorClass curColor = ColorNormal;
2097 int suppressKibitz = 0;
2098
2099 // [HGM] seekgraph
2100 Boolean soughtPending = FALSE;
2101 Boolean seekGraphUp;
2102 #define MAX_SEEK_ADS 200
2103 #define SQUARE 0x80
2104 char *seekAdList[MAX_SEEK_ADS];
2105 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2106 float tcList[MAX_SEEK_ADS];
2107 char colorList[MAX_SEEK_ADS];
2108 int nrOfSeekAds = 0;
2109 int minRating = 1010, maxRating = 2800;
2110 int hMargin = 10, vMargin = 20, h, w;
2111 extern int squareSize, lineGap;
2112
2113 void
2114 PlotSeekAd(int i)
2115 {
2116         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2117         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2118         if(r < minRating+100 && r >=0 ) r = minRating+100;
2119         if(r > maxRating) r = maxRating;
2120         if(tc < 1.) tc = 1.;
2121         if(tc > 95.) tc = 95.;
2122         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2123         y = ((double)r - minRating)/(maxRating - minRating)
2124             * (h-vMargin-squareSize/8-1) + vMargin;
2125         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2126         if(strstr(seekAdList[i], " u ")) color = 1;
2127         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2128            !strstr(seekAdList[i], "bullet") &&
2129            !strstr(seekAdList[i], "blitz") &&
2130            !strstr(seekAdList[i], "standard") ) color = 2;
2131         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2132         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2133 }
2134
2135 void
2136 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2137 {
2138         char buf[MSG_SIZ], *ext = "";
2139         VariantClass v = StringToVariant(type);
2140         if(strstr(type, "wild")) {
2141             ext = type + 4; // append wild number
2142             if(v == VariantFischeRandom) type = "chess960"; else
2143             if(v == VariantLoadable) type = "setup"; else
2144             type = VariantName(v);
2145         }
2146         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2147         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2148             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2149             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2150             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2151             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2152             seekNrList[nrOfSeekAds] = nr;
2153             zList[nrOfSeekAds] = 0;
2154             seekAdList[nrOfSeekAds++] = StrSave(buf);
2155             if(plot) PlotSeekAd(nrOfSeekAds-1);
2156         }
2157 }
2158
2159 void
2160 EraseSeekDot(int i)
2161 {
2162     int x = xList[i], y = yList[i], d=squareSize/4, k;
2163     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2164     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2165     // now replot every dot that overlapped
2166     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2167         int xx = xList[k], yy = yList[k];
2168         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2169             DrawSeekDot(xx, yy, colorList[k]);
2170     }
2171 }
2172
2173 void
2174 RemoveSeekAd(int nr)
2175 {
2176         int i;
2177         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2178             EraseSeekDot(i);
2179             if(seekAdList[i]) free(seekAdList[i]);
2180             seekAdList[i] = seekAdList[--nrOfSeekAds];
2181             seekNrList[i] = seekNrList[nrOfSeekAds];
2182             ratingList[i] = ratingList[nrOfSeekAds];
2183             colorList[i]  = colorList[nrOfSeekAds];
2184             tcList[i] = tcList[nrOfSeekAds];
2185             xList[i]  = xList[nrOfSeekAds];
2186             yList[i]  = yList[nrOfSeekAds];
2187             zList[i]  = zList[nrOfSeekAds];
2188             seekAdList[nrOfSeekAds] = NULL;
2189             break;
2190         }
2191 }
2192
2193 Boolean
2194 MatchSoughtLine(char *line)
2195 {
2196     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2197     int nr, base, inc, u=0; char dummy;
2198
2199     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2200        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2201        (u=1) &&
2202        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2203         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2204         // match: compact and save the line
2205         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2206         return TRUE;
2207     }
2208     return FALSE;
2209 }
2210
2211 int
2212 DrawSeekGraph()
2213 {
2214     int i;
2215     if(!seekGraphUp) return FALSE;
2216     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2217     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2218
2219     DrawSeekBackground(0, 0, w, h);
2220     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2221     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2222     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2223         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2224         yy = h-1-yy;
2225         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2226         if(i%500 == 0) {
2227             char buf[MSG_SIZ];
2228             sprintf(buf, "%d", i);
2229             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2230         }
2231     }
2232     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2233     for(i=1; i<100; i+=(i<10?1:5)) {
2234         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2235         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2236         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2237             char buf[MSG_SIZ];
2238             sprintf(buf, "%d", i);
2239             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2240         }
2241     }
2242     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2243     return TRUE;
2244 }
2245
2246 int SeekGraphClick(ClickType click, int x, int y, int moving)
2247 {
2248     static int lastDown = 0, displayed = 0, lastSecond;
2249     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2250         if(click == Release || moving) return FALSE;
2251         nrOfSeekAds = 0;
2252         soughtPending = TRUE;
2253         SendToICS(ics_prefix);
2254         SendToICS("sought\n"); // should this be "sought all"?
2255     } else { // issue challenge based on clicked ad
2256         int dist = 10000; int i, closest = 0, second = 0;
2257         for(i=0; i<nrOfSeekAds; i++) {
2258             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2259             if(d < dist) { dist = d; closest = i; }
2260             second += (d - zList[i] < 120); // count in-range ads
2261             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2262         }
2263         if(dist < 120) {
2264             char buf[MSG_SIZ];
2265             second = (second > 1);
2266             if(displayed != closest || second != lastSecond) {
2267                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2268                 lastSecond = second; displayed = closest;
2269             }
2270             if(click == Press) {
2271                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2272                 lastDown = closest;
2273                 return TRUE;
2274             } // on press 'hit', only show info
2275             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2276             sprintf(buf, "play %d\n", seekNrList[closest]);
2277             SendToICS(ics_prefix);
2278             SendToICS(buf);
2279             return TRUE; // let incoming board of started game pop down the graph
2280         } else if(click == Release) { // release 'miss' is ignored
2281             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2282             if(moving == 2) { // right up-click
2283                 nrOfSeekAds = 0; // refresh graph
2284                 soughtPending = TRUE;
2285                 SendToICS(ics_prefix);
2286                 SendToICS("sought\n"); // should this be "sought all"?
2287             }
2288             return TRUE;
2289         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2290         // press miss or release hit 'pop down' seek graph
2291         seekGraphUp = FALSE;
2292         DrawPosition(TRUE, NULL);
2293     }
2294     return TRUE;
2295 }
2296
2297 void
2298 read_from_ics(isr, closure, data, count, error)
2299      InputSourceRef isr;
2300      VOIDSTAR closure;
2301      char *data;
2302      int count;
2303      int error;
2304 {
2305 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2306 #define STARTED_NONE 0
2307 #define STARTED_MOVES 1
2308 #define STARTED_BOARD 2
2309 #define STARTED_OBSERVE 3
2310 #define STARTED_HOLDINGS 4
2311 #define STARTED_CHATTER 5
2312 #define STARTED_COMMENT 6
2313 #define STARTED_MOVES_NOHIDE 7
2314     
2315     static int started = STARTED_NONE;
2316     static char parse[20000];
2317     static int parse_pos = 0;
2318     static char buf[BUF_SIZE + 1];
2319     static int firstTime = TRUE, intfSet = FALSE;
2320     static ColorClass prevColor = ColorNormal;
2321     static int savingComment = FALSE;
2322     static int cmatch = 0; // continuation sequence match
2323     char *bp;
2324     char str[500];
2325     int i, oldi;
2326     int buf_len;
2327     int next_out;
2328     int tkind;
2329     int backup;    /* [DM] For zippy color lines */
2330     char *p;
2331     char talker[MSG_SIZ]; // [HGM] chat
2332     int channel;
2333
2334     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2335
2336     if (appData.debugMode) {
2337       if (!error) {
2338         fprintf(debugFP, "<ICS: ");
2339         show_bytes(debugFP, data, count);
2340         fprintf(debugFP, "\n");
2341       }
2342     }
2343
2344     if (appData.debugMode) { int f = forwardMostMove;
2345         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2346                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2347                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2348     }
2349     if (count > 0) {
2350         /* If last read ended with a partial line that we couldn't parse,
2351            prepend it to the new read and try again. */
2352         if (leftover_len > 0) {
2353             for (i=0; i<leftover_len; i++)
2354               buf[i] = buf[leftover_start + i];
2355         }
2356
2357     /* copy new characters into the buffer */
2358     bp = buf + leftover_len;
2359     buf_len=leftover_len;
2360     for (i=0; i<count; i++)
2361     {
2362         // ignore these
2363         if (data[i] == '\r')
2364             continue;
2365
2366         // join lines split by ICS?
2367         if (!appData.noJoin)
2368         {
2369             /*
2370                 Joining just consists of finding matches against the
2371                 continuation sequence, and discarding that sequence
2372                 if found instead of copying it.  So, until a match
2373                 fails, there's nothing to do since it might be the
2374                 complete sequence, and thus, something we don't want
2375                 copied.
2376             */
2377             if (data[i] == cont_seq[cmatch])
2378             {
2379                 cmatch++;
2380                 if (cmatch == strlen(cont_seq))
2381                 {
2382                     cmatch = 0; // complete match.  just reset the counter
2383
2384                     /*
2385                         it's possible for the ICS to not include the space
2386                         at the end of the last word, making our [correct]
2387                         join operation fuse two separate words.  the server
2388                         does this when the space occurs at the width setting.
2389                     */
2390                     if (!buf_len || buf[buf_len-1] != ' ')
2391                     {
2392                         *bp++ = ' ';
2393                         buf_len++;
2394                     }
2395                 }
2396                 continue;
2397             }
2398             else if (cmatch)
2399             {
2400                 /*
2401                     match failed, so we have to copy what matched before
2402                     falling through and copying this character.  In reality,
2403                     this will only ever be just the newline character, but
2404                     it doesn't hurt to be precise.
2405                 */
2406                 strncpy(bp, cont_seq, cmatch);
2407                 bp += cmatch;
2408                 buf_len += cmatch;
2409                 cmatch = 0;
2410             }
2411         }
2412
2413         // copy this char
2414         *bp++ = data[i];
2415         buf_len++;
2416     }
2417
2418         buf[buf_len] = NULLCHAR;
2419 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2420         next_out = 0;
2421         leftover_start = 0;
2422         
2423         i = 0;
2424         while (i < buf_len) {
2425             /* Deal with part of the TELNET option negotiation
2426                protocol.  We refuse to do anything beyond the
2427                defaults, except that we allow the WILL ECHO option,
2428                which ICS uses to turn off password echoing when we are
2429                directly connected to it.  We reject this option
2430                if localLineEditing mode is on (always on in xboard)
2431                and we are talking to port 23, which might be a real
2432                telnet server that will try to keep WILL ECHO on permanently.
2433              */
2434             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2435                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2436                 unsigned char option;
2437                 oldi = i;
2438                 switch ((unsigned char) buf[++i]) {
2439                   case TN_WILL:
2440                     if (appData.debugMode)
2441                       fprintf(debugFP, "\n<WILL ");
2442                     switch (option = (unsigned char) buf[++i]) {
2443                       case TN_ECHO:
2444                         if (appData.debugMode)
2445                           fprintf(debugFP, "ECHO ");
2446                         /* Reply only if this is a change, according
2447                            to the protocol rules. */
2448                         if (remoteEchoOption) break;
2449                         if (appData.localLineEditing &&
2450                             atoi(appData.icsPort) == TN_PORT) {
2451                             TelnetRequest(TN_DONT, TN_ECHO);
2452                         } else {
2453                             EchoOff();
2454                             TelnetRequest(TN_DO, TN_ECHO);
2455                             remoteEchoOption = TRUE;
2456                         }
2457                         break;
2458                       default:
2459                         if (appData.debugMode)
2460                           fprintf(debugFP, "%d ", option);
2461                         /* Whatever this is, we don't want it. */
2462                         TelnetRequest(TN_DONT, option);
2463                         break;
2464                     }
2465                     break;
2466                   case TN_WONT:
2467                     if (appData.debugMode)
2468                       fprintf(debugFP, "\n<WONT ");
2469                     switch (option = (unsigned char) buf[++i]) {
2470                       case TN_ECHO:
2471                         if (appData.debugMode)
2472                           fprintf(debugFP, "ECHO ");
2473                         /* Reply only if this is a change, according
2474                            to the protocol rules. */
2475                         if (!remoteEchoOption) break;
2476                         EchoOn();
2477                         TelnetRequest(TN_DONT, TN_ECHO);
2478                         remoteEchoOption = FALSE;
2479                         break;
2480                       default:
2481                         if (appData.debugMode)
2482                           fprintf(debugFP, "%d ", (unsigned char) option);
2483                         /* Whatever this is, it must already be turned
2484                            off, because we never agree to turn on
2485                            anything non-default, so according to the
2486                            protocol rules, we don't reply. */
2487                         break;
2488                     }
2489                     break;
2490                   case TN_DO:
2491                     if (appData.debugMode)
2492                       fprintf(debugFP, "\n<DO ");
2493                     switch (option = (unsigned char) buf[++i]) {
2494                       default:
2495                         /* Whatever this is, we refuse to do it. */
2496                         if (appData.debugMode)
2497                           fprintf(debugFP, "%d ", option);
2498                         TelnetRequest(TN_WONT, option);
2499                         break;
2500                     }
2501                     break;
2502                   case TN_DONT:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<DONT ");
2505                     switch (option = (unsigned char) buf[++i]) {
2506                       default:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "%d ", option);
2509                         /* Whatever this is, we are already not doing
2510                            it, because we never agree to do anything
2511                            non-default, so according to the protocol
2512                            rules, we don't reply. */
2513                         break;
2514                     }
2515                     break;
2516                   case TN_IAC:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<IAC ");
2519                     /* Doubled IAC; pass it through */
2520                     i--;
2521                     break;
2522                   default:
2523                     if (appData.debugMode)
2524                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2525                     /* Drop all other telnet commands on the floor */
2526                     break;
2527                 }
2528                 if (oldi > next_out)
2529                   SendToPlayer(&buf[next_out], oldi - next_out);
2530                 if (++i > next_out)
2531                   next_out = i;
2532                 continue;
2533             }
2534                 
2535             /* OK, this at least will *usually* work */
2536             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2537                 loggedOn = TRUE;
2538             }
2539             
2540             if (loggedOn && !intfSet) {
2541                 if (ics_type == ICS_ICC) {
2542                   sprintf(str,
2543                           "/set-quietly interface %s\n/set-quietly style 12\n",
2544                           programVersion);
2545                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2546                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2547                 } else if (ics_type == ICS_CHESSNET) {
2548                   sprintf(str, "/style 12\n");
2549                 } else {
2550                   strcpy(str, "alias $ @\n$set interface ");
2551                   strcat(str, programVersion);
2552                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2553                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2554                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2555 #ifdef WIN32
2556                   strcat(str, "$iset nohighlight 1\n");
2557 #endif
2558                   strcat(str, "$iset lock 1\n$style 12\n");
2559                 }
2560                 SendToICS(str);
2561                 NotifyFrontendLogin();
2562                 intfSet = TRUE;
2563             }
2564
2565             if (started == STARTED_COMMENT) {
2566                 /* Accumulate characters in comment */
2567                 parse[parse_pos++] = buf[i];
2568                 if (buf[i] == '\n') {
2569                     parse[parse_pos] = NULLCHAR;
2570                     if(chattingPartner>=0) {
2571                         char mess[MSG_SIZ];
2572                         sprintf(mess, "%s%s", talker, parse);
2573                         OutputChatMessage(chattingPartner, mess);
2574                         chattingPartner = -1;
2575                         next_out = i+1; // [HGM] suppress printing in ICS window
2576                     } else
2577                     if(!suppressKibitz) // [HGM] kibitz
2578                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2579                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2580                         int nrDigit = 0, nrAlph = 0, j;
2581                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2582                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2583                         parse[parse_pos] = NULLCHAR;
2584                         // try to be smart: if it does not look like search info, it should go to
2585                         // ICS interaction window after all, not to engine-output window.
2586                         for(j=0; j<parse_pos; j++) { // count letters and digits
2587                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2588                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2589                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2590                         }
2591                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2592                             int depth=0; float score;
2593                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2594                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2595                                 pvInfoList[forwardMostMove-1].depth = depth;
2596                                 pvInfoList[forwardMostMove-1].score = 100*score;
2597                             }
2598                             OutputKibitz(suppressKibitz, parse);
2599                         } else {
2600                             char tmp[MSG_SIZ];
2601                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2602                             SendToPlayer(tmp, strlen(tmp));
2603                         }
2604                         next_out = i+1; // [HGM] suppress printing in ICS window
2605                     }
2606                     started = STARTED_NONE;
2607                 } else {
2608                     /* Don't match patterns against characters in comment */
2609                     i++;
2610                     continue;
2611                 }
2612             }
2613             if (started == STARTED_CHATTER) {
2614                 if (buf[i] != '\n') {
2615                     /* Don't match patterns against characters in chatter */
2616                     i++;
2617                     continue;
2618                 }
2619                 started = STARTED_NONE;
2620                 if(suppressKibitz) next_out = i+1;
2621             }
2622
2623             /* Kludge to deal with rcmd protocol */
2624             if (firstTime && looking_at(buf, &i, "\001*")) {
2625                 DisplayFatalError(&buf[1], 0, 1);
2626                 continue;
2627             } else {
2628                 firstTime = FALSE;
2629             }
2630
2631             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2632                 ics_type = ICS_ICC;
2633                 ics_prefix = "/";
2634                 if (appData.debugMode)
2635                   fprintf(debugFP, "ics_type %d\n", ics_type);
2636                 continue;
2637             }
2638             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2639                 ics_type = ICS_FICS;
2640                 ics_prefix = "$";
2641                 if (appData.debugMode)
2642                   fprintf(debugFP, "ics_type %d\n", ics_type);
2643                 continue;
2644             }
2645             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2646                 ics_type = ICS_CHESSNET;
2647                 ics_prefix = "/";
2648                 if (appData.debugMode)
2649                   fprintf(debugFP, "ics_type %d\n", ics_type);
2650                 continue;
2651             }
2652
2653             if (!loggedOn &&
2654                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2655                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2656                  looking_at(buf, &i, "will be \"*\""))) {
2657               strcpy(ics_handle, star_match[0]);
2658               continue;
2659             }
2660
2661             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2662               char buf[MSG_SIZ];
2663               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2664               DisplayIcsInteractionTitle(buf);
2665               have_set_title = TRUE;
2666             }
2667
2668             /* skip finger notes */
2669             if (started == STARTED_NONE &&
2670                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2671                  (buf[i] == '1' && buf[i+1] == '0')) &&
2672                 buf[i+2] == ':' && buf[i+3] == ' ') {
2673               started = STARTED_CHATTER;
2674               i += 3;
2675               continue;
2676             }
2677
2678             oldi = i;
2679             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2680             if(appData.seekGraph) {
2681                 if(soughtPending && MatchSoughtLine(buf+i)) {
2682                     i = strstr(buf+i, "rated") - buf;
2683                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2684                     next_out = leftover_start = i;
2685                     started = STARTED_CHATTER;
2686                     suppressKibitz = TRUE;
2687                     continue;
2688                 }
2689                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2690                         && looking_at(buf, &i, "* ads displayed")) {
2691                     soughtPending = FALSE;
2692                     seekGraphUp = TRUE;
2693                     DrawSeekGraph();
2694                     continue;
2695                 }
2696                 if(appData.autoRefresh) {
2697                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2698                         int s = (ics_type == ICS_ICC); // ICC format differs
2699                         if(seekGraphUp)
2700                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2701                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2702                         looking_at(buf, &i, "*% "); // eat prompt
2703                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2704                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2705                         next_out = i; // suppress
2706                         continue;
2707                     }
2708                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2709                         char *p = star_match[0];
2710                         while(*p) {
2711                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2712                             while(*p && *p++ != ' '); // next
2713                         }
2714                         looking_at(buf, &i, "*% "); // eat prompt
2715                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2716                         next_out = i;
2717                         continue;
2718                     }
2719                 }
2720             }
2721
2722             /* skip formula vars */
2723             if (started == STARTED_NONE &&
2724                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2725               started = STARTED_CHATTER;
2726               i += 3;
2727               continue;
2728             }
2729
2730             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2731             if (appData.autoKibitz && started == STARTED_NONE && 
2732                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2733                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2734                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2735                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2736                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2737                         suppressKibitz = TRUE;
2738                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2739                         next_out = i;
2740                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2741                                 && (gameMode == IcsPlayingWhite)) ||
2742                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2743                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2744                             started = STARTED_CHATTER; // own kibitz we simply discard
2745                         else {
2746                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2747                             parse_pos = 0; parse[0] = NULLCHAR;
2748                             savingComment = TRUE;
2749                             suppressKibitz = gameMode != IcsObserving ? 2 :
2750                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2751                         } 
2752                         continue;
2753                 } else
2754                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2755                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2756                          && atoi(star_match[0])) {
2757                     // suppress the acknowledgements of our own autoKibitz
2758                     char *p;
2759                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2760                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2761                     SendToPlayer(star_match[0], strlen(star_match[0]));
2762                     if(looking_at(buf, &i, "*% ")) // eat prompt
2763                         suppressKibitz = FALSE;
2764                     next_out = i;
2765                     continue;
2766                 }
2767             } // [HGM] kibitz: end of patch
2768
2769             // [HGM] chat: intercept tells by users for which we have an open chat window
2770             channel = -1;
2771             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2772                                            looking_at(buf, &i, "* whispers:") ||
2773                                            looking_at(buf, &i, "* kibitzes:") ||
2774                                            looking_at(buf, &i, "* shouts:") ||
2775                                            looking_at(buf, &i, "* c-shouts:") ||
2776                                            looking_at(buf, &i, "--> * ") ||
2777                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2778                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2779                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2781                 int p;
2782                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2783                 chattingPartner = -1;
2784
2785                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2786                 for(p=0; p<MAX_CHAT; p++) {
2787                     if(channel == atoi(chatPartner[p])) {
2788                     talker[0] = '['; strcat(talker, "] ");
2789                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2790                     chattingPartner = p; break;
2791                     }
2792                 } else
2793                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2794                 for(p=0; p<MAX_CHAT; p++) {
2795                     if(!strcmp("kibitzes", chatPartner[p])) {
2796                         talker[0] = '['; strcat(talker, "] ");
2797                         chattingPartner = p; break;
2798                     }
2799                 } else
2800                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2801                 for(p=0; p<MAX_CHAT; p++) {
2802                     if(!strcmp("whispers", chatPartner[p])) {
2803                         talker[0] = '['; strcat(talker, "] ");
2804                         chattingPartner = p; break;
2805                     }
2806                 } else
2807                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2808                   if(buf[i-8] == '-' && buf[i-3] == 't')
2809                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2810                     if(!strcmp("c-shouts", chatPartner[p])) {
2811                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2812                         chattingPartner = p; break;
2813                     }
2814                   }
2815                   if(chattingPartner < 0)
2816                   for(p=0; p<MAX_CHAT; p++) {
2817                     if(!strcmp("shouts", chatPartner[p])) {
2818                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2819                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2820                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2821                         chattingPartner = p; break;
2822                     }
2823                   }
2824                 }
2825                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2826                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2827                     talker[0] = 0; Colorize(ColorTell, FALSE);
2828                     chattingPartner = p; break;
2829                 }
2830                 if(chattingPartner<0) i = oldi; else {
2831                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2832                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2833                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2834                     started = STARTED_COMMENT;
2835                     parse_pos = 0; parse[0] = NULLCHAR;
2836                     savingComment = 3 + chattingPartner; // counts as TRUE
2837                     suppressKibitz = TRUE;
2838                     continue;
2839                 }
2840             } // [HGM] chat: end of patch
2841
2842             if (appData.zippyTalk || appData.zippyPlay) {
2843                 /* [DM] Backup address for color zippy lines */
2844                 backup = i;
2845 #if ZIPPY
2846                if (loggedOn == TRUE)
2847                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2848                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2849 #endif
2850             } // [DM] 'else { ' deleted
2851                 if (
2852                     /* Regular tells and says */
2853                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2854                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2855                     looking_at(buf, &i, "* says: ") ||
2856                     /* Don't color "message" or "messages" output */
2857                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2858                     looking_at(buf, &i, "*. * at *:*: ") ||
2859                     looking_at(buf, &i, "--* (*:*): ") ||
2860                     /* Message notifications (same color as tells) */
2861                     looking_at(buf, &i, "* has left a message ") ||
2862                     looking_at(buf, &i, "* just sent you a message:\n") ||
2863                     /* Whispers and kibitzes */
2864                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2865                     looking_at(buf, &i, "* kibitzes: ") ||
2866                     /* Channel tells */
2867                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2868
2869                   if (tkind == 1 && strchr(star_match[0], ':')) {
2870                       /* Avoid "tells you:" spoofs in channels */
2871                      tkind = 3;
2872                   }
2873                   if (star_match[0][0] == NULLCHAR ||
2874                       strchr(star_match[0], ' ') ||
2875                       (tkind == 3 && strchr(star_match[1], ' '))) {
2876                     /* Reject bogus matches */
2877                     i = oldi;
2878                   } else {
2879                     if (appData.colorize) {
2880                       if (oldi > next_out) {
2881                         SendToPlayer(&buf[next_out], oldi - next_out);
2882                         next_out = oldi;
2883                       }
2884                       switch (tkind) {
2885                       case 1:
2886                         Colorize(ColorTell, FALSE);
2887                         curColor = ColorTell;
2888                         break;
2889                       case 2:
2890                         Colorize(ColorKibitz, FALSE);
2891                         curColor = ColorKibitz;
2892                         break;
2893                       case 3:
2894                         p = strrchr(star_match[1], '(');
2895                         if (p == NULL) {
2896                           p = star_match[1];
2897                         } else {
2898                           p++;
2899                         }
2900                         if (atoi(p) == 1) {
2901                           Colorize(ColorChannel1, FALSE);
2902                           curColor = ColorChannel1;
2903                         } else {
2904                           Colorize(ColorChannel, FALSE);
2905                           curColor = ColorChannel;
2906                         }
2907                         break;
2908                       case 5:
2909                         curColor = ColorNormal;
2910                         break;
2911                       }
2912                     }
2913                     if (started == STARTED_NONE && appData.autoComment &&
2914                         (gameMode == IcsObserving ||
2915                          gameMode == IcsPlayingWhite ||
2916                          gameMode == IcsPlayingBlack)) {
2917                       parse_pos = i - oldi;
2918                       memcpy(parse, &buf[oldi], parse_pos);
2919                       parse[parse_pos] = NULLCHAR;
2920                       started = STARTED_COMMENT;
2921                       savingComment = TRUE;
2922                     } else {
2923                       started = STARTED_CHATTER;
2924                       savingComment = FALSE;
2925                     }
2926                     loggedOn = TRUE;
2927                     continue;
2928                   }
2929                 }
2930
2931                 if (looking_at(buf, &i, "* s-shouts: ") ||
2932                     looking_at(buf, &i, "* c-shouts: ")) {
2933                     if (appData.colorize) {
2934                         if (oldi > next_out) {
2935                             SendToPlayer(&buf[next_out], oldi - next_out);
2936                             next_out = oldi;
2937                         }
2938                         Colorize(ColorSShout, FALSE);
2939                         curColor = ColorSShout;
2940                     }
2941                     loggedOn = TRUE;
2942                     started = STARTED_CHATTER;
2943                     continue;
2944                 }
2945
2946                 if (looking_at(buf, &i, "--->")) {
2947                     loggedOn = TRUE;
2948                     continue;
2949                 }
2950
2951                 if (looking_at(buf, &i, "* shouts: ") ||
2952                     looking_at(buf, &i, "--> ")) {
2953                     if (appData.colorize) {
2954                         if (oldi > next_out) {
2955                             SendToPlayer(&buf[next_out], oldi - next_out);
2956                             next_out = oldi;
2957                         }
2958                         Colorize(ColorShout, FALSE);
2959                         curColor = ColorShout;
2960                     }
2961                     loggedOn = TRUE;
2962                     started = STARTED_CHATTER;
2963                     continue;
2964                 }
2965
2966                 if (looking_at( buf, &i, "Challenge:")) {
2967                     if (appData.colorize) {
2968                         if (oldi > next_out) {
2969                             SendToPlayer(&buf[next_out], oldi - next_out);
2970                             next_out = oldi;
2971                         }
2972                         Colorize(ColorChallenge, FALSE);
2973                         curColor = ColorChallenge;
2974                     }
2975                     loggedOn = TRUE;
2976                     continue;
2977                 }
2978
2979                 if (looking_at(buf, &i, "* offers you") ||
2980                     looking_at(buf, &i, "* offers to be") ||
2981                     looking_at(buf, &i, "* would like to") ||
2982                     looking_at(buf, &i, "* requests to") ||
2983                     looking_at(buf, &i, "Your opponent offers") ||
2984                     looking_at(buf, &i, "Your opponent requests")) {
2985
2986                     if (appData.colorize) {
2987                         if (oldi > next_out) {
2988                             SendToPlayer(&buf[next_out], oldi - next_out);
2989                             next_out = oldi;
2990                         }
2991                         Colorize(ColorRequest, FALSE);
2992                         curColor = ColorRequest;
2993                     }
2994                     continue;
2995                 }
2996
2997                 if (looking_at(buf, &i, "* (*) seeking")) {
2998                     if (appData.colorize) {
2999                         if (oldi > next_out) {
3000                             SendToPlayer(&buf[next_out], oldi - next_out);
3001                             next_out = oldi;
3002                         }
3003                         Colorize(ColorSeek, FALSE);
3004                         curColor = ColorSeek;
3005                     }
3006                     continue;
3007             }
3008
3009             if (looking_at(buf, &i, "\\   ")) {
3010                 if (prevColor != ColorNormal) {
3011                     if (oldi > next_out) {
3012                         SendToPlayer(&buf[next_out], oldi - next_out);
3013                         next_out = oldi;
3014                     }
3015                     Colorize(prevColor, TRUE);
3016                     curColor = prevColor;
3017                 }
3018                 if (savingComment) {
3019                     parse_pos = i - oldi;
3020                     memcpy(parse, &buf[oldi], parse_pos);
3021                     parse[parse_pos] = NULLCHAR;
3022                     started = STARTED_COMMENT;
3023                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3024                         chattingPartner = savingComment - 3; // kludge to remember the box
3025                 } else {
3026                     started = STARTED_CHATTER;
3027                 }
3028                 continue;
3029             }
3030
3031             if (looking_at(buf, &i, "Black Strength :") ||
3032                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3033                 looking_at(buf, &i, "<10>") ||
3034                 looking_at(buf, &i, "#@#")) {
3035                 /* Wrong board style */
3036                 loggedOn = TRUE;
3037                 SendToICS(ics_prefix);
3038                 SendToICS("set style 12\n");
3039                 SendToICS(ics_prefix);
3040                 SendToICS("refresh\n");
3041                 continue;
3042             }
3043             
3044             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3045                 ICSInitScript();
3046                 have_sent_ICS_logon = 1;
3047                 continue;
3048             }
3049               
3050             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3051                 (looking_at(buf, &i, "\n<12> ") ||
3052                  looking_at(buf, &i, "<12> "))) {
3053                 loggedOn = TRUE;
3054                 if (oldi > next_out) {
3055                     SendToPlayer(&buf[next_out], oldi - next_out);
3056                 }
3057                 next_out = i;
3058                 started = STARTED_BOARD;
3059                 parse_pos = 0;
3060                 continue;
3061             }
3062
3063             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3064                 looking_at(buf, &i, "<b1> ")) {
3065                 if (oldi > next_out) {
3066                     SendToPlayer(&buf[next_out], oldi - next_out);
3067                 }
3068                 next_out = i;
3069                 started = STARTED_HOLDINGS;
3070                 parse_pos = 0;
3071                 continue;
3072             }
3073
3074             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3075                 loggedOn = TRUE;
3076                 /* Header for a move list -- first line */
3077
3078                 switch (ics_getting_history) {
3079                   case H_FALSE:
3080                     switch (gameMode) {
3081                       case IcsIdle:
3082                       case BeginningOfGame:
3083                         /* User typed "moves" or "oldmoves" while we
3084                            were idle.  Pretend we asked for these
3085                            moves and soak them up so user can step
3086                            through them and/or save them.
3087                            */
3088                         Reset(FALSE, TRUE);
3089                         gameMode = IcsObserving;
3090                         ModeHighlight();
3091                         ics_gamenum = -1;
3092                         ics_getting_history = H_GOT_UNREQ_HEADER;
3093                         break;
3094                       case EditGame: /*?*/
3095                       case EditPosition: /*?*/
3096                         /* Should above feature work in these modes too? */
3097                         /* For now it doesn't */
3098                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3099                         break;
3100                       default:
3101                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3102                         break;
3103                     }
3104                     break;
3105                   case H_REQUESTED:
3106                     /* Is this the right one? */
3107                     if (gameInfo.white && gameInfo.black &&
3108                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3109                         strcmp(gameInfo.black, star_match[2]) == 0) {
3110                         /* All is well */
3111                         ics_getting_history = H_GOT_REQ_HEADER;
3112                     }
3113                     break;
3114                   case H_GOT_REQ_HEADER:
3115                   case H_GOT_UNREQ_HEADER:
3116                   case H_GOT_UNWANTED_HEADER:
3117                   case H_GETTING_MOVES:
3118                     /* Should not happen */
3119                     DisplayError(_("Error gathering move list: two headers"), 0);
3120                     ics_getting_history = H_FALSE;
3121                     break;
3122                 }
3123
3124                 /* Save player ratings into gameInfo if needed */
3125                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3126                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3127                     (gameInfo.whiteRating == -1 ||
3128                      gameInfo.blackRating == -1)) {
3129
3130                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3131                     gameInfo.blackRating = string_to_rating(star_match[3]);
3132                     if (appData.debugMode)
3133                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3134                               gameInfo.whiteRating, gameInfo.blackRating);
3135                 }
3136                 continue;
3137             }
3138
3139             if (looking_at(buf, &i,
3140               "* * match, initial time: * minute*, increment: * second")) {
3141                 /* Header for a move list -- second line */
3142                 /* Initial board will follow if this is a wild game */
3143                 if (gameInfo.event != NULL) free(gameInfo.event);
3144                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3145                 gameInfo.event = StrSave(str);
3146                 /* [HGM] we switched variant. Translate boards if needed. */
3147                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "Move  ")) {
3152                 /* Beginning of a move list */
3153                 switch (ics_getting_history) {
3154                   case H_FALSE:
3155                     /* Normally should not happen */
3156                     /* Maybe user hit reset while we were parsing */
3157                     break;
3158                   case H_REQUESTED:
3159                     /* Happens if we are ignoring a move list that is not
3160                      * the one we just requested.  Common if the user
3161                      * tries to observe two games without turning off
3162                      * getMoveList */
3163                     break;
3164                   case H_GETTING_MOVES:
3165                     /* Should not happen */
3166                     DisplayError(_("Error gathering move list: nested"), 0);
3167                     ics_getting_history = H_FALSE;
3168                     break;
3169                   case H_GOT_REQ_HEADER:
3170                     ics_getting_history = H_GETTING_MOVES;
3171                     started = STARTED_MOVES;
3172                     parse_pos = 0;
3173                     if (oldi > next_out) {
3174                         SendToPlayer(&buf[next_out], oldi - next_out);
3175                     }
3176                     break;
3177                   case H_GOT_UNREQ_HEADER:
3178                     ics_getting_history = H_GETTING_MOVES;
3179                     started = STARTED_MOVES_NOHIDE;
3180                     parse_pos = 0;
3181                     break;
3182                   case H_GOT_UNWANTED_HEADER:
3183                     ics_getting_history = H_FALSE;
3184                     break;
3185                 }
3186                 continue;
3187             }                           
3188             
3189             if (looking_at(buf, &i, "% ") ||
3190                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3191                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3192                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3193                     soughtPending = FALSE;
3194                     seekGraphUp = TRUE;
3195                     DrawSeekGraph();
3196                 }
3197                 if(suppressKibitz) next_out = i;
3198                 savingComment = FALSE;
3199                 suppressKibitz = 0;
3200                 switch (started) {
3201                   case STARTED_MOVES:
3202                   case STARTED_MOVES_NOHIDE:
3203                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3204                     parse[parse_pos + i - oldi] = NULLCHAR;
3205                     ParseGameHistory(parse);
3206 #if ZIPPY
3207                     if (appData.zippyPlay && first.initDone) {
3208                         FeedMovesToProgram(&first, forwardMostMove);
3209                         if (gameMode == IcsPlayingWhite) {
3210                             if (WhiteOnMove(forwardMostMove)) {
3211                                 if (first.sendTime) {
3212                                   if (first.useColors) {
3213                                     SendToProgram("black\n", &first); 
3214                                   }
3215                                   SendTimeRemaining(&first, TRUE);
3216                                 }
3217                                 if (first.useColors) {
3218                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3219                                 }
3220                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3221                                 first.maybeThinking = TRUE;
3222                             } else {
3223                                 if (first.usePlayother) {
3224                                   if (first.sendTime) {
3225                                     SendTimeRemaining(&first, TRUE);
3226                                   }
3227                                   SendToProgram("playother\n", &first);
3228                                   firstMove = FALSE;
3229                                 } else {
3230                                   firstMove = TRUE;
3231                                 }
3232                             }
3233                         } else if (gameMode == IcsPlayingBlack) {
3234                             if (!WhiteOnMove(forwardMostMove)) {
3235                                 if (first.sendTime) {
3236                                   if (first.useColors) {
3237                                     SendToProgram("white\n", &first);
3238                                   }
3239                                   SendTimeRemaining(&first, FALSE);
3240                                 }
3241                                 if (first.useColors) {
3242                                   SendToProgram("black\n", &first);
3243                                 }
3244                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3245                                 first.maybeThinking = TRUE;
3246                             } else {
3247                                 if (first.usePlayother) {
3248                                   if (first.sendTime) {
3249                                     SendTimeRemaining(&first, FALSE);
3250                                   }
3251                                   SendToProgram("playother\n", &first);
3252                                   firstMove = FALSE;
3253                                 } else {
3254                                   firstMove = TRUE;
3255                                 }
3256                             }
3257                         }                       
3258                     }
3259 #endif
3260                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3261                         /* Moves came from oldmoves or moves command
3262                            while we weren't doing anything else.
3263                            */
3264                         currentMove = forwardMostMove;
3265                         ClearHighlights();/*!!could figure this out*/
3266                         flipView = appData.flipView;
3267                         DrawPosition(TRUE, boards[currentMove]);
3268                         DisplayBothClocks();
3269                         sprintf(str, "%s vs. %s",
3270                                 gameInfo.white, gameInfo.black);
3271                         DisplayTitle(str);
3272                         gameMode = IcsIdle;
3273                     } else {
3274                         /* Moves were history of an active game */
3275                         if (gameInfo.resultDetails != NULL) {
3276                             free(gameInfo.resultDetails);
3277                             gameInfo.resultDetails = NULL;
3278                         }
3279                     }
3280                     HistorySet(parseList, backwardMostMove,
3281                                forwardMostMove, currentMove-1);
3282                     DisplayMove(currentMove - 1);
3283                     if (started == STARTED_MOVES) next_out = i;
3284                     started = STARTED_NONE;
3285                     ics_getting_history = H_FALSE;
3286                     break;
3287
3288                   case STARTED_OBSERVE:
3289                     started = STARTED_NONE;
3290                     SendToICS(ics_prefix);
3291                     SendToICS("refresh\n");
3292                     break;
3293
3294                   default:
3295                     break;
3296                 }
3297                 if(bookHit) { // [HGM] book: simulate book reply
3298                     static char bookMove[MSG_SIZ]; // a bit generous?
3299
3300                     programStats.nodes = programStats.depth = programStats.time = 
3301                     programStats.score = programStats.got_only_move = 0;
3302                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3303
3304                     strcpy(bookMove, "move ");
3305                     strcat(bookMove, bookHit);
3306                     HandleMachineMove(bookMove, &first);
3307                 }
3308                 continue;
3309             }
3310             
3311             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3312                  started == STARTED_HOLDINGS ||
3313                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3314                 /* Accumulate characters in move list or board */
3315                 parse[parse_pos++] = buf[i];
3316             }
3317             
3318             /* Start of game messages.  Mostly we detect start of game
3319                when the first board image arrives.  On some versions
3320                of the ICS, though, we need to do a "refresh" after starting
3321                to observe in order to get the current board right away. */
3322             if (looking_at(buf, &i, "Adding game * to observation list")) {
3323                 started = STARTED_OBSERVE;
3324                 continue;
3325             }
3326
3327             /* Handle auto-observe */
3328             if (appData.autoObserve &&
3329                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3330                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3331                 char *player;
3332                 /* Choose the player that was highlighted, if any. */
3333                 if (star_match[0][0] == '\033' ||
3334                     star_match[1][0] != '\033') {
3335                     player = star_match[0];
3336                 } else {
3337                     player = star_match[2];
3338                 }
3339                 sprintf(str, "%sobserve %s\n",
3340                         ics_prefix, StripHighlightAndTitle(player));
3341                 SendToICS(str);
3342
3343                 /* Save ratings from notify string */
3344                 strcpy(player1Name, star_match[0]);
3345                 player1Rating = string_to_rating(star_match[1]);
3346                 strcpy(player2Name, star_match[2]);
3347                 player2Rating = string_to_rating(star_match[3]);
3348
3349                 if (appData.debugMode)
3350                   fprintf(debugFP, 
3351                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3352                           player1Name, player1Rating,
3353                           player2Name, player2Rating);
3354
3355                 continue;
3356             }
3357
3358             /* Deal with automatic examine mode after a game,
3359                and with IcsObserving -> IcsExamining transition */
3360             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3361                 looking_at(buf, &i, "has made you an examiner of game *")) {
3362
3363                 int gamenum = atoi(star_match[0]);
3364                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3365                     gamenum == ics_gamenum) {
3366                     /* We were already playing or observing this game;
3367                        no need to refetch history */
3368                     gameMode = IcsExamining;
3369                     if (pausing) {
3370                         pauseExamForwardMostMove = forwardMostMove;
3371                     } else if (currentMove < forwardMostMove) {
3372                         ForwardInner(forwardMostMove);
3373                     }
3374                 } else {
3375                     /* I don't think this case really can happen */
3376                     SendToICS(ics_prefix);
3377                     SendToICS("refresh\n");
3378                 }
3379                 continue;
3380             }    
3381             
3382             /* Error messages */
3383 //          if (ics_user_moved) {
3384             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3385                 if (looking_at(buf, &i, "Illegal move") ||
3386                     looking_at(buf, &i, "Not a legal move") ||
3387                     looking_at(buf, &i, "Your king is in check") ||
3388                     looking_at(buf, &i, "It isn't your turn") ||
3389                     looking_at(buf, &i, "It is not your move")) {
3390                     /* Illegal move */
3391                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3392                         currentMove = forwardMostMove-1;
3393                         DisplayMove(currentMove - 1); /* before DMError */
3394                         DrawPosition(FALSE, boards[currentMove]);
3395                         SwitchClocks(forwardMostMove-1); // [HGM] race
3396                         DisplayBothClocks();
3397                     }
3398                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3399                     ics_user_moved = 0;
3400                     continue;
3401                 }
3402             }
3403
3404             if (looking_at(buf, &i, "still have time") ||
3405                 looking_at(buf, &i, "not out of time") ||
3406                 looking_at(buf, &i, "either player is out of time") ||
3407                 looking_at(buf, &i, "has timeseal; checking")) {
3408                 /* We must have called his flag a little too soon */
3409                 whiteFlag = blackFlag = FALSE;
3410                 continue;
3411             }
3412
3413             if (looking_at(buf, &i, "added * seconds to") ||
3414                 looking_at(buf, &i, "seconds were added to")) {
3415                 /* Update the clocks */
3416                 SendToICS(ics_prefix);
3417                 SendToICS("refresh\n");
3418                 continue;
3419             }
3420
3421             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3422                 ics_clock_paused = TRUE;
3423                 StopClocks();
3424                 continue;
3425             }
3426
3427             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3428                 ics_clock_paused = FALSE;
3429                 StartClocks();
3430                 continue;
3431             }
3432
3433             /* Grab player ratings from the Creating: message.
3434                Note we have to check for the special case when
3435                the ICS inserts things like [white] or [black]. */
3436             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3437                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3438                 /* star_matches:
3439                    0    player 1 name (not necessarily white)
3440                    1    player 1 rating
3441                    2    empty, white, or black (IGNORED)
3442                    3    player 2 name (not necessarily black)
3443                    4    player 2 rating
3444                    
3445                    The names/ratings are sorted out when the game
3446                    actually starts (below).
3447                 */
3448                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3449                 player1Rating = string_to_rating(star_match[1]);
3450                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3451                 player2Rating = string_to_rating(star_match[4]);
3452
3453                 if (appData.debugMode)
3454                   fprintf(debugFP, 
3455                           "Ratings from 'Creating:' %s %d, %s %d\n",
3456                           player1Name, player1Rating,
3457                           player2Name, player2Rating);
3458
3459                 continue;
3460             }
3461             
3462             /* Improved generic start/end-of-game messages */
3463             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3464                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3465                 /* If tkind == 0: */
3466                 /* star_match[0] is the game number */
3467                 /*           [1] is the white player's name */
3468                 /*           [2] is the black player's name */
3469                 /* For end-of-game: */
3470                 /*           [3] is the reason for the game end */
3471                 /*           [4] is a PGN end game-token, preceded by " " */
3472                 /* For start-of-game: */
3473                 /*           [3] begins with "Creating" or "Continuing" */
3474                 /*           [4] is " *" or empty (don't care). */
3475                 int gamenum = atoi(star_match[0]);
3476                 char *whitename, *blackname, *why, *endtoken;
3477                 ChessMove endtype = (ChessMove) 0;
3478
3479                 if (tkind == 0) {
3480                   whitename = star_match[1];
3481                   blackname = star_match[2];
3482                   why = star_match[3];
3483                   endtoken = star_match[4];
3484                 } else {
3485                   whitename = star_match[1];
3486                   blackname = star_match[3];
3487                   why = star_match[5];
3488                   endtoken = star_match[6];
3489                 }
3490
3491                 /* Game start messages */
3492                 if (strncmp(why, "Creating ", 9) == 0 ||
3493                     strncmp(why, "Continuing ", 11) == 0) {
3494                     gs_gamenum = gamenum;
3495                     strcpy(gs_kind, strchr(why, ' ') + 1);
3496                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3497 #if ZIPPY
3498                     if (appData.zippyPlay) {
3499                         ZippyGameStart(whitename, blackname);
3500                     }
3501 #endif /*ZIPPY*/
3502                     partnerBoardValid = FALSE; // [HGM] bughouse
3503                     continue;
3504                 }
3505
3506                 /* Game end messages */
3507                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3508                     ics_gamenum != gamenum) {
3509                     continue;
3510                 }
3511                 while (endtoken[0] == ' ') endtoken++;
3512                 switch (endtoken[0]) {
3513                   case '*':
3514                   default:
3515                     endtype = GameUnfinished;
3516                     break;
3517                   case '0':
3518                     endtype = BlackWins;
3519                     break;
3520                   case '1':
3521                     if (endtoken[1] == '/')
3522                       endtype = GameIsDrawn;
3523                     else
3524                       endtype = WhiteWins;
3525                     break;
3526                 }
3527                 GameEnds(endtype, why, GE_ICS);
3528 #if ZIPPY
3529                 if (appData.zippyPlay && first.initDone) {
3530                     ZippyGameEnd(endtype, why);
3531                     if (first.pr == NULL) {
3532                       /* Start the next process early so that we'll
3533                          be ready for the next challenge */
3534                       StartChessProgram(&first);
3535                     }
3536                     /* Send "new" early, in case this command takes
3537                        a long time to finish, so that we'll be ready
3538                        for the next challenge. */
3539                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3540                     Reset(TRUE, TRUE);
3541                 }
3542 #endif /*ZIPPY*/
3543                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3544                 continue;
3545             }
3546
3547             if (looking_at(buf, &i, "Removing game * from observation") ||
3548                 looking_at(buf, &i, "no longer observing game *") ||
3549                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3550                 if (gameMode == IcsObserving &&
3551                     atoi(star_match[0]) == ics_gamenum)
3552                   {
3553                       /* icsEngineAnalyze */
3554                       if (appData.icsEngineAnalyze) {
3555                             ExitAnalyzeMode();
3556                             ModeHighlight();
3557                       }
3558                       StopClocks();
3559                       gameMode = IcsIdle;
3560                       ics_gamenum = -1;
3561                       ics_user_moved = FALSE;
3562                   }
3563                 continue;
3564             }
3565
3566             if (looking_at(buf, &i, "no longer examining game *")) {
3567                 if (gameMode == IcsExamining &&
3568                     atoi(star_match[0]) == ics_gamenum)
3569                   {
3570                       gameMode = IcsIdle;
3571                       ics_gamenum = -1;
3572                       ics_user_moved = FALSE;
3573                   }
3574                 continue;
3575             }
3576
3577             /* Advance leftover_start past any newlines we find,
3578                so only partial lines can get reparsed */
3579             if (looking_at(buf, &i, "\n")) {
3580                 prevColor = curColor;
3581                 if (curColor != ColorNormal) {
3582                     if (oldi > next_out) {
3583                         SendToPlayer(&buf[next_out], oldi - next_out);
3584                         next_out = oldi;
3585                     }
3586                     Colorize(ColorNormal, FALSE);
3587                     curColor = ColorNormal;
3588                 }
3589                 if (started == STARTED_BOARD) {
3590                     started = STARTED_NONE;
3591                     parse[parse_pos] = NULLCHAR;
3592                     ParseBoard12(parse);
3593                     ics_user_moved = 0;
3594
3595                     /* Send premove here */
3596                     if (appData.premove) {
3597                       char str[MSG_SIZ];
3598                       if (currentMove == 0 &&
3599                           gameMode == IcsPlayingWhite &&
3600                           appData.premoveWhite) {
3601                         sprintf(str, "%s\n", appData.premoveWhiteText);
3602                         if (appData.debugMode)
3603                           fprintf(debugFP, "Sending premove:\n");
3604                         SendToICS(str);
3605                       } else if (currentMove == 1 &&
3606                                  gameMode == IcsPlayingBlack &&
3607                                  appData.premoveBlack) {
3608                         sprintf(str, "%s\n", appData.premoveBlackText);
3609                         if (appData.debugMode)
3610                           fprintf(debugFP, "Sending premove:\n");
3611                         SendToICS(str);
3612                       } else if (gotPremove) {
3613                         gotPremove = 0;
3614                         ClearPremoveHighlights();
3615                         if (appData.debugMode)
3616                           fprintf(debugFP, "Sending premove:\n");
3617                           UserMoveEvent(premoveFromX, premoveFromY, 
3618                                         premoveToX, premoveToY, 
3619                                         premovePromoChar);
3620                       }
3621                     }
3622
3623                     /* Usually suppress following prompt */
3624                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3625                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3626                         if (looking_at(buf, &i, "*% ")) {
3627                             savingComment = FALSE;
3628                             suppressKibitz = 0;
3629                         }
3630                     }
3631                     next_out = i;
3632                 } else if (started == STARTED_HOLDINGS) {
3633                     int gamenum;
3634                     char new_piece[MSG_SIZ];
3635                     started = STARTED_NONE;
3636                     parse[parse_pos] = NULLCHAR;
3637                     if (appData.debugMode)
3638                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3639                                                         parse, currentMove);
3640                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3641                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3642                         if (gameInfo.variant == VariantNormal) {
3643                           /* [HGM] We seem to switch variant during a game!
3644                            * Presumably no holdings were displayed, so we have
3645                            * to move the position two files to the right to
3646                            * create room for them!
3647                            */
3648                           VariantClass newVariant;
3649                           switch(gameInfo.boardWidth) { // base guess on board width
3650                                 case 9:  newVariant = VariantShogi; break;
3651                                 case 10: newVariant = VariantGreat; break;
3652                                 default: newVariant = VariantCrazyhouse; break;
3653                           }
3654                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3655                           /* Get a move list just to see the header, which
3656                              will tell us whether this is really bug or zh */
3657                           if (ics_getting_history == H_FALSE) {
3658                             ics_getting_history = H_REQUESTED;
3659                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3660                             SendToICS(str);
3661                           }
3662                         }
3663                         new_piece[0] = NULLCHAR;
3664                         sscanf(parse, "game %d white [%s black [%s <- %s",
3665                                &gamenum, white_holding, black_holding,
3666                                new_piece);
3667                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3668                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3669                         /* [HGM] copy holdings to board holdings area */
3670                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3671                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3672                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3673 #if ZIPPY
3674                         if (appData.zippyPlay && first.initDone) {
3675                             ZippyHoldings(white_holding, black_holding,
3676                                           new_piece);
3677                         }
3678 #endif /*ZIPPY*/
3679                         if (tinyLayout || smallLayout) {
3680                             char wh[16], bh[16];
3681                             PackHolding(wh, white_holding);
3682                             PackHolding(bh, black_holding);
3683                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3684                                     gameInfo.white, gameInfo.black);
3685                         } else {
3686                             sprintf(str, "%s [%s] vs. %s [%s]",
3687                                     gameInfo.white, white_holding,
3688                                     gameInfo.black, black_holding);
3689                         }
3690                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3691                         DrawPosition(FALSE, boards[currentMove]);
3692                         DisplayTitle(str);
3693                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3694                         sscanf(parse, "game %d white [%s black [%s <- %s",
3695                                &gamenum, white_holding, black_holding,
3696                                new_piece);
3697                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3698                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3699                         /* [HGM] copy holdings to partner-board holdings area */
3700                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3701                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3702                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3703                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3704                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3705                       }
3706                     }
3707                     /* Suppress following prompt */
3708                     if (looking_at(buf, &i, "*% ")) {
3709                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3710                         savingComment = FALSE;
3711                         suppressKibitz = 0;
3712                     }
3713                     next_out = i;
3714                 }
3715                 continue;
3716             }
3717
3718             i++;                /* skip unparsed character and loop back */
3719         }
3720         
3721         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3722 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3723 //          SendToPlayer(&buf[next_out], i - next_out);
3724             started != STARTED_HOLDINGS && leftover_start > next_out) {
3725             SendToPlayer(&buf[next_out], leftover_start - next_out);
3726             next_out = i;
3727         }
3728         
3729         leftover_len = buf_len - leftover_start;
3730         /* if buffer ends with something we couldn't parse,
3731            reparse it after appending the next read */
3732         
3733     } else if (count == 0) {
3734         RemoveInputSource(isr);
3735         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3736     } else {
3737         DisplayFatalError(_("Error reading from ICS"), error, 1);
3738     }
3739 }
3740
3741
3742 /* Board style 12 looks like this:
3743    
3744    <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
3745    
3746  * The "<12> " is stripped before it gets to this routine.  The two
3747  * trailing 0's (flip state and clock ticking) are later addition, and
3748  * some chess servers may not have them, or may have only the first.
3749  * Additional trailing fields may be added in the future.  
3750  */
3751
3752 #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"
3753
3754 #define RELATION_OBSERVING_PLAYED    0
3755 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3756 #define RELATION_PLAYING_MYMOVE      1
3757 #define RELATION_PLAYING_NOTMYMOVE  -1
3758 #define RELATION_EXAMINING           2
3759 #define RELATION_ISOLATED_BOARD     -3
3760 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3761
3762 void
3763 ParseBoard12(string)
3764      char *string;
3765
3766     GameMode newGameMode;
3767     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3768     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3769     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3770     char to_play, board_chars[200];
3771     char move_str[500], str[500], elapsed_time[500];
3772     char black[32], white[32];
3773     Board board;
3774     int prevMove = currentMove;
3775     int ticking = 2;
3776     ChessMove moveType;
3777     int fromX, fromY, toX, toY;
3778     char promoChar;
3779     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3780     char *bookHit = NULL; // [HGM] book
3781     Boolean weird = FALSE, reqFlag = FALSE;
3782
3783     fromX = fromY = toX = toY = -1;
3784     
3785     newGame = FALSE;
3786
3787     if (appData.debugMode)
3788       fprintf(debugFP, _("Parsing board: %s\n"), string);
3789
3790     move_str[0] = NULLCHAR;
3791     elapsed_time[0] = NULLCHAR;
3792     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3793         int  i = 0, j;
3794         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3795             if(string[i] == ' ') { ranks++; files = 0; }
3796             else files++;
3797             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3798             i++;
3799         }
3800         for(j = 0; j <i; j++) board_chars[j] = string[j];
3801         board_chars[i] = '\0';
3802         string += i + 1;
3803     }
3804     n = sscanf(string, PATTERN, &to_play, &double_push,
3805                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3806                &gamenum, white, black, &relation, &basetime, &increment,
3807                &white_stren, &black_stren, &white_time, &black_time,
3808                &moveNum, str, elapsed_time, move_str, &ics_flip,
3809                &ticking);
3810
3811     if (n < 21) {
3812         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3813         DisplayError(str, 0);
3814         return;
3815     }
3816
3817     /* Convert the move number to internal form */
3818     moveNum = (moveNum - 1) * 2;
3819     if (to_play == 'B') moveNum++;
3820     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3821       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3822                         0, 1);
3823       return;
3824     }
3825     
3826     switch (relation) {
3827       case RELATION_OBSERVING_PLAYED:
3828       case RELATION_OBSERVING_STATIC:
3829         if (gamenum == -1) {
3830             /* Old ICC buglet */
3831             relation = RELATION_OBSERVING_STATIC;
3832         }
3833         newGameMode = IcsObserving;
3834         break;
3835       case RELATION_PLAYING_MYMOVE:
3836       case RELATION_PLAYING_NOTMYMOVE:
3837         newGameMode =
3838           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3839             IcsPlayingWhite : IcsPlayingBlack;
3840         break;
3841       case RELATION_EXAMINING:
3842         newGameMode = IcsExamining;
3843         break;
3844       case RELATION_ISOLATED_BOARD:
3845       default:
3846         /* Just display this board.  If user was doing something else,
3847            we will forget about it until the next board comes. */ 
3848         newGameMode = IcsIdle;
3849         break;
3850       case RELATION_STARTING_POSITION:
3851         newGameMode = gameMode;
3852         break;
3853     }
3854     
3855     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3856          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3857       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3858       char *toSqr;
3859       for (k = 0; k < ranks; k++) {
3860         for (j = 0; j < files; j++)
3861           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3862         if(gameInfo.holdingsWidth > 1) {
3863              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3864              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3865         }
3866       }
3867       CopyBoard(partnerBoard, board);
3868       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3869         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3870         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3871       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3872       if(toSqr = strchr(str, '-')) {
3873         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3874         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3875       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3876       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3877       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3878       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3879       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3880       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3881                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3882       DisplayMessage(partnerStatus, "");
3883         partnerBoardValid = TRUE;
3884       return;
3885     }
3886
3887     /* Modify behavior for initial board display on move listing
3888        of wild games.
3889        */
3890     switch (ics_getting_history) {
3891       case H_FALSE:
3892       case H_REQUESTED:
3893         break;
3894       case H_GOT_REQ_HEADER:
3895       case H_GOT_UNREQ_HEADER:
3896         /* This is the initial position of the current game */
3897         gamenum = ics_gamenum;
3898         moveNum = 0;            /* old ICS bug workaround */
3899         if (to_play == 'B') {
3900           startedFromSetupPosition = TRUE;
3901           blackPlaysFirst = TRUE;
3902           moveNum = 1;
3903           if (forwardMostMove == 0) forwardMostMove = 1;
3904           if (backwardMostMove == 0) backwardMostMove = 1;
3905           if (currentMove == 0) currentMove = 1;
3906         }
3907         newGameMode = gameMode;
3908         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3909         break;
3910       case H_GOT_UNWANTED_HEADER:
3911         /* This is an initial board that we don't want */
3912         return;
3913       case H_GETTING_MOVES:
3914         /* Should not happen */
3915         DisplayError(_("Error gathering move list: extra board"), 0);
3916         ics_getting_history = H_FALSE;
3917         return;
3918     }
3919
3920    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3921                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3922      /* [HGM] We seem to have switched variant unexpectedly
3923       * Try to guess new variant from board size
3924       */
3925           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3926           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3927           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3928           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3929           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3930           if(!weird) newVariant = VariantNormal;
3931           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3932           /* Get a move list just to see the header, which
3933              will tell us whether this is really bug or zh */
3934           if (ics_getting_history == H_FALSE) {
3935             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3936             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3937             SendToICS(str);
3938           }
3939     }
3940     
3941     /* Take action if this is the first board of a new game, or of a
3942        different game than is currently being displayed.  */
3943     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3944         relation == RELATION_ISOLATED_BOARD) {
3945         
3946         /* Forget the old game and get the history (if any) of the new one */
3947         if (gameMode != BeginningOfGame) {
3948           Reset(TRUE, TRUE);
3949         }
3950         newGame = TRUE;
3951         if (appData.autoRaiseBoard) BoardToTop();
3952         prevMove = -3;
3953         if (gamenum == -1) {
3954             newGameMode = IcsIdle;
3955         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3956                    appData.getMoveList && !reqFlag) {
3957             /* Need to get game history */
3958             ics_getting_history = H_REQUESTED;
3959             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3960             SendToICS(str);
3961         }
3962         
3963         /* Initially flip the board to have black on the bottom if playing
3964            black or if the ICS flip flag is set, but let the user change
3965            it with the Flip View button. */
3966         flipView = appData.autoFlipView ? 
3967           (newGameMode == IcsPlayingBlack) || ics_flip :
3968           appData.flipView;
3969         
3970         /* Done with values from previous mode; copy in new ones */
3971         gameMode = newGameMode;
3972         ModeHighlight();
3973         ics_gamenum = gamenum;
3974         if (gamenum == gs_gamenum) {
3975             int klen = strlen(gs_kind);
3976             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3977             sprintf(str, "ICS %s", gs_kind);
3978             gameInfo.event = StrSave(str);
3979         } else {
3980             gameInfo.event = StrSave("ICS game");
3981         }
3982         gameInfo.site = StrSave(appData.icsHost);
3983         gameInfo.date = PGNDate();
3984         gameInfo.round = StrSave("-");
3985         gameInfo.white = StrSave(white);
3986         gameInfo.black = StrSave(black);
3987         timeControl = basetime * 60 * 1000;
3988         timeControl_2 = 0;
3989         timeIncrement = increment * 1000;
3990         movesPerSession = 0;
3991         gameInfo.timeControl = TimeControlTagValue();
3992         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3993   if (appData.debugMode) {
3994     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3995     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3996     setbuf(debugFP, NULL);
3997   }
3998
3999         gameInfo.outOfBook = NULL;
4000         
4001         /* Do we have the ratings? */
4002         if (strcmp(player1Name, white) == 0 &&
4003             strcmp(player2Name, black) == 0) {
4004             if (appData.debugMode)
4005               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4006                       player1Rating, player2Rating);
4007             gameInfo.whiteRating = player1Rating;
4008             gameInfo.blackRating = player2Rating;
4009         } else if (strcmp(player2Name, white) == 0 &&
4010                    strcmp(player1Name, black) == 0) {
4011             if (appData.debugMode)
4012               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4013                       player2Rating, player1Rating);
4014             gameInfo.whiteRating = player2Rating;
4015             gameInfo.blackRating = player1Rating;
4016         }
4017         player1Name[0] = player2Name[0] = NULLCHAR;
4018
4019         /* Silence shouts if requested */
4020         if (appData.quietPlay &&
4021             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4022             SendToICS(ics_prefix);
4023             SendToICS("set shout 0\n");
4024         }
4025     }
4026     
4027     /* Deal with midgame name changes */
4028     if (!newGame) {
4029         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4030             if (gameInfo.white) free(gameInfo.white);
4031             gameInfo.white = StrSave(white);
4032         }
4033         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4034             if (gameInfo.black) free(gameInfo.black);
4035             gameInfo.black = StrSave(black);
4036         }
4037     }
4038     
4039     /* Throw away game result if anything actually changes in examine mode */
4040     if (gameMode == IcsExamining && !newGame) {
4041         gameInfo.result = GameUnfinished;
4042         if (gameInfo.resultDetails != NULL) {
4043             free(gameInfo.resultDetails);
4044             gameInfo.resultDetails = NULL;
4045         }
4046     }
4047     
4048     /* In pausing && IcsExamining mode, we ignore boards coming
4049        in if they are in a different variation than we are. */
4050     if (pauseExamInvalid) return;
4051     if (pausing && gameMode == IcsExamining) {
4052         if (moveNum <= pauseExamForwardMostMove) {
4053             pauseExamInvalid = TRUE;
4054             forwardMostMove = pauseExamForwardMostMove;
4055             return;
4056         }
4057     }
4058     
4059   if (appData.debugMode) {
4060     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4061   }
4062     /* Parse the board */
4063     for (k = 0; k < ranks; k++) {
4064       for (j = 0; j < files; j++)
4065         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4066       if(gameInfo.holdingsWidth > 1) {
4067            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4068            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4069       }
4070     }
4071     CopyBoard(boards[moveNum], board);
4072     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4073     if (moveNum == 0) {
4074         startedFromSetupPosition =
4075           !CompareBoards(board, initialPosition);
4076         if(startedFromSetupPosition)
4077             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4078     }
4079
4080     /* [HGM] Set castling rights. Take the outermost Rooks,
4081        to make it also work for FRC opening positions. Note that board12
4082        is really defective for later FRC positions, as it has no way to
4083        indicate which Rook can castle if they are on the same side of King.
4084        For the initial position we grant rights to the outermost Rooks,
4085        and remember thos rights, and we then copy them on positions
4086        later in an FRC game. This means WB might not recognize castlings with
4087        Rooks that have moved back to their original position as illegal,
4088        but in ICS mode that is not its job anyway.
4089     */
4090     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4091     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4092
4093         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4094             if(board[0][i] == WhiteRook) j = i;
4095         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4096         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4097             if(board[0][i] == WhiteRook) j = i;
4098         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4099         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4100             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4101         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4102         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4103             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4104         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4105
4106         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4107         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4108             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[BOARD_HEIGHT-1][k] == bKing)
4111                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4112         if(gameInfo.variant == VariantTwoKings) {
4113             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4114             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4115             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4116         }
4117     } else { int r;
4118         r = boards[moveNum][CASTLING][0] = initialRights[0];
4119         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4120         r = boards[moveNum][CASTLING][1] = initialRights[1];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4122         r = boards[moveNum][CASTLING][3] = initialRights[3];
4123         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4124         r = boards[moveNum][CASTLING][4] = initialRights[4];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4126         /* wildcastle kludge: always assume King has rights */
4127         r = boards[moveNum][CASTLING][2] = initialRights[2];
4128         r = boards[moveNum][CASTLING][5] = initialRights[5];
4129     }
4130     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4131     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4132
4133     
4134     if (ics_getting_history == H_GOT_REQ_HEADER ||
4135         ics_getting_history == H_GOT_UNREQ_HEADER) {
4136         /* This was an initial position from a move list, not
4137            the current position */
4138         return;
4139     }
4140     
4141     /* Update currentMove and known move number limits */
4142     newMove = newGame || moveNum > forwardMostMove;
4143
4144     if (newGame) {
4145         forwardMostMove = backwardMostMove = currentMove = moveNum;
4146         if (gameMode == IcsExamining && moveNum == 0) {
4147           /* Workaround for ICS limitation: we are not told the wild
4148              type when starting to examine a game.  But if we ask for
4149              the move list, the move list header will tell us */
4150             ics_getting_history = H_REQUESTED;
4151             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4152             SendToICS(str);
4153         }
4154     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4155                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4156 #if ZIPPY
4157         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4158         /* [HGM] applied this also to an engine that is silently watching        */
4159         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4160             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4161             gameInfo.variant == currentlyInitializedVariant) {
4162           takeback = forwardMostMove - moveNum;
4163           for (i = 0; i < takeback; i++) {
4164             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4165             SendToProgram("undo\n", &first);
4166           }
4167         }
4168 #endif
4169
4170         forwardMostMove = moveNum;
4171         if (!pausing || currentMove > forwardMostMove)
4172           currentMove = forwardMostMove;
4173     } else {
4174         /* New part of history that is not contiguous with old part */ 
4175         if (pausing && gameMode == IcsExamining) {
4176             pauseExamInvalid = TRUE;
4177             forwardMostMove = pauseExamForwardMostMove;
4178             return;
4179         }
4180         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4181 #if ZIPPY
4182             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4183                 // [HGM] when we will receive the move list we now request, it will be
4184                 // fed to the engine from the first move on. So if the engine is not
4185                 // in the initial position now, bring it there.
4186                 InitChessProgram(&first, 0);
4187             }
4188 #endif
4189             ics_getting_history = H_REQUESTED;
4190             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4191             SendToICS(str);
4192         }
4193         forwardMostMove = backwardMostMove = currentMove = moveNum;
4194     }
4195     
4196     /* Update the clocks */
4197     if (strchr(elapsed_time, '.')) {
4198       /* Time is in ms */
4199       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4200       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4201     } else {
4202       /* Time is in seconds */
4203       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4204       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4205     }
4206       
4207
4208 #if ZIPPY
4209     if (appData.zippyPlay && newGame &&
4210         gameMode != IcsObserving && gameMode != IcsIdle &&
4211         gameMode != IcsExamining)
4212       ZippyFirstBoard(moveNum, basetime, increment);
4213 #endif
4214     
4215     /* Put the move on the move list, first converting
4216        to canonical algebraic form. */
4217     if (moveNum > 0) {
4218   if (appData.debugMode) {
4219     if (appData.debugMode) { int f = forwardMostMove;
4220         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4221                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4222                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4223     }
4224     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4225     fprintf(debugFP, "moveNum = %d\n", moveNum);
4226     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4227     setbuf(debugFP, NULL);
4228   }
4229         if (moveNum <= backwardMostMove) {
4230             /* We don't know what the board looked like before
4231                this move.  Punt. */
4232             strcpy(parseList[moveNum - 1], move_str);
4233             strcat(parseList[moveNum - 1], " ");
4234             strcat(parseList[moveNum - 1], elapsed_time);
4235             moveList[moveNum - 1][0] = NULLCHAR;
4236         } else if (strcmp(move_str, "none") == 0) {
4237             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4238             /* Again, we don't know what the board looked like;
4239                this is really the start of the game. */
4240             parseList[moveNum - 1][0] = NULLCHAR;
4241             moveList[moveNum - 1][0] = NULLCHAR;
4242             backwardMostMove = moveNum;
4243             startedFromSetupPosition = TRUE;
4244             fromX = fromY = toX = toY = -1;
4245         } else {
4246           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4247           //                 So we parse the long-algebraic move string in stead of the SAN move
4248           int valid; char buf[MSG_SIZ], *prom;
4249
4250           // str looks something like "Q/a1-a2"; kill the slash
4251           if(str[1] == '/') 
4252                 sprintf(buf, "%c%s", str[0], str+2);
4253           else  strcpy(buf, str); // might be castling
4254           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4255                 strcat(buf, prom); // long move lacks promo specification!
4256           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4257                 if(appData.debugMode) 
4258                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4259                 strcpy(move_str, buf);
4260           }
4261           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4262                                 &fromX, &fromY, &toX, &toY, &promoChar)
4263                || ParseOneMove(buf, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar);
4265           // end of long SAN patch
4266           if (valid) {
4267             (void) CoordsToAlgebraic(boards[moveNum - 1],
4268                                      PosFlags(moveNum - 1),
4269                                      fromY, fromX, toY, toX, promoChar,
4270                                      parseList[moveNum-1]);
4271             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4272               case MT_NONE:
4273               case MT_STALEMATE:
4274               default:
4275                 break;
4276               case MT_CHECK:
4277                 if(gameInfo.variant != VariantShogi)
4278                     strcat(parseList[moveNum - 1], "+");
4279                 break;
4280               case MT_CHECKMATE:
4281               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4282                 strcat(parseList[moveNum - 1], "#");
4283                 break;
4284             }
4285             strcat(parseList[moveNum - 1], " ");
4286             strcat(parseList[moveNum - 1], elapsed_time);
4287             /* currentMoveString is set as a side-effect of ParseOneMove */
4288             strcpy(moveList[moveNum - 1], currentMoveString);
4289             strcat(moveList[moveNum - 1], "\n");
4290           } else {
4291             /* Move from ICS was illegal!?  Punt. */
4292   if (appData.debugMode) {
4293     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4294     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4295   }
4296             strcpy(parseList[moveNum - 1], move_str);
4297             strcat(parseList[moveNum - 1], " ");
4298             strcat(parseList[moveNum - 1], elapsed_time);
4299             moveList[moveNum - 1][0] = NULLCHAR;
4300             fromX = fromY = toX = toY = -1;
4301           }
4302         }
4303   if (appData.debugMode) {
4304     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4305     setbuf(debugFP, NULL);
4306   }
4307
4308 #if ZIPPY
4309         /* Send move to chess program (BEFORE animating it). */
4310         if (appData.zippyPlay && !newGame && newMove && 
4311            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4312
4313             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4314                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4315                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4316                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4317                             move_str);
4318                     DisplayError(str, 0);
4319                 } else {
4320                     if (first.sendTime) {
4321                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4322                     }
4323                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4324                     if (firstMove && !bookHit) {
4325                         firstMove = FALSE;
4326                         if (first.useColors) {
4327                           SendToProgram(gameMode == IcsPlayingWhite ?
4328                                         "white\ngo\n" :
4329                                         "black\ngo\n", &first);
4330                         } else {
4331                           SendToProgram("go\n", &first);
4332                         }
4333                         first.maybeThinking = TRUE;
4334                     }
4335                 }
4336             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4337               if (moveList[moveNum - 1][0] == NULLCHAR) {
4338                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4339                 DisplayError(str, 0);
4340               } else {
4341                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4342                 SendMoveToProgram(moveNum - 1, &first);
4343               }
4344             }
4345         }
4346 #endif
4347     }
4348
4349     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4350         /* If move comes from a remote source, animate it.  If it
4351            isn't remote, it will have already been animated. */
4352         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4353             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4354         }
4355         if (!pausing && appData.highlightLastMove) {
4356             SetHighlights(fromX, fromY, toX, toY);
4357         }
4358     }
4359     
4360     /* Start the clocks */
4361     whiteFlag = blackFlag = FALSE;
4362     appData.clockMode = !(basetime == 0 && increment == 0);
4363     if (ticking == 0) {
4364       ics_clock_paused = TRUE;
4365       StopClocks();
4366     } else if (ticking == 1) {
4367       ics_clock_paused = FALSE;
4368     }
4369     if (gameMode == IcsIdle ||
4370         relation == RELATION_OBSERVING_STATIC ||
4371         relation == RELATION_EXAMINING ||
4372         ics_clock_paused)
4373       DisplayBothClocks();
4374     else
4375       StartClocks();
4376     
4377     /* Display opponents and material strengths */
4378     if (gameInfo.variant != VariantBughouse &&
4379         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4380         if (tinyLayout || smallLayout) {
4381             if(gameInfo.variant == VariantNormal)
4382                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4383                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4384                     basetime, increment);
4385             else
4386                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4387                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4388                     basetime, increment, (int) gameInfo.variant);
4389         } else {
4390             if(gameInfo.variant == VariantNormal)
4391                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4392                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4393                     basetime, increment);
4394             else
4395                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4396                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4397                     basetime, increment, VariantName(gameInfo.variant));
4398         }
4399         DisplayTitle(str);
4400   if (appData.debugMode) {
4401     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4402   }
4403     }
4404
4405
4406     /* Display the board */
4407     if (!pausing && !appData.noGUI) {
4408       
4409       if (appData.premove)
4410           if (!gotPremove || 
4411              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4412              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4413               ClearPremoveHighlights();
4414
4415       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4416         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4417       DrawPosition(j, boards[currentMove]);
4418
4419       DisplayMove(moveNum - 1);
4420       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4421             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4422               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4423         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4424       }
4425     }
4426
4427     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4428 #if ZIPPY
4429     if(bookHit) { // [HGM] book: simulate book reply
4430         static char bookMove[MSG_SIZ]; // a bit generous?
4431
4432         programStats.nodes = programStats.depth = programStats.time = 
4433         programStats.score = programStats.got_only_move = 0;
4434         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4435
4436         strcpy(bookMove, "move ");
4437         strcat(bookMove, bookHit);
4438         HandleMachineMove(bookMove, &first);
4439     }
4440 #endif
4441 }
4442
4443 void
4444 GetMoveListEvent()
4445 {
4446     char buf[MSG_SIZ];
4447     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4448         ics_getting_history = H_REQUESTED;
4449         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4450         SendToICS(buf);
4451     }
4452 }
4453
4454 void
4455 AnalysisPeriodicEvent(force)
4456      int force;
4457 {
4458     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4459          && !force) || !appData.periodicUpdates)
4460       return;
4461
4462     /* Send . command to Crafty to collect stats */
4463     SendToProgram(".\n", &first);
4464
4465     /* Don't send another until we get a response (this makes
4466        us stop sending to old Crafty's which don't understand
4467        the "." command (sending illegal cmds resets node count & time,
4468        which looks bad)) */
4469     programStats.ok_to_send = 0;
4470 }
4471
4472 void ics_update_width(new_width)
4473         int new_width;
4474 {
4475         ics_printf("set width %d\n", new_width);
4476 }
4477
4478 void
4479 SendMoveToProgram(moveNum, cps)
4480      int moveNum;
4481      ChessProgramState *cps;
4482 {
4483     char buf[MSG_SIZ];
4484
4485     if (cps->useUsermove) {
4486       SendToProgram("usermove ", cps);
4487     }
4488     if (cps->useSAN) {
4489       char *space;
4490       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4491         int len = space - parseList[moveNum];
4492         memcpy(buf, parseList[moveNum], len);
4493         buf[len++] = '\n';
4494         buf[len] = NULLCHAR;
4495       } else {
4496         sprintf(buf, "%s\n", parseList[moveNum]);
4497       }
4498       SendToProgram(buf, cps);
4499     } else {
4500       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4501         AlphaRank(moveList[moveNum], 4);
4502         SendToProgram(moveList[moveNum], cps);
4503         AlphaRank(moveList[moveNum], 4); // and back
4504       } else
4505       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4506        * the engine. It would be nice to have a better way to identify castle 
4507        * moves here. */
4508       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4509                                                                          && cps->useOOCastle) {
4510         int fromX = moveList[moveNum][0] - AAA; 
4511         int fromY = moveList[moveNum][1] - ONE;
4512         int toX = moveList[moveNum][2] - AAA; 
4513         int toY = moveList[moveNum][3] - ONE;
4514         if((boards[moveNum][fromY][fromX] == WhiteKing 
4515             && boards[moveNum][toY][toX] == WhiteRook)
4516            || (boards[moveNum][fromY][fromX] == BlackKing 
4517                && boards[moveNum][toY][toX] == BlackRook)) {
4518           if(toX > fromX) SendToProgram("O-O\n", cps);
4519           else SendToProgram("O-O-O\n", cps);
4520         }
4521         else SendToProgram(moveList[moveNum], cps);
4522       }
4523       else SendToProgram(moveList[moveNum], cps);
4524       /* End of additions by Tord */
4525     }
4526
4527     /* [HGM] setting up the opening has brought engine in force mode! */
4528     /*       Send 'go' if we are in a mode where machine should play. */
4529     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4530         (gameMode == TwoMachinesPlay   ||
4531 #if ZIPPY
4532          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4533 #endif
4534          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4535         SendToProgram("go\n", cps);
4536   if (appData.debugMode) {
4537     fprintf(debugFP, "(extra)\n");
4538   }
4539     }
4540     setboardSpoiledMachineBlack = 0;
4541 }
4542
4543 void
4544 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4545      ChessMove moveType;
4546      int fromX, fromY, toX, toY;
4547 {
4548     char user_move[MSG_SIZ];
4549
4550     switch (moveType) {
4551       default:
4552         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4553                 (int)moveType, fromX, fromY, toX, toY);
4554         DisplayError(user_move + strlen("say "), 0);
4555         break;
4556       case WhiteKingSideCastle:
4557       case BlackKingSideCastle:
4558       case WhiteQueenSideCastleWild:
4559       case BlackQueenSideCastleWild:
4560       /* PUSH Fabien */
4561       case WhiteHSideCastleFR:
4562       case BlackHSideCastleFR:
4563       /* POP Fabien */
4564         sprintf(user_move, "o-o\n");
4565         break;
4566       case WhiteQueenSideCastle:
4567       case BlackQueenSideCastle:
4568       case WhiteKingSideCastleWild:
4569       case BlackKingSideCastleWild:
4570       /* PUSH Fabien */
4571       case WhiteASideCastleFR:
4572       case BlackASideCastleFR:
4573       /* POP Fabien */
4574         sprintf(user_move, "o-o-o\n");
4575         break;
4576       case WhitePromotionQueen:
4577       case BlackPromotionQueen:
4578       case WhitePromotionRook:
4579       case BlackPromotionRook:
4580       case WhitePromotionBishop:
4581       case BlackPromotionBishop:
4582       case WhitePromotionKnight:
4583       case BlackPromotionKnight:
4584       case WhitePromotionKing:
4585       case BlackPromotionKing:
4586       case WhitePromotionChancellor:
4587       case BlackPromotionChancellor:
4588       case WhitePromotionArchbishop:
4589       case BlackPromotionArchbishop:
4590         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4591             sprintf(user_move, "%c%c%c%c=%c\n",
4592                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4593                 PieceToChar(WhiteFerz));
4594         else if(gameInfo.variant == VariantGreat)
4595             sprintf(user_move, "%c%c%c%c=%c\n",
4596                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4597                 PieceToChar(WhiteMan));
4598         else
4599             sprintf(user_move, "%c%c%c%c=%c\n",
4600                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4601                 PieceToChar(PromoPiece(moveType)));
4602         break;
4603       case WhiteDrop:
4604       case BlackDrop:
4605         sprintf(user_move, "%c@%c%c\n",
4606                 ToUpper(PieceToChar((ChessSquare) fromX)),
4607                 AAA + toX, ONE + toY);
4608         break;
4609       case NormalMove:
4610       case WhiteCapturesEnPassant:
4611       case BlackCapturesEnPassant:
4612       case IllegalMove:  /* could be a variant we don't quite understand */
4613         sprintf(user_move, "%c%c%c%c\n",
4614                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4615         break;
4616     }
4617     SendToICS(user_move);
4618     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4619         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4620 }
4621
4622 void
4623 UploadGameEvent()
4624 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4625     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4626     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4627     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4628         DisplayError("You cannot do this while you are playing or observing", 0);
4629         return;
4630     }
4631     if(gameMode != IcsExamining) { // is this ever not the case?
4632         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4633
4634         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4635             sprintf(command, "match %s", ics_handle);
4636         } else { // on FICS we must first go to general examine mode
4637             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4638         }
4639         if(gameInfo.variant != VariantNormal) {
4640             // try figure out wild number, as xboard names are not always valid on ICS
4641             for(i=1; i<=36; i++) {
4642                 sprintf(buf, "wild/%d", i);
4643                 if(StringToVariant(buf) == gameInfo.variant) break;
4644             }
4645             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4646             else if(i == 22) sprintf(buf, "%s fr\n", command);
4647             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4648         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4649         SendToICS(ics_prefix);
4650         SendToICS(buf);
4651         if(startedFromSetupPosition || backwardMostMove != 0) {
4652           fen = PositionToFEN(backwardMostMove, NULL);
4653           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4654             sprintf(buf, "loadfen %s\n", fen);
4655             SendToICS(buf);
4656           } else { // FICS: everything has to set by separate bsetup commands
4657             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4658             sprintf(buf, "bsetup fen %s\n", fen);
4659             SendToICS(buf);
4660             if(!WhiteOnMove(backwardMostMove)) {
4661                 SendToICS("bsetup tomove black\n");
4662             }
4663             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4664             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4665             SendToICS(buf);
4666             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4667             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4668             SendToICS(buf);
4669             i = boards[backwardMostMove][EP_STATUS];
4670             if(i >= 0) { // set e.p.
4671                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4672                 SendToICS(buf);
4673             }
4674             bsetup++;
4675           }
4676         }
4677       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4678             SendToICS("bsetup done\n"); // switch to normal examining.
4679     }
4680     for(i = backwardMostMove; i<last; i++) {
4681         char buf[20];
4682         sprintf(buf, "%s\n", parseList[i]);
4683         SendToICS(buf);
4684     }
4685     SendToICS(ics_prefix);
4686     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4687 }
4688
4689 void
4690 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4691      int rf, ff, rt, ft;
4692      char promoChar;
4693      char move[7];
4694 {
4695     if (rf == DROP_RANK) {
4696         sprintf(move, "%c@%c%c\n",
4697                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4698     } else {
4699         if (promoChar == 'x' || promoChar == NULLCHAR) {
4700             sprintf(move, "%c%c%c%c\n",
4701                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4702         } else {
4703             sprintf(move, "%c%c%c%c%c\n",
4704                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4705         }
4706     }
4707 }
4708
4709 void
4710 ProcessICSInitScript(f)
4711      FILE *f;
4712 {
4713     char buf[MSG_SIZ];
4714
4715     while (fgets(buf, MSG_SIZ, f)) {
4716         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4717     }
4718
4719     fclose(f);
4720 }
4721
4722
4723 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4724 void
4725 AlphaRank(char *move, int n)
4726 {
4727 //    char *p = move, c; int x, y;
4728
4729     if (appData.debugMode) {
4730         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4731     }
4732
4733     if(move[1]=='*' && 
4734        move[2]>='0' && move[2]<='9' &&
4735        move[3]>='a' && move[3]<='x'    ) {
4736         move[1] = '@';
4737         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4738         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4739     } else
4740     if(move[0]>='0' && move[0]<='9' &&
4741        move[1]>='a' && move[1]<='x' &&
4742        move[2]>='0' && move[2]<='9' &&
4743        move[3]>='a' && move[3]<='x'    ) {
4744         /* input move, Shogi -> normal */
4745         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4746         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4747         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4748         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4749     } else
4750     if(move[1]=='@' &&
4751        move[3]>='0' && move[3]<='9' &&
4752        move[2]>='a' && move[2]<='x'    ) {
4753         move[1] = '*';
4754         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4755         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4756     } else
4757     if(
4758        move[0]>='a' && move[0]<='x' &&
4759        move[3]>='0' && move[3]<='9' &&
4760        move[2]>='a' && move[2]<='x'    ) {
4761          /* output move, normal -> Shogi */
4762         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4763         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4764         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4765         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4766         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4767     }
4768     if (appData.debugMode) {
4769         fprintf(debugFP, "   out = '%s'\n", move);
4770     }
4771 }
4772
4773 char yy_textstr[8000];
4774
4775 /* Parser for moves from gnuchess, ICS, or user typein box */
4776 Boolean
4777 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4778      char *move;
4779      int moveNum;
4780      ChessMove *moveType;
4781      int *fromX, *fromY, *toX, *toY;
4782      char *promoChar;
4783 {       
4784     if (appData.debugMode) {
4785         fprintf(debugFP, "move to parse: %s\n", move);
4786     }
4787     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4788
4789     switch (*moveType) {
4790       case WhitePromotionChancellor:
4791       case BlackPromotionChancellor:
4792       case WhitePromotionArchbishop:
4793       case BlackPromotionArchbishop:
4794       case WhitePromotionQueen:
4795       case BlackPromotionQueen:
4796       case WhitePromotionRook:
4797       case BlackPromotionRook:
4798       case WhitePromotionBishop:
4799       case BlackPromotionBishop:
4800       case WhitePromotionKnight:
4801       case BlackPromotionKnight:
4802       case WhitePromotionKing:
4803       case BlackPromotionKing:
4804       case NormalMove:
4805       case WhiteCapturesEnPassant:
4806       case BlackCapturesEnPassant:
4807       case WhiteKingSideCastle:
4808       case WhiteQueenSideCastle:
4809       case BlackKingSideCastle:
4810       case BlackQueenSideCastle:
4811       case WhiteKingSideCastleWild:
4812       case WhiteQueenSideCastleWild:
4813       case BlackKingSideCastleWild:
4814       case BlackQueenSideCastleWild:
4815       /* Code added by Tord: */
4816       case WhiteHSideCastleFR:
4817       case WhiteASideCastleFR:
4818       case BlackHSideCastleFR:
4819       case BlackASideCastleFR:
4820       /* End of code added by Tord */
4821       case IllegalMove:         /* bug or odd chess variant */
4822         *fromX = currentMoveString[0] - AAA;
4823         *fromY = currentMoveString[1] - ONE;
4824         *toX = currentMoveString[2] - AAA;
4825         *toY = currentMoveString[3] - ONE;
4826         *promoChar = currentMoveString[4];
4827         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4828             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4829     if (appData.debugMode) {
4830         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4831     }
4832             *fromX = *fromY = *toX = *toY = 0;
4833             return FALSE;
4834         }
4835         if (appData.testLegality) {
4836           return (*moveType != IllegalMove);
4837         } else {
4838           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4839                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4840         }
4841
4842       case WhiteDrop:
4843       case BlackDrop:
4844         *fromX = *moveType == WhiteDrop ?
4845           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4846           (int) CharToPiece(ToLower(currentMoveString[0]));
4847         *fromY = DROP_RANK;
4848         *toX = currentMoveString[2] - AAA;
4849         *toY = currentMoveString[3] - ONE;
4850         *promoChar = NULLCHAR;
4851         return TRUE;
4852
4853       case AmbiguousMove:
4854       case ImpossibleMove:
4855       case (ChessMove) 0:       /* end of file */
4856       case ElapsedTime:
4857       case Comment:
4858       case PGNTag:
4859       case NAG:
4860       case WhiteWins:
4861       case BlackWins:
4862       case GameIsDrawn:
4863       default:
4864     if (appData.debugMode) {
4865         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4866     }
4867         /* bug? */
4868         *fromX = *fromY = *toX = *toY = 0;
4869         *promoChar = NULLCHAR;
4870         return FALSE;
4871     }
4872 }
4873
4874
4875 void
4876 ParsePV(char *pv, Boolean storeComments)
4877 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4878   int fromX, fromY, toX, toY; char promoChar;
4879   ChessMove moveType;
4880   Boolean valid;
4881   int nr = 0;
4882
4883   endPV = forwardMostMove;
4884   do {
4885     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4886     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4887     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4888 if(appData.debugMode){
4889 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);
4890 }
4891     if(!valid && nr == 0 &&
4892        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4893         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4894         // Hande case where played move is different from leading PV move
4895         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4896         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4897         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4898         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4899           endPV += 2; // if position different, keep this
4900           moveList[endPV-1][0] = fromX + AAA;
4901           moveList[endPV-1][1] = fromY + ONE;
4902           moveList[endPV-1][2] = toX + AAA;
4903           moveList[endPV-1][3] = toY + ONE;
4904           parseList[endPV-1][0] = NULLCHAR;
4905           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4906         }
4907       }
4908     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4909     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4910     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4911     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4912         valid++; // allow comments in PV
4913         continue;
4914     }
4915     nr++;
4916     if(endPV+1 > framePtr) break; // no space, truncate
4917     if(!valid) break;
4918     endPV++;
4919     CopyBoard(boards[endPV], boards[endPV-1]);
4920     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4921     moveList[endPV-1][0] = fromX + AAA;
4922     moveList[endPV-1][1] = fromY + ONE;
4923     moveList[endPV-1][2] = toX + AAA;
4924     moveList[endPV-1][3] = toY + ONE;
4925     if(storeComments)
4926         CoordsToAlgebraic(boards[endPV - 1],
4927                              PosFlags(endPV - 1),
4928                              fromY, fromX, toY, toX, promoChar,
4929                              parseList[endPV - 1]);
4930     else
4931         parseList[endPV-1][0] = NULLCHAR;
4932   } while(valid);
4933   currentMove = endPV;
4934   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4935   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4936                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4937   DrawPosition(TRUE, boards[currentMove]);
4938 }
4939
4940 static int lastX, lastY;
4941
4942 Boolean
4943 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4944 {
4945         int startPV;
4946         char *p;
4947
4948         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4949         lastX = x; lastY = y;
4950         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4951         startPV = index;
4952         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4953         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4954         index = startPV;
4955         do{ while(buf[index] && buf[index] != '\n') index++;
4956         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4957         buf[index] = 0;
4958         ParsePV(buf+startPV, FALSE);
4959         *start = startPV; *end = index-1;
4960         return TRUE;
4961 }
4962
4963 Boolean
4964 LoadPV(int x, int y)
4965 { // called on right mouse click to load PV
4966   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4967   lastX = x; lastY = y;
4968   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4969   return TRUE;
4970 }
4971
4972 void
4973 UnLoadPV()
4974 {
4975   if(endPV < 0) return;
4976   endPV = -1;
4977   currentMove = forwardMostMove;
4978   ClearPremoveHighlights();
4979   DrawPosition(TRUE, boards[currentMove]);
4980 }
4981
4982 void
4983 MovePV(int x, int y, int h)
4984 { // step through PV based on mouse coordinates (called on mouse move)
4985   int margin = h>>3, step = 0;
4986
4987   if(endPV < 0) return;
4988   // we must somehow check if right button is still down (might be released off board!)
4989   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4990   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4991   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4992   if(!step) return;
4993   lastX = x; lastY = y;
4994   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4995   currentMove += step;
4996   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4997   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4998                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4999   DrawPosition(FALSE, boards[currentMove]);
5000 }
5001
5002
5003 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5004 // All positions will have equal probability, but the current method will not provide a unique
5005 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5006 #define DARK 1
5007 #define LITE 2
5008 #define ANY 3
5009
5010 int squaresLeft[4];
5011 int piecesLeft[(int)BlackPawn];
5012 int seed, nrOfShuffles;
5013
5014 void GetPositionNumber()
5015 {       // sets global variable seed
5016         int i;
5017
5018         seed = appData.defaultFrcPosition;
5019         if(seed < 0) { // randomize based on time for negative FRC position numbers
5020                 for(i=0; i<50; i++) seed += random();
5021                 seed = random() ^ random() >> 8 ^ random() << 8;
5022                 if(seed<0) seed = -seed;
5023         }
5024 }
5025
5026 int put(Board board, int pieceType, int rank, int n, int shade)
5027 // put the piece on the (n-1)-th empty squares of the given shade
5028 {
5029         int i;
5030
5031         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5032                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5033                         board[rank][i] = (ChessSquare) pieceType;
5034                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5035                         squaresLeft[ANY]--;
5036                         piecesLeft[pieceType]--; 
5037                         return i;
5038                 }
5039         }
5040         return -1;
5041 }
5042
5043
5044 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5045 // calculate where the next piece goes, (any empty square), and put it there
5046 {
5047         int i;
5048
5049         i = seed % squaresLeft[shade];
5050         nrOfShuffles *= squaresLeft[shade];
5051         seed /= squaresLeft[shade];
5052         put(board, pieceType, rank, i, shade);
5053 }
5054
5055 void AddTwoPieces(Board board, int pieceType, int rank)
5056 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5057 {
5058         int i, n=squaresLeft[ANY], j=n-1, k;
5059
5060         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5061         i = seed % k;  // pick one
5062         nrOfShuffles *= k;
5063         seed /= k;
5064         while(i >= j) i -= j--;
5065         j = n - 1 - j; i += j;
5066         put(board, pieceType, rank, j, ANY);
5067         put(board, pieceType, rank, i, ANY);
5068 }
5069
5070 void SetUpShuffle(Board board, int number)
5071 {
5072         int i, p, first=1;
5073
5074         GetPositionNumber(); nrOfShuffles = 1;
5075
5076         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5077         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5078         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5079
5080         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5081
5082         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5083             p = (int) board[0][i];
5084             if(p < (int) BlackPawn) piecesLeft[p] ++;
5085             board[0][i] = EmptySquare;
5086         }
5087
5088         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5089             // shuffles restricted to allow normal castling put KRR first
5090             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5091                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5092             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5093                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5094             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5095                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5096             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5097                 put(board, WhiteRook, 0, 0, ANY);
5098             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5099         }
5100
5101         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5102             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5103             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5104                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5105                 while(piecesLeft[p] >= 2) {
5106                     AddOnePiece(board, p, 0, LITE);
5107                     AddOnePiece(board, p, 0, DARK);
5108                 }
5109                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5110             }
5111
5112         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5113             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5114             // but we leave King and Rooks for last, to possibly obey FRC restriction
5115             if(p == (int)WhiteRook) continue;
5116             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5117             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5118         }
5119
5120         // now everything is placed, except perhaps King (Unicorn) and Rooks
5121
5122         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5123             // Last King gets castling rights
5124             while(piecesLeft[(int)WhiteUnicorn]) {
5125                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5126                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5127             }
5128
5129             while(piecesLeft[(int)WhiteKing]) {
5130                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5131                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5132             }
5133
5134
5135         } else {
5136             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5137             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5138         }
5139
5140         // Only Rooks can be left; simply place them all
5141         while(piecesLeft[(int)WhiteRook]) {
5142                 i = put(board, WhiteRook, 0, 0, ANY);
5143                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5144                         if(first) {
5145                                 first=0;
5146                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5147                         }
5148                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5149                 }
5150         }
5151         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5152             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5153         }
5154
5155         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5156 }
5157
5158 int SetCharTable( char *table, const char * map )
5159 /* [HGM] moved here from winboard.c because of its general usefulness */
5160 /*       Basically a safe strcpy that uses the last character as King */
5161 {
5162     int result = FALSE; int NrPieces;
5163
5164     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5165                     && NrPieces >= 12 && !(NrPieces&1)) {
5166         int i; /* [HGM] Accept even length from 12 to 34 */
5167
5168         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5169         for( i=0; i<NrPieces/2-1; i++ ) {
5170             table[i] = map[i];
5171             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5172         }
5173         table[(int) WhiteKing]  = map[NrPieces/2-1];
5174         table[(int) BlackKing]  = map[NrPieces-1];
5175
5176         result = TRUE;
5177     }
5178
5179     return result;
5180 }
5181
5182 void Prelude(Board board)
5183 {       // [HGM] superchess: random selection of exo-pieces
5184         int i, j, k; ChessSquare p; 
5185         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5186
5187         GetPositionNumber(); // use FRC position number
5188
5189         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5190             SetCharTable(pieceToChar, appData.pieceToCharTable);
5191             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5192                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5193         }
5194
5195         j = seed%4;                 seed /= 4; 
5196         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5197         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5198         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5199         j = seed%3 + (seed%3 >= j); seed /= 3; 
5200         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5201         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5202         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5203         j = seed%3;                 seed /= 3; 
5204         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5205         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5206         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5207         j = seed%2 + (seed%2 >= j); seed /= 2; 
5208         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5209         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5210         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5211         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5212         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5213         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5214         put(board, exoPieces[0],    0, 0, ANY);
5215         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5216 }
5217
5218 void
5219 InitPosition(redraw)
5220      int redraw;
5221 {
5222     ChessSquare (* pieces)[BOARD_FILES];
5223     int i, j, pawnRow, overrule,
5224     oldx = gameInfo.boardWidth,
5225     oldy = gameInfo.boardHeight,
5226     oldh = gameInfo.holdingsWidth,
5227     oldv = gameInfo.variant;
5228
5229     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5230
5231     /* [AS] Initialize pv info list [HGM] and game status */
5232     {
5233         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5234             pvInfoList[i].depth = 0;
5235             boards[i][EP_STATUS] = EP_NONE;
5236             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5237         }
5238
5239         initialRulePlies = 0; /* 50-move counter start */
5240
5241         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5242         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5243     }
5244
5245     
5246     /* [HGM] logic here is completely changed. In stead of full positions */
5247     /* the initialized data only consist of the two backranks. The switch */
5248     /* selects which one we will use, which is than copied to the Board   */
5249     /* initialPosition, which for the rest is initialized by Pawns and    */
5250     /* empty squares. This initial position is then copied to boards[0],  */
5251     /* possibly after shuffling, so that it remains available.            */
5252
5253     gameInfo.holdingsWidth = 0; /* default board sizes */
5254     gameInfo.boardWidth    = 8;
5255     gameInfo.boardHeight   = 8;
5256     gameInfo.holdingsSize  = 0;
5257     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5258     for(i=0; i<BOARD_FILES-2; i++)
5259       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5260     initialPosition[EP_STATUS] = EP_NONE;
5261     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5262     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5263          SetCharTable(pieceNickName, appData.pieceNickNames);
5264     else SetCharTable(pieceNickName, "............");
5265
5266     switch (gameInfo.variant) {
5267     case VariantFischeRandom:
5268       shuffleOpenings = TRUE;
5269     default:
5270       pieces = FIDEArray;
5271       break;
5272     case VariantShatranj:
5273       pieces = ShatranjArray;
5274       nrCastlingRights = 0;
5275       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5276       break;
5277     case VariantMakruk:
5278       pieces = makrukArray;
5279       nrCastlingRights = 0;
5280       startedFromSetupPosition = TRUE;
5281       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5282       break;
5283     case VariantTwoKings:
5284       pieces = twoKingsArray;
5285       break;
5286     case VariantCapaRandom:
5287       shuffleOpenings = TRUE;
5288     case VariantCapablanca:
5289       pieces = CapablancaArray;
5290       gameInfo.boardWidth = 10;
5291       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5292       break;
5293     case VariantGothic:
5294       pieces = GothicArray;
5295       gameInfo.boardWidth = 10;
5296       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5297       break;
5298     case VariantJanus:
5299       pieces = JanusArray;
5300       gameInfo.boardWidth = 10;
5301       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5302       nrCastlingRights = 6;
5303         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5304         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5305         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5306         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5307         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5308         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5309       break;
5310     case VariantFalcon:
5311       pieces = FalconArray;
5312       gameInfo.boardWidth = 10;
5313       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5314       break;
5315     case VariantXiangqi:
5316       pieces = XiangqiArray;
5317       gameInfo.boardWidth  = 9;
5318       gameInfo.boardHeight = 10;
5319       nrCastlingRights = 0;
5320       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5321       break;
5322     case VariantShogi:
5323       pieces = ShogiArray;
5324       gameInfo.boardWidth  = 9;
5325       gameInfo.boardHeight = 9;
5326       gameInfo.holdingsSize = 7;
5327       nrCastlingRights = 0;
5328       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5329       break;
5330     case VariantCourier:
5331       pieces = CourierArray;
5332       gameInfo.boardWidth  = 12;
5333       nrCastlingRights = 0;
5334       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5335       break;
5336     case VariantKnightmate:
5337       pieces = KnightmateArray;
5338       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5339       break;
5340     case VariantFairy:
5341       pieces = fairyArray;
5342       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5343       break;
5344     case VariantGreat:
5345       pieces = GreatArray;
5346       gameInfo.boardWidth = 10;
5347       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5348       gameInfo.holdingsSize = 8;
5349       break;
5350     case VariantSuper:
5351       pieces = FIDEArray;
5352       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5353       gameInfo.holdingsSize = 8;
5354       startedFromSetupPosition = TRUE;
5355       break;
5356     case VariantCrazyhouse:
5357     case VariantBughouse:
5358       pieces = FIDEArray;
5359       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5360       gameInfo.holdingsSize = 5;
5361       break;
5362     case VariantWildCastle:
5363       pieces = FIDEArray;
5364       /* !!?shuffle with kings guaranteed to be on d or e file */
5365       shuffleOpenings = 1;
5366       break;
5367     case VariantNoCastle:
5368       pieces = FIDEArray;
5369       nrCastlingRights = 0;
5370       /* !!?unconstrained back-rank shuffle */
5371       shuffleOpenings = 1;
5372       break;
5373     }
5374
5375     overrule = 0;
5376     if(appData.NrFiles >= 0) {
5377         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5378         gameInfo.boardWidth = appData.NrFiles;
5379     }
5380     if(appData.NrRanks >= 0) {
5381         gameInfo.boardHeight = appData.NrRanks;
5382     }
5383     if(appData.holdingsSize >= 0) {
5384         i = appData.holdingsSize;
5385         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5386         gameInfo.holdingsSize = i;
5387     }
5388     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5389     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5390         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5391
5392     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5393     if(pawnRow < 1) pawnRow = 1;
5394     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5395
5396     /* User pieceToChar list overrules defaults */
5397     if(appData.pieceToCharTable != NULL)
5398         SetCharTable(pieceToChar, appData.pieceToCharTable);
5399
5400     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5401
5402         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5403             s = (ChessSquare) 0; /* account holding counts in guard band */
5404         for( i=0; i<BOARD_HEIGHT; i++ )
5405             initialPosition[i][j] = s;
5406
5407         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5408         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5409         initialPosition[pawnRow][j] = WhitePawn;
5410         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5411         if(gameInfo.variant == VariantXiangqi) {
5412             if(j&1) {
5413                 initialPosition[pawnRow][j] = 
5414                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5415                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5416                    initialPosition[2][j] = WhiteCannon;
5417                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5418                 }
5419             }
5420         }
5421         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5422     }
5423     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5424
5425             j=BOARD_LEFT+1;
5426             initialPosition[1][j] = WhiteBishop;
5427             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5428             j=BOARD_RGHT-2;
5429             initialPosition[1][j] = WhiteRook;
5430             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5431     }
5432
5433     if( nrCastlingRights == -1) {
5434         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5435         /*       This sets default castling rights from none to normal corners   */
5436         /* Variants with other castling rights must set them themselves above    */
5437         nrCastlingRights = 6;
5438        
5439         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5440         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5441         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5442         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5443         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5444         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5445      }
5446
5447      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5448      if(gameInfo.variant == VariantGreat) { // promotion commoners
5449         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5450         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5451         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5452         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5453      }
5454   if (appData.debugMode) {
5455     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5456   }
5457     if(shuffleOpenings) {
5458         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5459         startedFromSetupPosition = TRUE;
5460     }
5461     if(startedFromPositionFile) {
5462       /* [HGM] loadPos: use PositionFile for every new game */
5463       CopyBoard(initialPosition, filePosition);
5464       for(i=0; i<nrCastlingRights; i++)
5465           initialRights[i] = filePosition[CASTLING][i];
5466       startedFromSetupPosition = TRUE;
5467     }
5468
5469     CopyBoard(boards[0], initialPosition);
5470
5471     if(oldx != gameInfo.boardWidth ||
5472        oldy != gameInfo.boardHeight ||
5473        oldh != gameInfo.holdingsWidth
5474 #ifdef GOTHIC
5475        || oldv == VariantGothic ||        // For licensing popups
5476        gameInfo.variant == VariantGothic
5477 #endif
5478 #ifdef FALCON
5479        || oldv == VariantFalcon ||
5480        gameInfo.variant == VariantFalcon
5481 #endif
5482                                          )
5483             InitDrawingSizes(-2 ,0);
5484
5485     if (redraw)
5486       DrawPosition(TRUE, boards[currentMove]);
5487 }
5488
5489 void
5490 SendBoard(cps, moveNum)
5491      ChessProgramState *cps;
5492      int moveNum;
5493 {
5494     char message[MSG_SIZ];
5495     
5496     if (cps->useSetboard) {
5497       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5498       sprintf(message, "setboard %s\n", fen);
5499       SendToProgram(message, cps);
5500       free(fen);
5501
5502     } else {
5503       ChessSquare *bp;
5504       int i, j;
5505       /* Kludge to set black to move, avoiding the troublesome and now
5506        * deprecated "black" command.
5507        */
5508       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5509
5510       SendToProgram("edit\n", cps);
5511       SendToProgram("#\n", cps);
5512       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5513         bp = &boards[moveNum][i][BOARD_LEFT];
5514         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5515           if ((int) *bp < (int) BlackPawn) {
5516             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5517                     AAA + j, ONE + i);
5518             if(message[0] == '+' || message[0] == '~') {
5519                 sprintf(message, "%c%c%c+\n",
5520                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5521                         AAA + j, ONE + i);
5522             }
5523             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5524                 message[1] = BOARD_RGHT   - 1 - j + '1';
5525                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5526             }
5527             SendToProgram(message, cps);
5528           }
5529         }
5530       }
5531     
5532       SendToProgram("c\n", cps);
5533       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5534         bp = &boards[moveNum][i][BOARD_LEFT];
5535         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5536           if (((int) *bp != (int) EmptySquare)
5537               && ((int) *bp >= (int) BlackPawn)) {
5538             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5539                     AAA + j, ONE + i);
5540             if(message[0] == '+' || message[0] == '~') {
5541                 sprintf(message, "%c%c%c+\n",
5542                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5543                         AAA + j, ONE + i);
5544             }
5545             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5546                 message[1] = BOARD_RGHT   - 1 - j + '1';
5547                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5548             }
5549             SendToProgram(message, cps);
5550           }
5551         }
5552       }
5553     
5554       SendToProgram(".\n", cps);
5555     }
5556     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5557 }
5558
5559 static int autoQueen; // [HGM] oneclick
5560
5561 int
5562 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5563 {
5564     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5565     /* [HGM] add Shogi promotions */
5566     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5567     ChessSquare piece;
5568     ChessMove moveType;
5569     Boolean premove;
5570
5571     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5572     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5573
5574     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5575       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5576         return FALSE;
5577
5578     piece = boards[currentMove][fromY][fromX];
5579     if(gameInfo.variant == VariantShogi) {
5580         promotionZoneSize = 3;
5581         highestPromotingPiece = (int)WhiteFerz;
5582     } else if(gameInfo.variant == VariantMakruk) {
5583         promotionZoneSize = 3;
5584     }
5585
5586     // next weed out all moves that do not touch the promotion zone at all
5587     if((int)piece >= BlackPawn) {
5588         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5589              return FALSE;
5590         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5591     } else {
5592         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5593            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5594     }
5595
5596     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5597
5598     // weed out mandatory Shogi promotions
5599     if(gameInfo.variant == VariantShogi) {
5600         if(piece >= BlackPawn) {
5601             if(toY == 0 && piece == BlackPawn ||
5602                toY == 0 && piece == BlackQueen ||
5603                toY <= 1 && piece == BlackKnight) {
5604                 *promoChoice = '+';
5605                 return FALSE;
5606             }
5607         } else {
5608             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5609                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5610                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5611                 *promoChoice = '+';
5612                 return FALSE;
5613             }
5614         }
5615     }
5616
5617     // weed out obviously illegal Pawn moves
5618     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5619         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5620         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5621         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5622         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5623         // note we are not allowed to test for valid (non-)capture, due to premove
5624     }
5625
5626     // we either have a choice what to promote to, or (in Shogi) whether to promote
5627     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5628         *promoChoice = PieceToChar(BlackFerz);  // no choice
5629         return FALSE;
5630     }
5631     if(autoQueen) { // predetermined
5632         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5633              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5634         else *promoChoice = PieceToChar(BlackQueen);
5635         return FALSE;
5636     }
5637
5638     // suppress promotion popup on illegal moves that are not premoves
5639     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5640               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5641     if(appData.testLegality && !premove) {
5642         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5643                         fromY, fromX, toY, toX, NULLCHAR);
5644         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5645            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5646             return FALSE;
5647     }
5648
5649     return TRUE;
5650 }
5651
5652 int
5653 InPalace(row, column)
5654      int row, column;
5655 {   /* [HGM] for Xiangqi */
5656     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5657          column < (BOARD_WIDTH + 4)/2 &&
5658          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5659     return FALSE;
5660 }
5661
5662 int
5663 PieceForSquare (x, y)
5664      int x;
5665      int y;
5666 {
5667   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5668      return -1;
5669   else
5670      return boards[currentMove][y][x];
5671 }
5672
5673 int
5674 OKToStartUserMove(x, y)
5675      int x, y;
5676 {
5677     ChessSquare from_piece;
5678     int white_piece;
5679
5680     if (matchMode) return FALSE;
5681     if (gameMode == EditPosition) return TRUE;
5682
5683     if (x >= 0 && y >= 0)
5684       from_piece = boards[currentMove][y][x];
5685     else
5686       from_piece = EmptySquare;
5687
5688     if (from_piece == EmptySquare) return FALSE;
5689
5690     white_piece = (int)from_piece >= (int)WhitePawn &&
5691       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5692
5693     switch (gameMode) {
5694       case PlayFromGameFile:
5695       case AnalyzeFile:
5696       case TwoMachinesPlay:
5697       case EndOfGame:
5698         return FALSE;
5699
5700       case IcsObserving:
5701       case IcsIdle:
5702         return FALSE;
5703
5704       case MachinePlaysWhite:
5705       case IcsPlayingBlack:
5706         if (appData.zippyPlay) return FALSE;
5707         if (white_piece) {
5708             DisplayMoveError(_("You are playing Black"));
5709             return FALSE;
5710         }
5711         break;
5712
5713       case MachinePlaysBlack:
5714       case IcsPlayingWhite:
5715         if (appData.zippyPlay) return FALSE;
5716         if (!white_piece) {
5717             DisplayMoveError(_("You are playing White"));
5718             return FALSE;
5719         }
5720         break;
5721
5722       case EditGame:
5723         if (!white_piece && WhiteOnMove(currentMove)) {
5724             DisplayMoveError(_("It is White's turn"));
5725             return FALSE;
5726         }           
5727         if (white_piece && !WhiteOnMove(currentMove)) {
5728             DisplayMoveError(_("It is Black's turn"));
5729             return FALSE;
5730         }           
5731         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5732             /* Editing correspondence game history */
5733             /* Could disallow this or prompt for confirmation */
5734             cmailOldMove = -1;
5735         }
5736         break;
5737
5738       case BeginningOfGame:
5739         if (appData.icsActive) return FALSE;
5740         if (!appData.noChessProgram) {
5741             if (!white_piece) {
5742                 DisplayMoveError(_("You are playing White"));
5743                 return FALSE;
5744             }
5745         }
5746         break;
5747         
5748       case Training:
5749         if (!white_piece && WhiteOnMove(currentMove)) {
5750             DisplayMoveError(_("It is White's turn"));
5751             return FALSE;
5752         }           
5753         if (white_piece && !WhiteOnMove(currentMove)) {
5754             DisplayMoveError(_("It is Black's turn"));
5755             return FALSE;
5756         }           
5757         break;
5758
5759       default:
5760       case IcsExamining:
5761         break;
5762     }
5763     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5764         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5765         && gameMode != AnalyzeFile && gameMode != Training) {
5766         DisplayMoveError(_("Displayed position is not current"));
5767         return FALSE;
5768     }
5769     return TRUE;
5770 }
5771
5772 Boolean
5773 OnlyMove(int *x, int *y, Boolean captures) {
5774     DisambiguateClosure cl;
5775     if (appData.zippyPlay) return FALSE;
5776     switch(gameMode) {
5777       case MachinePlaysBlack:
5778       case IcsPlayingWhite:
5779       case BeginningOfGame:
5780         if(!WhiteOnMove(currentMove)) return FALSE;
5781         break;
5782       case MachinePlaysWhite:
5783       case IcsPlayingBlack:
5784         if(WhiteOnMove(currentMove)) return FALSE;
5785         break;
5786       default:
5787         return FALSE;
5788     }
5789     cl.pieceIn = EmptySquare; 
5790     cl.rfIn = *y;
5791     cl.ffIn = *x;
5792     cl.rtIn = -1;
5793     cl.ftIn = -1;
5794     cl.promoCharIn = NULLCHAR;
5795     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5796     if( cl.kind == NormalMove ||
5797         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5798         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5799         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5800         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5801       fromX = cl.ff;
5802       fromY = cl.rf;
5803       *x = cl.ft;
5804       *y = cl.rt;
5805       return TRUE;
5806     }
5807     if(cl.kind != ImpossibleMove) return FALSE;
5808     cl.pieceIn = EmptySquare;
5809     cl.rfIn = -1;
5810     cl.ffIn = -1;
5811     cl.rtIn = *y;
5812     cl.ftIn = *x;
5813     cl.promoCharIn = NULLCHAR;
5814     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5815     if( cl.kind == NormalMove ||
5816         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5817         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5818         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5819         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5820       fromX = cl.ff;
5821       fromY = cl.rf;
5822       *x = cl.ft;
5823       *y = cl.rt;
5824       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5825       return TRUE;
5826     }
5827     return FALSE;
5828 }
5829
5830 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5831 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5832 int lastLoadGameUseList = FALSE;
5833 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5834 ChessMove lastLoadGameStart = (ChessMove) 0;
5835
5836 ChessMove
5837 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5838      int fromX, fromY, toX, toY;
5839      int promoChar;
5840      Boolean captureOwn;
5841 {
5842     ChessMove moveType;
5843     ChessSquare pdown, pup;
5844
5845     /* Check if the user is playing in turn.  This is complicated because we
5846        let the user "pick up" a piece before it is his turn.  So the piece he
5847        tried to pick up may have been captured by the time he puts it down!
5848        Therefore we use the color the user is supposed to be playing in this
5849        test, not the color of the piece that is currently on the starting
5850        square---except in EditGame mode, where the user is playing both
5851        sides; fortunately there the capture race can't happen.  (It can
5852        now happen in IcsExamining mode, but that's just too bad.  The user
5853        will get a somewhat confusing message in that case.)
5854        */
5855
5856     switch (gameMode) {
5857       case PlayFromGameFile:
5858       case AnalyzeFile:
5859       case TwoMachinesPlay:
5860       case EndOfGame:
5861       case IcsObserving:
5862       case IcsIdle:
5863         /* We switched into a game mode where moves are not accepted,
5864            perhaps while the mouse button was down. */
5865         return ImpossibleMove;
5866
5867       case MachinePlaysWhite:
5868         /* User is moving for Black */
5869         if (WhiteOnMove(currentMove)) {
5870             DisplayMoveError(_("It is White's turn"));
5871             return ImpossibleMove;
5872         }
5873         break;
5874
5875       case MachinePlaysBlack:
5876         /* User is moving for White */
5877         if (!WhiteOnMove(currentMove)) {
5878             DisplayMoveError(_("It is Black's turn"));
5879             return ImpossibleMove;
5880         }
5881         break;
5882
5883       case EditGame:
5884       case IcsExamining:
5885       case BeginningOfGame:
5886       case AnalyzeMode:
5887       case Training:
5888         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5889             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5890             /* User is moving for Black */
5891             if (WhiteOnMove(currentMove)) {
5892                 DisplayMoveError(_("It is White's turn"));
5893                 return ImpossibleMove;
5894             }
5895         } else {
5896             /* User is moving for White */
5897             if (!WhiteOnMove(currentMove)) {
5898                 DisplayMoveError(_("It is Black's turn"));
5899                 return ImpossibleMove;
5900             }
5901         }
5902         break;
5903
5904       case IcsPlayingBlack:
5905         /* User is moving for Black */
5906         if (WhiteOnMove(currentMove)) {
5907             if (!appData.premove) {
5908                 DisplayMoveError(_("It is White's turn"));
5909             } else if (toX >= 0 && toY >= 0) {
5910                 premoveToX = toX;
5911                 premoveToY = toY;
5912                 premoveFromX = fromX;
5913                 premoveFromY = fromY;
5914                 premovePromoChar = promoChar;
5915                 gotPremove = 1;
5916                 if (appData.debugMode) 
5917                     fprintf(debugFP, "Got premove: fromX %d,"
5918                             "fromY %d, toX %d, toY %d\n",
5919                             fromX, fromY, toX, toY);
5920             }
5921             return ImpossibleMove;
5922         }
5923         break;
5924
5925       case IcsPlayingWhite:
5926         /* User is moving for White */
5927         if (!WhiteOnMove(currentMove)) {
5928             if (!appData.premove) {
5929                 DisplayMoveError(_("It is Black's turn"));
5930             } else if (toX >= 0 && toY >= 0) {
5931                 premoveToX = toX;
5932                 premoveToY = toY;
5933                 premoveFromX = fromX;
5934                 premoveFromY = fromY;
5935                 premovePromoChar = promoChar;
5936                 gotPremove = 1;
5937                 if (appData.debugMode) 
5938                     fprintf(debugFP, "Got premove: fromX %d,"
5939                             "fromY %d, toX %d, toY %d\n",
5940                             fromX, fromY, toX, toY);
5941             }
5942             return ImpossibleMove;
5943         }
5944         break;
5945
5946       default:
5947         break;
5948
5949       case EditPosition:
5950         /* EditPosition, empty square, or different color piece;
5951            click-click move is possible */
5952         if (toX == -2 || toY == -2) {
5953             boards[0][fromY][fromX] = EmptySquare;
5954             return AmbiguousMove;
5955         } else if (toX >= 0 && toY >= 0) {
5956             boards[0][toY][toX] = boards[0][fromY][fromX];
5957             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5958                 if(boards[0][fromY][0] != EmptySquare) {
5959                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5960                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5961                 }
5962             } else
5963             if(fromX == BOARD_RGHT+1) {
5964                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5965                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5966                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5967                 }
5968             } else
5969             boards[0][fromY][fromX] = EmptySquare;
5970             return AmbiguousMove;
5971         }
5972         return ImpossibleMove;
5973     }
5974
5975     if(toX < 0 || toY < 0) return ImpossibleMove;
5976     pdown = boards[currentMove][fromY][fromX];
5977     pup = boards[currentMove][toY][toX];
5978
5979     /* [HGM] If move started in holdings, it means a drop */
5980     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5981          if( pup != EmptySquare ) return ImpossibleMove;
5982          if(appData.testLegality) {
5983              /* it would be more logical if LegalityTest() also figured out
5984               * which drops are legal. For now we forbid pawns on back rank.
5985               * Shogi is on its own here...
5986               */
5987              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5988                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5989                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5990          }
5991          return WhiteDrop; /* Not needed to specify white or black yet */
5992     }
5993
5994     /* [HGM] always test for legality, to get promotion info */
5995     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5996                                          fromY, fromX, toY, toX, promoChar);
5997     /* [HGM] but possibly ignore an IllegalMove result */
5998     if (appData.testLegality) {
5999         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6000             DisplayMoveError(_("Illegal move"));
6001             return ImpossibleMove;
6002         }
6003     }
6004
6005     return moveType;
6006     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6007        function is made into one that returns an OK move type if FinishMove
6008        should be called. This to give the calling driver routine the
6009        opportunity to finish the userMove input with a promotion popup,
6010        without bothering the user with this for invalid or illegal moves */
6011
6012 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6013 }
6014
6015 /* Common tail of UserMoveEvent and DropMenuEvent */
6016 int
6017 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6018      ChessMove moveType;
6019      int fromX, fromY, toX, toY;
6020      /*char*/int promoChar;
6021 {
6022     char *bookHit = 0;
6023
6024     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6025         // [HGM] superchess: suppress promotions to non-available piece
6026         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6027         if(WhiteOnMove(currentMove)) {
6028             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6029         } else {
6030             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6031         }
6032     }
6033
6034     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6035        move type in caller when we know the move is a legal promotion */
6036     if(moveType == NormalMove && promoChar)
6037         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6038
6039     /* [HGM] convert drag-and-drop piece drops to standard form */
6040     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6041          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6042            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6043                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6044            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6045            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6046            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6047            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6048          fromY = DROP_RANK;
6049     }
6050
6051     /* [HGM] <popupFix> The following if has been moved here from
6052        UserMoveEvent(). Because it seemed to belong here (why not allow
6053        piece drops in training games?), and because it can only be
6054        performed after it is known to what we promote. */
6055     if (gameMode == Training) {
6056       /* compare the move played on the board to the next move in the
6057        * game. If they match, display the move and the opponent's response. 
6058        * If they don't match, display an error message.
6059        */
6060       int saveAnimate;
6061       Board testBoard;
6062       CopyBoard(testBoard, boards[currentMove]);
6063       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6064
6065       if (CompareBoards(testBoard, boards[currentMove+1])) {
6066         ForwardInner(currentMove+1);
6067
6068         /* Autoplay the opponent's response.
6069          * if appData.animate was TRUE when Training mode was entered,
6070          * the response will be animated.
6071          */
6072         saveAnimate = appData.animate;
6073         appData.animate = animateTraining;
6074         ForwardInner(currentMove+1);
6075         appData.animate = saveAnimate;
6076
6077         /* check for the end of the game */
6078         if (currentMove >= forwardMostMove) {
6079           gameMode = PlayFromGameFile;
6080           ModeHighlight();
6081           SetTrainingModeOff();
6082           DisplayInformation(_("End of game"));
6083         }
6084       } else {
6085         DisplayError(_("Incorrect move"), 0);
6086       }
6087       return 1;
6088     }
6089
6090   /* Ok, now we know that the move is good, so we can kill
6091      the previous line in Analysis Mode */
6092   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6093                                 && currentMove < forwardMostMove) {
6094     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6095   }
6096
6097   /* If we need the chess program but it's dead, restart it */
6098   ResurrectChessProgram();
6099
6100   /* A user move restarts a paused game*/
6101   if (pausing)
6102     PauseEvent();
6103
6104   thinkOutput[0] = NULLCHAR;
6105
6106   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6107
6108   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6109
6110   if (gameMode == BeginningOfGame) {
6111     if (appData.noChessProgram) {
6112       gameMode = EditGame;
6113       SetGameInfo();
6114     } else {
6115       char buf[MSG_SIZ];
6116       gameMode = MachinePlaysBlack;
6117       StartClocks();
6118       SetGameInfo();
6119       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6120       DisplayTitle(buf);
6121       if (first.sendName) {
6122         sprintf(buf, "name %s\n", gameInfo.white);
6123         SendToProgram(buf, &first);
6124       }
6125       StartClocks();
6126     }
6127     ModeHighlight();
6128   }
6129
6130   /* Relay move to ICS or chess engine */
6131   if (appData.icsActive) {
6132     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6133         gameMode == IcsExamining) {
6134       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6135         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6136         SendToICS("draw ");
6137         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6138       }
6139       // also send plain move, in case ICS does not understand atomic claims
6140       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6141       ics_user_moved = 1;
6142     }
6143   } else {
6144     if (first.sendTime && (gameMode == BeginningOfGame ||
6145                            gameMode == MachinePlaysWhite ||
6146                            gameMode == MachinePlaysBlack)) {
6147       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6148     }
6149     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6150          // [HGM] book: if program might be playing, let it use book
6151         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6152         first.maybeThinking = TRUE;
6153     } else SendMoveToProgram(forwardMostMove-1, &first);
6154     if (currentMove == cmailOldMove + 1) {
6155       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6156     }
6157   }
6158
6159   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6160
6161   switch (gameMode) {
6162   case EditGame:
6163     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6164     case MT_NONE:
6165     case MT_CHECK:
6166       break;
6167     case MT_CHECKMATE:
6168     case MT_STAINMATE:
6169       if (WhiteOnMove(currentMove)) {
6170         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6171       } else {
6172         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6173       }
6174       break;
6175     case MT_STALEMATE:
6176       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6177       break;
6178     }
6179     break;
6180     
6181   case MachinePlaysBlack:
6182   case MachinePlaysWhite:
6183     /* disable certain menu options while machine is thinking */
6184     SetMachineThinkingEnables();
6185     break;
6186
6187   default:
6188     break;
6189   }
6190
6191   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6192         
6193   if(bookHit) { // [HGM] book: simulate book reply
6194         static char bookMove[MSG_SIZ]; // a bit generous?
6195
6196         programStats.nodes = programStats.depth = programStats.time = 
6197         programStats.score = programStats.got_only_move = 0;
6198         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6199
6200         strcpy(bookMove, "move ");
6201         strcat(bookMove, bookHit);
6202         HandleMachineMove(bookMove, &first);
6203   }
6204   return 1;
6205 }
6206
6207 void
6208 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6209      int fromX, fromY, toX, toY;
6210      int promoChar;
6211 {
6212     /* [HGM] This routine was added to allow calling of its two logical
6213        parts from other modules in the old way. Before, UserMoveEvent()
6214        automatically called FinishMove() if the move was OK, and returned
6215        otherwise. I separated the two, in order to make it possible to
6216        slip a promotion popup in between. But that it always needs two
6217        calls, to the first part, (now called UserMoveTest() ), and to
6218        FinishMove if the first part succeeded. Calls that do not need
6219        to do anything in between, can call this routine the old way. 
6220     */
6221     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6222 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6223     if(moveType == AmbiguousMove)
6224         DrawPosition(FALSE, boards[currentMove]);
6225     else if(moveType != ImpossibleMove && moveType != Comment)
6226         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6227 }
6228
6229 void
6230 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6231      Board board;
6232      int flags;
6233      ChessMove kind;
6234      int rf, ff, rt, ft;
6235      VOIDSTAR closure;
6236 {
6237     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6238     Markers *m = (Markers *) closure;
6239     if(rf == fromY && ff == fromX)
6240         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6241                          || kind == WhiteCapturesEnPassant
6242                          || kind == BlackCapturesEnPassant);
6243     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6244 }
6245
6246 void
6247 MarkTargetSquares(int clear)
6248 {
6249   int x, y;
6250   if(!appData.markers || !appData.highlightDragging || 
6251      !appData.testLegality || gameMode == EditPosition) return;
6252   if(clear) {
6253     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6254   } else {
6255     int capt = 0;
6256     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6257     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6258       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6259       if(capt)
6260       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6261     }
6262   }
6263   DrawPosition(TRUE, NULL);
6264 }
6265
6266 void LeftClick(ClickType clickType, int xPix, int yPix)
6267 {
6268     int x, y;
6269     Boolean saveAnimate;
6270     static int second = 0, promotionChoice = 0, dragging = 0;
6271     char promoChoice = NULLCHAR;
6272
6273     if(appData.seekGraph && appData.icsActive && loggedOn &&
6274         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6275         SeekGraphClick(clickType, xPix, yPix, 0);
6276         return;
6277     }
6278
6279     if (clickType == Press) ErrorPopDown();
6280     MarkTargetSquares(1);
6281
6282     x = EventToSquare(xPix, BOARD_WIDTH);
6283     y = EventToSquare(yPix, BOARD_HEIGHT);
6284     if (!flipView && y >= 0) {
6285         y = BOARD_HEIGHT - 1 - y;
6286     }
6287     if (flipView && x >= 0) {
6288         x = BOARD_WIDTH - 1 - x;
6289     }
6290
6291     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6292         if(clickType == Release) return; // ignore upclick of click-click destination
6293         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6294         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6295         if(gameInfo.holdingsWidth && 
6296                 (WhiteOnMove(currentMove) 
6297                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6298                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6299             // click in right holdings, for determining promotion piece
6300             ChessSquare p = boards[currentMove][y][x];
6301             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6302             if(p != EmptySquare) {
6303                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6304                 fromX = fromY = -1;
6305                 return;
6306             }
6307         }
6308         DrawPosition(FALSE, boards[currentMove]);
6309         return;
6310     }
6311
6312     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6313     if(clickType == Press
6314             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6315               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6316               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6317         return;
6318
6319     autoQueen = appData.alwaysPromoteToQueen;
6320
6321     if (fromX == -1) {
6322       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6323         if (clickType == Press) {
6324             /* First square */
6325             if (OKToStartUserMove(x, y)) {
6326                 fromX = x;
6327                 fromY = y;
6328                 second = 0;
6329                 MarkTargetSquares(0);
6330                 DragPieceBegin(xPix, yPix); dragging = 1;
6331                 if (appData.highlightDragging) {
6332                     SetHighlights(x, y, -1, -1);
6333                 }
6334             }
6335         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6336             DragPieceEnd(xPix, yPix); dragging = 0;
6337             DrawPosition(FALSE, NULL);
6338         }
6339         return;
6340       }
6341     }
6342
6343     /* fromX != -1 */
6344     if (clickType == Press && gameMode != EditPosition) {
6345         ChessSquare fromP;
6346         ChessSquare toP;
6347         int frc;
6348
6349         // ignore off-board to clicks
6350         if(y < 0 || x < 0) return;
6351
6352         /* Check if clicking again on the same color piece */
6353         fromP = boards[currentMove][fromY][fromX];
6354         toP = boards[currentMove][y][x];
6355         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6356         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6357              WhitePawn <= toP && toP <= WhiteKing &&
6358              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6359              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6360             (BlackPawn <= fromP && fromP <= BlackKing && 
6361              BlackPawn <= toP && toP <= BlackKing &&
6362              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6363              !(fromP == BlackKing && toP == BlackRook && frc))) {
6364             /* Clicked again on same color piece -- changed his mind */
6365             second = (x == fromX && y == fromY);
6366            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6367             if (appData.highlightDragging) {
6368                 SetHighlights(x, y, -1, -1);
6369             } else {
6370                 ClearHighlights();
6371             }
6372             if (OKToStartUserMove(x, y)) {
6373                 fromX = x;
6374                 fromY = y; dragging = 1;
6375                 MarkTargetSquares(0);
6376                 DragPieceBegin(xPix, yPix);
6377             }
6378             return;
6379            }
6380         }
6381         // ignore clicks on holdings
6382         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6383     }
6384
6385     if (clickType == Release && x == fromX && y == fromY) {
6386         DragPieceEnd(xPix, yPix); dragging = 0;
6387         if (appData.animateDragging) {
6388             /* Undo animation damage if any */
6389             DrawPosition(FALSE, NULL);
6390         }
6391         if (second) {
6392             /* Second up/down in same square; just abort move */
6393             second = 0;
6394             fromX = fromY = -1;
6395             ClearHighlights();
6396             gotPremove = 0;
6397             ClearPremoveHighlights();
6398         } else {
6399             /* First upclick in same square; start click-click mode */
6400             SetHighlights(x, y, -1, -1);
6401         }
6402         return;
6403     }
6404
6405     /* we now have a different from- and (possibly off-board) to-square */
6406     /* Completed move */
6407     toX = x;
6408     toY = y;
6409     saveAnimate = appData.animate;
6410     if (clickType == Press) {
6411         /* Finish clickclick move */
6412         if (appData.animate || appData.highlightLastMove) {
6413             SetHighlights(fromX, fromY, toX, toY);
6414         } else {
6415             ClearHighlights();
6416         }
6417     } else {
6418         /* Finish drag move */
6419         if (appData.highlightLastMove) {
6420             SetHighlights(fromX, fromY, toX, toY);
6421         } else {
6422             ClearHighlights();
6423         }
6424         DragPieceEnd(xPix, yPix); dragging = 0;
6425         /* Don't animate move and drag both */
6426         appData.animate = FALSE;
6427     }
6428
6429     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6430     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6431         ChessSquare piece = boards[currentMove][fromY][fromX];
6432         if(gameMode == EditPosition && piece != EmptySquare &&
6433            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6434             int n;
6435              
6436             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6437                 n = PieceToNumber(piece - (int)BlackPawn);
6438                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6439                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6440                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6441             } else
6442             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6443                 n = PieceToNumber(piece);
6444                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6445                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6446                 boards[currentMove][n][BOARD_WIDTH-2]++;
6447             }
6448             boards[currentMove][fromY][fromX] = EmptySquare;
6449         }
6450         ClearHighlights();
6451         fromX = fromY = -1;
6452         DrawPosition(TRUE, boards[currentMove]);
6453         return;
6454     }
6455
6456     // off-board moves should not be highlighted
6457     if(x < 0 || x < 0) ClearHighlights();
6458
6459     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6460         SetHighlights(fromX, fromY, toX, toY);
6461         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6462             // [HGM] super: promotion to captured piece selected from holdings
6463             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6464             promotionChoice = TRUE;
6465             // kludge follows to temporarily execute move on display, without promoting yet
6466             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6467             boards[currentMove][toY][toX] = p;
6468             DrawPosition(FALSE, boards[currentMove]);
6469             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6470             boards[currentMove][toY][toX] = q;
6471             DisplayMessage("Click in holdings to choose piece", "");
6472             return;
6473         }
6474         PromotionPopUp();
6475     } else {
6476         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6477         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6478         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6479         fromX = fromY = -1;
6480     }
6481     appData.animate = saveAnimate;
6482     if (appData.animate || appData.animateDragging) {
6483         /* Undo animation damage if needed */
6484         DrawPosition(FALSE, NULL);
6485     }
6486 }
6487
6488 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6489 {   // front-end-free part taken out of PieceMenuPopup
6490     int whichMenu; int xSqr, ySqr;
6491
6492     if(seekGraphUp) { // [HGM] seekgraph
6493         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6494         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6495         return -2;
6496     }
6497
6498     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6499          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6500         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6501         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6502         if(action == Press)   {
6503             originalFlip = flipView;
6504             flipView = !flipView; // temporarily flip board to see game from partners perspective
6505             DrawPosition(TRUE, partnerBoard);
6506             DisplayMessage(partnerStatus, "");
6507             partnerUp = TRUE;
6508         } else if(action == Release) {
6509             flipView = originalFlip;
6510             DrawPosition(TRUE, boards[currentMove]);
6511             partnerUp = FALSE;
6512         }
6513         return -2;
6514     }
6515
6516     xSqr = EventToSquare(x, BOARD_WIDTH);
6517     ySqr = EventToSquare(y, BOARD_HEIGHT);
6518     if (action == Release) UnLoadPV(); // [HGM] pv
6519     if (action != Press) return -2; // return code to be ignored
6520     switch (gameMode) {
6521       case IcsExamining:
6522         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6523       case EditPosition:
6524         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6525         if (xSqr < 0 || ySqr < 0) return -1;\r
6526         whichMenu = 0; // edit-position menu
6527         break;
6528       case IcsObserving:
6529         if(!appData.icsEngineAnalyze) return -1;
6530       case IcsPlayingWhite:
6531       case IcsPlayingBlack:
6532         if(!appData.zippyPlay) goto noZip;
6533       case AnalyzeMode:
6534       case AnalyzeFile:
6535       case MachinePlaysWhite:
6536       case MachinePlaysBlack:
6537       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6538         if (!appData.dropMenu) {
6539           LoadPV(x, y);
6540           return 2; // flag front-end to grab mouse events
6541         }
6542         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6543            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6544       case EditGame:
6545       noZip:
6546         if (xSqr < 0 || ySqr < 0) return -1;
6547         if (!appData.dropMenu || appData.testLegality &&
6548             gameInfo.variant != VariantBughouse &&
6549             gameInfo.variant != VariantCrazyhouse) return -1;
6550         whichMenu = 1; // drop menu
6551         break;
6552       default:
6553         return -1;
6554     }
6555
6556     if (((*fromX = xSqr) < 0) ||
6557         ((*fromY = ySqr) < 0)) {
6558         *fromX = *fromY = -1;
6559         return -1;
6560     }
6561     if (flipView)
6562       *fromX = BOARD_WIDTH - 1 - *fromX;
6563     else
6564       *fromY = BOARD_HEIGHT - 1 - *fromY;
6565
6566     return whichMenu;
6567 }
6568
6569 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6570 {
6571 //    char * hint = lastHint;
6572     FrontEndProgramStats stats;
6573
6574     stats.which = cps == &first ? 0 : 1;
6575     stats.depth = cpstats->depth;
6576     stats.nodes = cpstats->nodes;
6577     stats.score = cpstats->score;
6578     stats.time = cpstats->time;
6579     stats.pv = cpstats->movelist;
6580     stats.hint = lastHint;
6581     stats.an_move_index = 0;
6582     stats.an_move_count = 0;
6583
6584     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6585         stats.hint = cpstats->move_name;
6586         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6587         stats.an_move_count = cpstats->nr_moves;
6588     }
6589
6590     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6591
6592     SetProgramStats( &stats );
6593 }
6594
6595 void
6596 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6597 {       // count all piece types
6598         int p, f, r;
6599         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6600         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6601         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6602                 p = board[r][f];
6603                 pCnt[p]++;
6604                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6605                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6606                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6607                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6608                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6609                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6610         }
6611 }
6612
6613 int
6614 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6615 {
6616         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6617         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6618                    
6619         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6620         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6621         if(myPawns == 2 && nMine == 3) // KPP
6622             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6623         if(myPawns == 1 && nMine == 2) // KP
6624             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6625         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6626             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6627         if(myPawns) return FALSE;
6628         if(pCnt[WhiteRook+side])
6629             return pCnt[BlackRook-side] || 
6630                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6631                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6632                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6633         if(pCnt[WhiteCannon+side]) {
6634             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6635             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6636         }
6637         if(pCnt[WhiteKnight+side])
6638             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6639         return FALSE;
6640 }
6641
6642 int
6643 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6644 {
6645         VariantClass v = gameInfo.variant;
6646
6647         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6648         if(v == VariantShatranj) return TRUE; // always winnable through baring
6649         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6650         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6651
6652         if(v == VariantXiangqi) {
6653                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6654
6655                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6656                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6657                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6658                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6659                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6660                 if(stale) // we have at least one last-rank P plus perhaps C
6661                     return majors // KPKX
6662                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6663                 else // KCA*E*
6664                     return pCnt[WhiteFerz+side] // KCAK
6665                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6666                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6667                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6668
6669         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6670                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6671                 
6672                 if(nMine == 1) return FALSE; // bare King
6673                 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
6674                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6675                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6676                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6677                 if(pCnt[WhiteKnight+side])
6678                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6679                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6680                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6681                 if(nBishops)
6682                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6683                 if(pCnt[WhiteAlfil+side])
6684                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6685                 if(pCnt[WhiteWazir+side])
6686                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6687         }
6688
6689         return TRUE;
6690 }
6691
6692 int
6693 Adjudicate(ChessProgramState *cps)
6694 {       // [HGM] some adjudications useful with buggy engines
6695         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6696         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6697         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6698         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6699         int k, count = 0; static int bare = 1;
6700         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6701         Boolean canAdjudicate = !appData.icsActive;
6702
6703         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6704         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6705             if( appData.testLegality )
6706             {   /* [HGM] Some more adjudications for obstinate engines */
6707                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6708                 static int moveCount = 6;
6709                 ChessMove result;
6710                 char *reason = NULL;
6711
6712                 /* Count what is on board. */
6713                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6714
6715                 /* Some material-based adjudications that have to be made before stalemate test */
6716                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6717                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6718                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6719                      if(canAdjudicate && appData.checkMates) {
6720                          if(engineOpponent)
6721                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6722                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6723                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6724                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6725                          return 1;
6726                      }
6727                 }
6728
6729                 /* Bare King in Shatranj (loses) or Losers (wins) */
6730                 if( nrW == 1 || nrB == 1) {
6731                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6732                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6733                      if(canAdjudicate && appData.checkMates) {
6734                          if(engineOpponent)
6735                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6736                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6737                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6738                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6739                          return 1;
6740                      }
6741                   } else
6742                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6743                   {    /* bare King */
6744                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6745                         if(canAdjudicate && appData.checkMates) {
6746                             /* but only adjudicate if adjudication enabled */
6747                             if(engineOpponent)
6748                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6749                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6751                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6752                             return 1;
6753                         }
6754                   }
6755                 } else bare = 1;
6756
6757
6758             // don't wait for engine to announce game end if we can judge ourselves
6759             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6760               case MT_CHECK:
6761                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6762                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6763                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6764                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6765                             checkCnt++;
6766                         if(checkCnt >= 2) {
6767                             reason = "Xboard adjudication: 3rd check";
6768                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6769                             break;
6770                         }
6771                     }
6772                 }
6773               case MT_NONE:
6774               default:
6775                 break;
6776               case MT_STALEMATE:
6777               case MT_STAINMATE:
6778                 reason = "Xboard adjudication: Stalemate";
6779                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6780                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6781                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6782                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6783                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6784                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6785                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6786                                                                         EP_CHECKMATE : EP_WINS);
6787                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6788                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6789                 }
6790                 break;
6791               case MT_CHECKMATE:
6792                 reason = "Xboard adjudication: Checkmate";
6793                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6794                 break;
6795             }
6796
6797                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6798                     case EP_STALEMATE:
6799                         result = GameIsDrawn; break;
6800                     case EP_CHECKMATE:
6801                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6802                     case EP_WINS:
6803                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6804                     default:
6805                         result = (ChessMove) 0;
6806                 }
6807                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6808                     if(engineOpponent)
6809                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6810                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6811                     GameEnds( result, reason, GE_XBOARD );
6812                     return 1;
6813                 }
6814
6815                 /* Next absolutely insufficient mating material. */
6816                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6817                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6818                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6819
6820                      /* always flag draws, for judging claims */
6821                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6822
6823                      if(canAdjudicate && appData.materialDraws) {
6824                          /* but only adjudicate them if adjudication enabled */
6825                          if(engineOpponent) {
6826                            SendToProgram("force\n", engineOpponent); // suppress reply
6827                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6828                          }
6829                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6830                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6831                          return 1;
6832                      }
6833                 }
6834
6835                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6836                 if(gameInfo.variant == VariantXiangqi ?
6837                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6838                  : nrW + nrB == 4 && 
6839                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6840                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6841                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6842                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6843                    ) ) {
6844                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6845                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6846                           if(engineOpponent) {
6847                             SendToProgram("force\n", engineOpponent); // suppress reply
6848                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6849                           }
6850                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6851                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6852                           return 1;
6853                      }
6854                 } else moveCount = 6;
6855             }
6856         }
6857           
6858         if (appData.debugMode) { int i;
6859             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6860                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6861                     appData.drawRepeats);
6862             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6863               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6864             
6865         }
6866
6867         // Repetition draws and 50-move rule can be applied independently of legality testing
6868
6869                 /* Check for rep-draws */
6870                 count = 0;
6871                 for(k = forwardMostMove-2;
6872                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6873                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6874                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6875                     k-=2)
6876                 {   int rights=0;
6877                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6878                         /* compare castling rights */
6879                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6880                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6881                                 rights++; /* King lost rights, while rook still had them */
6882                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6883                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6884                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6885                                    rights++; /* but at least one rook lost them */
6886                         }
6887                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6888                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6889                                 rights++; 
6890                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6891                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6892                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6893                                    rights++;
6894                         }
6895                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6896                             && appData.drawRepeats > 1) {
6897                              /* adjudicate after user-specified nr of repeats */
6898                              int result = GameIsDrawn;
6899                              char *details = "XBoard adjudication: repetition draw";
6900                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6901                                 // [HGM] xiangqi: check for forbidden perpetuals
6902                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6903                                 for(m=forwardMostMove; m>k; m-=2) {
6904                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6905                                         ourPerpetual = 0; // the current mover did not always check
6906                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6907                                         hisPerpetual = 0; // the opponent did not always check
6908                                 }
6909                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6910                                                                         ourPerpetual, hisPerpetual);
6911                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6912                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6913                                     details = "Xboard adjudication: perpetual checking";
6914                                 } else
6915                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6916                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6917                                 } else
6918                                 // Now check for perpetual chases
6919                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6920                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6921                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6922                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6923                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6924                                         details = "Xboard adjudication: perpetual chasing";
6925                                     } else
6926                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6927                                         break; // Abort repetition-checking loop.
6928                                 }
6929                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6930                              }
6931                              if(engineOpponent) {
6932                                SendToProgram("force\n", engineOpponent); // suppress reply
6933                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6934                              }
6935                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936                              GameEnds( result, details, GE_XBOARD );
6937                              return 1;
6938                         }
6939                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6940                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6941                     }
6942                 }
6943
6944                 /* Now we test for 50-move draws. Determine ply count */
6945                 count = forwardMostMove;
6946                 /* look for last irreversble move */
6947                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6948                     count--;
6949                 /* if we hit starting position, add initial plies */
6950                 if( count == backwardMostMove )
6951                     count -= initialRulePlies;
6952                 count = forwardMostMove - count; 
6953                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6954                         // adjust reversible move counter for checks in Xiangqi
6955                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6956                         if(i < backwardMostMove) i = backwardMostMove;
6957                         while(i <= forwardMostMove) {
6958                                 lastCheck = inCheck; // check evasion does not count
6959                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6960                                 if(inCheck || lastCheck) count--; // check does not count
6961                                 i++;
6962                         }
6963                 }
6964                 if( count >= 100)
6965                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6966                          /* this is used to judge if draw claims are legal */
6967                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6968                          if(engineOpponent) {
6969                            SendToProgram("force\n", engineOpponent); // suppress reply
6970                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6971                          }
6972                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6973                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6974                          return 1;
6975                 }
6976
6977                 /* if draw offer is pending, treat it as a draw claim
6978                  * when draw condition present, to allow engines a way to
6979                  * claim draws before making their move to avoid a race
6980                  * condition occurring after their move
6981                  */
6982                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6983                          char *p = NULL;
6984                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6985                              p = "Draw claim: 50-move rule";
6986                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6987                              p = "Draw claim: 3-fold repetition";
6988                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6989                              p = "Draw claim: insufficient mating material";
6990                          if( p != NULL && canAdjudicate) {
6991                              if(engineOpponent) {
6992                                SendToProgram("force\n", engineOpponent); // suppress reply
6993                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6994                              }
6995                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6996                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6997                              return 1;
6998                          }
6999                 }
7000
7001                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7002                     if(engineOpponent) {
7003                       SendToProgram("force\n", engineOpponent); // suppress reply
7004                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7005                     }
7006                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7007                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7008                     return 1;
7009                 }
7010         return 0;
7011 }
7012
7013 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7014 {   // [HGM] book: this routine intercepts moves to simulate book replies
7015     char *bookHit = NULL;
7016
7017     //first determine if the incoming move brings opponent into his book
7018     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7019         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7020     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7021     if(bookHit != NULL && !cps->bookSuspend) {
7022         // make sure opponent is not going to reply after receiving move to book position
7023         SendToProgram("force\n", cps);
7024         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7025     }
7026     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7027     // now arrange restart after book miss
7028     if(bookHit) {
7029         // after a book hit we never send 'go', and the code after the call to this routine
7030         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7031         char buf[MSG_SIZ];
7032         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7033         SendToProgram(buf, cps);
7034         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7035     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7036         SendToProgram("go\n", cps);
7037         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7038     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7039         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7040             SendToProgram("go\n", cps); 
7041         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7042     }
7043     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7044 }
7045
7046 char *savedMessage;
7047 ChessProgramState *savedState;
7048 void DeferredBookMove(void)
7049 {
7050         if(savedState->lastPing != savedState->lastPong)
7051                     ScheduleDelayedEvent(DeferredBookMove, 10);
7052         else
7053         HandleMachineMove(savedMessage, savedState);
7054 }
7055
7056 void
7057 HandleMachineMove(message, cps)
7058      char *message;
7059      ChessProgramState *cps;
7060 {
7061     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7062     char realname[MSG_SIZ];
7063     int fromX, fromY, toX, toY;
7064     ChessMove moveType;
7065     char promoChar;
7066     char *p;
7067     int machineWhite;
7068     char *bookHit;
7069
7070     cps->userError = 0;
7071
7072 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7073     /*
7074      * Kludge to ignore BEL characters
7075      */
7076     while (*message == '\007') message++;
7077
7078     /*
7079      * [HGM] engine debug message: ignore lines starting with '#' character
7080      */
7081     if(cps->debug && *message == '#') return;
7082
7083     /*
7084      * Look for book output
7085      */
7086     if (cps == &first && bookRequested) {
7087         if (message[0] == '\t' || message[0] == ' ') {
7088             /* Part of the book output is here; append it */
7089             strcat(bookOutput, message);
7090             strcat(bookOutput, "  \n");
7091             return;
7092         } else if (bookOutput[0] != NULLCHAR) {
7093             /* All of book output has arrived; display it */
7094             char *p = bookOutput;
7095             while (*p != NULLCHAR) {
7096                 if (*p == '\t') *p = ' ';
7097                 p++;
7098             }
7099             DisplayInformation(bookOutput);
7100             bookRequested = FALSE;
7101             /* Fall through to parse the current output */
7102         }
7103     }
7104
7105     /*
7106      * Look for machine move.
7107      */
7108     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7109         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7110     {
7111         /* This method is only useful on engines that support ping */
7112         if (cps->lastPing != cps->lastPong) {
7113           if (gameMode == BeginningOfGame) {
7114             /* Extra move from before last new; ignore */
7115             if (appData.debugMode) {
7116                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7117             }
7118           } else {
7119             if (appData.debugMode) {
7120                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7121                         cps->which, gameMode);
7122             }
7123
7124             SendToProgram("undo\n", cps);
7125           }
7126           return;
7127         }
7128
7129         switch (gameMode) {
7130           case BeginningOfGame:
7131             /* Extra move from before last reset; ignore */
7132             if (appData.debugMode) {
7133                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7134             }
7135             return;
7136
7137           case EndOfGame:
7138           case IcsIdle:
7139           default:
7140             /* Extra move after we tried to stop.  The mode test is
7141                not a reliable way of detecting this problem, but it's
7142                the best we can do on engines that don't support ping.
7143             */
7144             if (appData.debugMode) {
7145                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7146                         cps->which, gameMode);
7147             }
7148             SendToProgram("undo\n", cps);
7149             return;
7150
7151           case MachinePlaysWhite:
7152           case IcsPlayingWhite:
7153             machineWhite = TRUE;
7154             break;
7155
7156           case MachinePlaysBlack:
7157           case IcsPlayingBlack:
7158             machineWhite = FALSE;
7159             break;
7160
7161           case TwoMachinesPlay:
7162             machineWhite = (cps->twoMachinesColor[0] == 'w');
7163             break;
7164         }
7165         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7166             if (appData.debugMode) {
7167                 fprintf(debugFP,
7168                         "Ignoring move out of turn by %s, gameMode %d"
7169                         ", forwardMost %d\n",
7170                         cps->which, gameMode, forwardMostMove);
7171             }
7172             return;
7173         }
7174
7175     if (appData.debugMode) { int f = forwardMostMove;
7176         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7177                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7178                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7179     }
7180         if(cps->alphaRank) AlphaRank(machineMove, 4);
7181         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7182                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7183             /* Machine move could not be parsed; ignore it. */
7184             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7185                     machineMove, cps->which);
7186             DisplayError(buf1, 0);
7187             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7188                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7189             if (gameMode == TwoMachinesPlay) {
7190               GameEnds(machineWhite ? BlackWins : WhiteWins,
7191                        buf1, GE_XBOARD);
7192             }
7193             return;
7194         }
7195
7196         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7197         /* So we have to redo legality test with true e.p. status here,  */
7198         /* to make sure an illegal e.p. capture does not slip through,   */
7199         /* to cause a forfeit on a justified illegal-move complaint      */
7200         /* of the opponent.                                              */
7201         if( gameMode==TwoMachinesPlay && appData.testLegality
7202             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7203                                                               ) {
7204            ChessMove moveType;
7205            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7206                              fromY, fromX, toY, toX, promoChar);
7207             if (appData.debugMode) {
7208                 int i;
7209                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7210                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7211                 fprintf(debugFP, "castling rights\n");
7212             }
7213             if(moveType == IllegalMove) {
7214                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7215                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7216                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7217                            buf1, GE_XBOARD);
7218                 return;
7219            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7220            /* [HGM] Kludge to handle engines that send FRC-style castling
7221               when they shouldn't (like TSCP-Gothic) */
7222            switch(moveType) {
7223              case WhiteASideCastleFR:
7224              case BlackASideCastleFR:
7225                toX+=2;
7226                currentMoveString[2]++;
7227                break;
7228              case WhiteHSideCastleFR:
7229              case BlackHSideCastleFR:
7230                toX--;
7231                currentMoveString[2]--;
7232                break;
7233              default: ; // nothing to do, but suppresses warning of pedantic compilers
7234            }
7235         }
7236         hintRequested = FALSE;
7237         lastHint[0] = NULLCHAR;
7238         bookRequested = FALSE;
7239         /* Program may be pondering now */
7240         cps->maybeThinking = TRUE;
7241         if (cps->sendTime == 2) cps->sendTime = 1;
7242         if (cps->offeredDraw) cps->offeredDraw--;
7243
7244         /* currentMoveString is set as a side-effect of ParseOneMove */
7245         strcpy(machineMove, currentMoveString);
7246         strcat(machineMove, "\n");
7247         strcpy(moveList[forwardMostMove], machineMove);
7248
7249         /* [AS] Save move info*/
7250         pvInfoList[ forwardMostMove ].score = programStats.score;
7251         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7252         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7253
7254         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7255
7256         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7257         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7258             int count = 0;
7259
7260             while( count < adjudicateLossPlies ) {
7261                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7262
7263                 if( count & 1 ) {
7264                     score = -score; /* Flip score for winning side */
7265                 }
7266
7267                 if( score > adjudicateLossThreshold ) {
7268                     break;
7269                 }
7270
7271                 count++;
7272             }
7273
7274             if( count >= adjudicateLossPlies ) {
7275                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7276
7277                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7278                     "Xboard adjudication", 
7279                     GE_XBOARD );
7280
7281                 return;
7282             }
7283         }
7284
7285         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7286
7287 #if ZIPPY
7288         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7289             first.initDone) {
7290           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7291                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7292                 SendToICS("draw ");
7293                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7294           }
7295           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7296           ics_user_moved = 1;
7297           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7298                 char buf[3*MSG_SIZ];
7299
7300                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7301                         programStats.score / 100.,
7302                         programStats.depth,
7303                         programStats.time / 100.,
7304                         (unsigned int)programStats.nodes,
7305                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7306                         programStats.movelist);
7307                 SendToICS(buf);
7308 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7309           }
7310         }
7311 #endif
7312
7313         /* [AS] Clear stats for next move */
7314         ClearProgramStats();
7315         thinkOutput[0] = NULLCHAR;
7316         hiddenThinkOutputState = 0;
7317
7318         bookHit = NULL;
7319         if (gameMode == TwoMachinesPlay) {
7320             /* [HGM] relaying draw offers moved to after reception of move */
7321             /* and interpreting offer as claim if it brings draw condition */
7322             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7323                 SendToProgram("draw\n", cps->other);
7324             }
7325             if (cps->other->sendTime) {
7326                 SendTimeRemaining(cps->other,
7327                                   cps->other->twoMachinesColor[0] == 'w');
7328             }
7329             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7330             if (firstMove && !bookHit) {
7331                 firstMove = FALSE;
7332                 if (cps->other->useColors) {
7333                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7334                 }
7335                 SendToProgram("go\n", cps->other);
7336             }
7337             cps->other->maybeThinking = TRUE;
7338         }
7339
7340         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7341         
7342         if (!pausing && appData.ringBellAfterMoves) {
7343             RingBell();
7344         }
7345
7346         /* 
7347          * Reenable menu items that were disabled while
7348          * machine was thinking
7349          */
7350         if (gameMode != TwoMachinesPlay)
7351             SetUserThinkingEnables();
7352
7353         // [HGM] book: after book hit opponent has received move and is now in force mode
7354         // force the book reply into it, and then fake that it outputted this move by jumping
7355         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7356         if(bookHit) {
7357                 static char bookMove[MSG_SIZ]; // a bit generous?
7358
7359                 strcpy(bookMove, "move ");
7360                 strcat(bookMove, bookHit);
7361                 message = bookMove;
7362                 cps = cps->other;
7363                 programStats.nodes = programStats.depth = programStats.time = 
7364                 programStats.score = programStats.got_only_move = 0;
7365                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7366
7367                 if(cps->lastPing != cps->lastPong) {
7368                     savedMessage = message; // args for deferred call
7369                     savedState = cps;
7370                     ScheduleDelayedEvent(DeferredBookMove, 10);
7371                     return;
7372                 }
7373                 goto FakeBookMove;
7374         }
7375
7376         return;
7377     }
7378
7379     /* Set special modes for chess engines.  Later something general
7380      *  could be added here; for now there is just one kludge feature,
7381      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7382      *  when "xboard" is given as an interactive command.
7383      */
7384     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7385         cps->useSigint = FALSE;
7386         cps->useSigterm = FALSE;
7387     }
7388     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7389       ParseFeatures(message+8, cps);
7390       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7391     }
7392
7393     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7394      * want this, I was asked to put it in, and obliged.
7395      */
7396     if (!strncmp(message, "setboard ", 9)) {
7397         Board initial_position;
7398
7399         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7400
7401         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7402             DisplayError(_("Bad FEN received from engine"), 0);
7403             return ;
7404         } else {
7405            Reset(TRUE, FALSE);
7406            CopyBoard(boards[0], initial_position);
7407            initialRulePlies = FENrulePlies;
7408            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7409            else gameMode = MachinePlaysBlack;                 
7410            DrawPosition(FALSE, boards[currentMove]);
7411         }
7412         return;
7413     }
7414
7415     /*
7416      * Look for communication commands
7417      */
7418     if (!strncmp(message, "telluser ", 9)) {
7419         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7420         DisplayNote(message + 9);
7421         return;
7422     }
7423     if (!strncmp(message, "tellusererror ", 14)) {
7424         cps->userError = 1;
7425         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7426         DisplayError(message + 14, 0);
7427         return;
7428     }
7429     if (!strncmp(message, "tellopponent ", 13)) {
7430       if (appData.icsActive) {
7431         if (loggedOn) {
7432           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7433           SendToICS(buf1);
7434         }
7435       } else {
7436         DisplayNote(message + 13);
7437       }
7438       return;
7439     }
7440     if (!strncmp(message, "tellothers ", 11)) {
7441       if (appData.icsActive) {
7442         if (loggedOn) {
7443           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7444           SendToICS(buf1);
7445         }
7446       }
7447       return;
7448     }
7449     if (!strncmp(message, "tellall ", 8)) {
7450       if (appData.icsActive) {
7451         if (loggedOn) {
7452           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7453           SendToICS(buf1);
7454         }
7455       } else {
7456         DisplayNote(message + 8);
7457       }
7458       return;
7459     }
7460     if (strncmp(message, "warning", 7) == 0) {
7461         /* Undocumented feature, use tellusererror in new code */
7462         DisplayError(message, 0);
7463         return;
7464     }
7465     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7466         strcpy(realname, cps->tidy);
7467         strcat(realname, " query");
7468         AskQuestion(realname, buf2, buf1, cps->pr);
7469         return;
7470     }
7471     /* Commands from the engine directly to ICS.  We don't allow these to be 
7472      *  sent until we are logged on. Crafty kibitzes have been known to 
7473      *  interfere with the login process.
7474      */
7475     if (loggedOn) {
7476         if (!strncmp(message, "tellics ", 8)) {
7477             SendToICS(message + 8);
7478             SendToICS("\n");
7479             return;
7480         }
7481         if (!strncmp(message, "tellicsnoalias ", 15)) {
7482             SendToICS(ics_prefix);
7483             SendToICS(message + 15);
7484             SendToICS("\n");
7485             return;
7486         }
7487         /* The following are for backward compatibility only */
7488         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7489             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7490             SendToICS(ics_prefix);
7491             SendToICS(message);
7492             SendToICS("\n");
7493             return;
7494         }
7495     }
7496     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7497         return;
7498     }
7499     /*
7500      * If the move is illegal, cancel it and redraw the board.
7501      * Also deal with other error cases.  Matching is rather loose
7502      * here to accommodate engines written before the spec.
7503      */
7504     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7505         strncmp(message, "Error", 5) == 0) {
7506         if (StrStr(message, "name") || 
7507             StrStr(message, "rating") || StrStr(message, "?") ||
7508             StrStr(message, "result") || StrStr(message, "board") ||
7509             StrStr(message, "bk") || StrStr(message, "computer") ||
7510             StrStr(message, "variant") || StrStr(message, "hint") ||
7511             StrStr(message, "random") || StrStr(message, "depth") ||
7512             StrStr(message, "accepted")) {
7513             return;
7514         }
7515         if (StrStr(message, "protover")) {
7516           /* Program is responding to input, so it's apparently done
7517              initializing, and this error message indicates it is
7518              protocol version 1.  So we don't need to wait any longer
7519              for it to initialize and send feature commands. */
7520           FeatureDone(cps, 1);
7521           cps->protocolVersion = 1;
7522           return;
7523         }
7524         cps->maybeThinking = FALSE;
7525
7526         if (StrStr(message, "draw")) {
7527             /* Program doesn't have "draw" command */
7528             cps->sendDrawOffers = 0;
7529             return;
7530         }
7531         if (cps->sendTime != 1 &&
7532             (StrStr(message, "time") || StrStr(message, "otim"))) {
7533           /* Program apparently doesn't have "time" or "otim" command */
7534           cps->sendTime = 0;
7535           return;
7536         }
7537         if (StrStr(message, "analyze")) {
7538             cps->analysisSupport = FALSE;
7539             cps->analyzing = FALSE;
7540             Reset(FALSE, TRUE);
7541             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7542             DisplayError(buf2, 0);
7543             return;
7544         }
7545         if (StrStr(message, "(no matching move)st")) {
7546           /* Special kludge for GNU Chess 4 only */
7547           cps->stKludge = TRUE;
7548           SendTimeControl(cps, movesPerSession, timeControl,
7549                           timeIncrement, appData.searchDepth,
7550                           searchTime);
7551           return;
7552         }
7553         if (StrStr(message, "(no matching move)sd")) {
7554           /* Special kludge for GNU Chess 4 only */
7555           cps->sdKludge = TRUE;
7556           SendTimeControl(cps, movesPerSession, timeControl,
7557                           timeIncrement, appData.searchDepth,
7558                           searchTime);
7559           return;
7560         }
7561         if (!StrStr(message, "llegal")) {
7562             return;
7563         }
7564         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7565             gameMode == IcsIdle) return;
7566         if (forwardMostMove <= backwardMostMove) return;
7567         if (pausing) PauseEvent();
7568       if(appData.forceIllegal) {
7569             // [HGM] illegal: machine refused move; force position after move into it
7570           SendToProgram("force\n", cps);
7571           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7572                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7573                 // when black is to move, while there might be nothing on a2 or black
7574                 // might already have the move. So send the board as if white has the move.
7575                 // But first we must change the stm of the engine, as it refused the last move
7576                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7577                 if(WhiteOnMove(forwardMostMove)) {
7578                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7579                     SendBoard(cps, forwardMostMove); // kludgeless board
7580                 } else {
7581                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7582                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7583                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7584                 }
7585           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7586             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7587                  gameMode == TwoMachinesPlay)
7588               SendToProgram("go\n", cps);
7589             return;
7590       } else
7591         if (gameMode == PlayFromGameFile) {
7592             /* Stop reading this game file */
7593             gameMode = EditGame;
7594             ModeHighlight();
7595         }
7596         currentMove = forwardMostMove-1;
7597         DisplayMove(currentMove-1); /* before DisplayMoveError */
7598         SwitchClocks(forwardMostMove-1); // [HGM] race
7599         DisplayBothClocks();
7600         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7601                 parseList[currentMove], cps->which);
7602         DisplayMoveError(buf1);
7603         DrawPosition(FALSE, boards[currentMove]);
7604
7605         /* [HGM] illegal-move claim should forfeit game when Xboard */
7606         /* only passes fully legal moves                            */
7607         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7608             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7609                                 "False illegal-move claim", GE_XBOARD );
7610         }
7611         return;
7612     }
7613     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7614         /* Program has a broken "time" command that
7615            outputs a string not ending in newline.
7616            Don't use it. */
7617         cps->sendTime = 0;
7618     }
7619     
7620     /*
7621      * If chess program startup fails, exit with an error message.
7622      * Attempts to recover here are futile.
7623      */
7624     if ((StrStr(message, "unknown host") != NULL)
7625         || (StrStr(message, "No remote directory") != NULL)
7626         || (StrStr(message, "not found") != NULL)
7627         || (StrStr(message, "No such file") != NULL)
7628         || (StrStr(message, "can't alloc") != NULL)
7629         || (StrStr(message, "Permission denied") != NULL)) {
7630
7631         cps->maybeThinking = FALSE;
7632         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7633                 cps->which, cps->program, cps->host, message);
7634         RemoveInputSource(cps->isr);
7635         DisplayFatalError(buf1, 0, 1);
7636         return;
7637     }
7638     
7639     /* 
7640      * Look for hint output
7641      */
7642     if (sscanf(message, "Hint: %s", buf1) == 1) {
7643         if (cps == &first && hintRequested) {
7644             hintRequested = FALSE;
7645             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7646                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7647                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7648                                     PosFlags(forwardMostMove),
7649                                     fromY, fromX, toY, toX, promoChar, buf1);
7650                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7651                 DisplayInformation(buf2);
7652             } else {
7653                 /* Hint move could not be parsed!? */
7654               snprintf(buf2, sizeof(buf2),
7655                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7656                         buf1, cps->which);
7657                 DisplayError(buf2, 0);
7658             }
7659         } else {
7660             strcpy(lastHint, buf1);
7661         }
7662         return;
7663     }
7664
7665     /*
7666      * Ignore other messages if game is not in progress
7667      */
7668     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7669         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7670
7671     /*
7672      * look for win, lose, draw, or draw offer
7673      */
7674     if (strncmp(message, "1-0", 3) == 0) {
7675         char *p, *q, *r = "";
7676         p = strchr(message, '{');
7677         if (p) {
7678             q = strchr(p, '}');
7679             if (q) {
7680                 *q = NULLCHAR;
7681                 r = p + 1;
7682             }
7683         }
7684         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7685         return;
7686     } else if (strncmp(message, "0-1", 3) == 0) {
7687         char *p, *q, *r = "";
7688         p = strchr(message, '{');
7689         if (p) {
7690             q = strchr(p, '}');
7691             if (q) {
7692                 *q = NULLCHAR;
7693                 r = p + 1;
7694             }
7695         }
7696         /* Kludge for Arasan 4.1 bug */
7697         if (strcmp(r, "Black resigns") == 0) {
7698             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7699             return;
7700         }
7701         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7702         return;
7703     } else if (strncmp(message, "1/2", 3) == 0) {
7704         char *p, *q, *r = "";
7705         p = strchr(message, '{');
7706         if (p) {
7707             q = strchr(p, '}');
7708             if (q) {
7709                 *q = NULLCHAR;
7710                 r = p + 1;
7711             }
7712         }
7713             
7714         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7715         return;
7716
7717     } else if (strncmp(message, "White resign", 12) == 0) {
7718         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7719         return;
7720     } else if (strncmp(message, "Black resign", 12) == 0) {
7721         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7722         return;
7723     } else if (strncmp(message, "White matches", 13) == 0 ||
7724                strncmp(message, "Black matches", 13) == 0   ) {
7725         /* [HGM] ignore GNUShogi noises */
7726         return;
7727     } else if (strncmp(message, "White", 5) == 0 &&
7728                message[5] != '(' &&
7729                StrStr(message, "Black") == NULL) {
7730         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7731         return;
7732     } else if (strncmp(message, "Black", 5) == 0 &&
7733                message[5] != '(') {
7734         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7735         return;
7736     } else if (strcmp(message, "resign") == 0 ||
7737                strcmp(message, "computer resigns") == 0) {
7738         switch (gameMode) {
7739           case MachinePlaysBlack:
7740           case IcsPlayingBlack:
7741             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7742             break;
7743           case MachinePlaysWhite:
7744           case IcsPlayingWhite:
7745             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7746             break;
7747           case TwoMachinesPlay:
7748             if (cps->twoMachinesColor[0] == 'w')
7749               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7750             else
7751               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7752             break;
7753           default:
7754             /* can't happen */
7755             break;
7756         }
7757         return;
7758     } else if (strncmp(message, "opponent mates", 14) == 0) {
7759         switch (gameMode) {
7760           case MachinePlaysBlack:
7761           case IcsPlayingBlack:
7762             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7763             break;
7764           case MachinePlaysWhite:
7765           case IcsPlayingWhite:
7766             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7767             break;
7768           case TwoMachinesPlay:
7769             if (cps->twoMachinesColor[0] == 'w')
7770               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7771             else
7772               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7773             break;
7774           default:
7775             /* can't happen */
7776             break;
7777         }
7778         return;
7779     } else if (strncmp(message, "computer mates", 14) == 0) {
7780         switch (gameMode) {
7781           case MachinePlaysBlack:
7782           case IcsPlayingBlack:
7783             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7784             break;
7785           case MachinePlaysWhite:
7786           case IcsPlayingWhite:
7787             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7788             break;
7789           case TwoMachinesPlay:
7790             if (cps->twoMachinesColor[0] == 'w')
7791               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7792             else
7793               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7794             break;
7795           default:
7796             /* can't happen */
7797             break;
7798         }
7799         return;
7800     } else if (strncmp(message, "checkmate", 9) == 0) {
7801         if (WhiteOnMove(forwardMostMove)) {
7802             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7803         } else {
7804             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7805         }
7806         return;
7807     } else if (strstr(message, "Draw") != NULL ||
7808                strstr(message, "game is a draw") != NULL) {
7809         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7810         return;
7811     } else if (strstr(message, "offer") != NULL &&
7812                strstr(message, "draw") != NULL) {
7813 #if ZIPPY
7814         if (appData.zippyPlay && first.initDone) {
7815             /* Relay offer to ICS */
7816             SendToICS(ics_prefix);
7817             SendToICS("draw\n");
7818         }
7819 #endif
7820         cps->offeredDraw = 2; /* valid until this engine moves twice */
7821         if (gameMode == TwoMachinesPlay) {
7822             if (cps->other->offeredDraw) {
7823                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7824             /* [HGM] in two-machine mode we delay relaying draw offer      */
7825             /* until after we also have move, to see if it is really claim */
7826             }
7827         } else if (gameMode == MachinePlaysWhite ||
7828                    gameMode == MachinePlaysBlack) {
7829           if (userOfferedDraw) {
7830             DisplayInformation(_("Machine accepts your draw offer"));
7831             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7832           } else {
7833             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7834           }
7835         }
7836     }
7837
7838     
7839     /*
7840      * Look for thinking output
7841      */
7842     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7843           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7844                                 ) {
7845         int plylev, mvleft, mvtot, curscore, time;
7846         char mvname[MOVE_LEN];
7847         u64 nodes; // [DM]
7848         char plyext;
7849         int ignore = FALSE;
7850         int prefixHint = FALSE;
7851         mvname[0] = NULLCHAR;
7852
7853         switch (gameMode) {
7854           case MachinePlaysBlack:
7855           case IcsPlayingBlack:
7856             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7857             break;
7858           case MachinePlaysWhite:
7859           case IcsPlayingWhite:
7860             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7861             break;
7862           case AnalyzeMode:
7863           case AnalyzeFile:
7864             break;
7865           case IcsObserving: /* [DM] icsEngineAnalyze */
7866             if (!appData.icsEngineAnalyze) ignore = TRUE;
7867             break;
7868           case TwoMachinesPlay:
7869             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7870                 ignore = TRUE;
7871             }
7872             break;
7873           default:
7874             ignore = TRUE;
7875             break;
7876         }
7877
7878         if (!ignore) {
7879             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7880             buf1[0] = NULLCHAR;
7881             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7882                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7883
7884                 if (plyext != ' ' && plyext != '\t') {
7885                     time *= 100;
7886                 }
7887
7888                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7889                 if( cps->scoreIsAbsolute && 
7890                     ( gameMode == MachinePlaysBlack ||
7891                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7892                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7893                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7894                      !WhiteOnMove(currentMove)
7895                     ) )
7896                 {
7897                     curscore = -curscore;
7898                 }
7899
7900
7901                 tempStats.depth = plylev;
7902                 tempStats.nodes = nodes;
7903                 tempStats.time = time;
7904                 tempStats.score = curscore;
7905                 tempStats.got_only_move = 0;
7906
7907                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7908                         int ticklen;
7909
7910                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7911                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7912                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7913                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7914                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7915                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7916                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7917                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7918                 }
7919
7920                 /* Buffer overflow protection */
7921                 if (buf1[0] != NULLCHAR) {
7922                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7923                         && appData.debugMode) {
7924                         fprintf(debugFP,
7925                                 "PV is too long; using the first %u bytes.\n",
7926                                 (unsigned) sizeof(tempStats.movelist) - 1);
7927                     }
7928
7929                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7930                 } else {
7931                     sprintf(tempStats.movelist, " no PV\n");
7932                 }
7933
7934                 if (tempStats.seen_stat) {
7935                     tempStats.ok_to_send = 1;
7936                 }
7937
7938                 if (strchr(tempStats.movelist, '(') != NULL) {
7939                     tempStats.line_is_book = 1;
7940                     tempStats.nr_moves = 0;
7941                     tempStats.moves_left = 0;
7942                 } else {
7943                     tempStats.line_is_book = 0;
7944                 }
7945
7946                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7947                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7948
7949                 SendProgramStatsToFrontend( cps, &tempStats );
7950
7951                 /* 
7952                     [AS] Protect the thinkOutput buffer from overflow... this
7953                     is only useful if buf1 hasn't overflowed first!
7954                 */
7955                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7956                         plylev, 
7957                         (gameMode == TwoMachinesPlay ?
7958                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7959                         ((double) curscore) / 100.0,
7960                         prefixHint ? lastHint : "",
7961                         prefixHint ? " " : "" );
7962
7963                 if( buf1[0] != NULLCHAR ) {
7964                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7965
7966                     if( strlen(buf1) > max_len ) {
7967                         if( appData.debugMode) {
7968                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7969                         }
7970                         buf1[max_len+1] = '\0';
7971                     }
7972
7973                     strcat( thinkOutput, buf1 );
7974                 }
7975
7976                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7977                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7978                     DisplayMove(currentMove - 1);
7979                 }
7980                 return;
7981
7982             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7983                 /* crafty (9.25+) says "(only move) <move>"
7984                  * if there is only 1 legal move
7985                  */
7986                 sscanf(p, "(only move) %s", buf1);
7987                 sprintf(thinkOutput, "%s (only move)", buf1);
7988                 sprintf(programStats.movelist, "%s (only move)", buf1);
7989                 programStats.depth = 1;
7990                 programStats.nr_moves = 1;
7991                 programStats.moves_left = 1;
7992                 programStats.nodes = 1;
7993                 programStats.time = 1;
7994                 programStats.got_only_move = 1;
7995
7996                 /* Not really, but we also use this member to
7997                    mean "line isn't going to change" (Crafty
7998                    isn't searching, so stats won't change) */
7999                 programStats.line_is_book = 1;
8000
8001                 SendProgramStatsToFrontend( cps, &programStats );
8002                 
8003                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
8004                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8005                     DisplayMove(currentMove - 1);
8006                 }
8007                 return;
8008             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8009                               &time, &nodes, &plylev, &mvleft,
8010                               &mvtot, mvname) >= 5) {
8011                 /* The stat01: line is from Crafty (9.29+) in response
8012                    to the "." command */
8013                 programStats.seen_stat = 1;
8014                 cps->maybeThinking = TRUE;
8015
8016                 if (programStats.got_only_move || !appData.periodicUpdates)
8017                   return;
8018
8019                 programStats.depth = plylev;
8020                 programStats.time = time;
8021                 programStats.nodes = nodes;
8022                 programStats.moves_left = mvleft;
8023                 programStats.nr_moves = mvtot;
8024                 strcpy(programStats.move_name, mvname);
8025                 programStats.ok_to_send = 1;
8026                 programStats.movelist[0] = '\0';
8027
8028                 SendProgramStatsToFrontend( cps, &programStats );
8029
8030                 return;
8031
8032             } else if (strncmp(message,"++",2) == 0) {
8033                 /* Crafty 9.29+ outputs this */
8034                 programStats.got_fail = 2;
8035                 return;
8036
8037             } else if (strncmp(message,"--",2) == 0) {
8038                 /* Crafty 9.29+ outputs this */
8039                 programStats.got_fail = 1;
8040                 return;
8041
8042             } else if (thinkOutput[0] != NULLCHAR &&
8043                        strncmp(message, "    ", 4) == 0) {
8044                 unsigned message_len;
8045
8046                 p = message;
8047                 while (*p && *p == ' ') p++;
8048
8049                 message_len = strlen( p );
8050
8051                 /* [AS] Avoid buffer overflow */
8052                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8053                     strcat(thinkOutput, " ");
8054                     strcat(thinkOutput, p);
8055                 }
8056
8057                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8058                     strcat(programStats.movelist, " ");
8059                     strcat(programStats.movelist, p);
8060                 }
8061
8062                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8063                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8064                     DisplayMove(currentMove - 1);
8065                 }
8066                 return;
8067             }
8068         }
8069         else {
8070             buf1[0] = NULLCHAR;
8071
8072             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8073                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8074             {
8075                 ChessProgramStats cpstats;
8076
8077                 if (plyext != ' ' && plyext != '\t') {
8078                     time *= 100;
8079                 }
8080
8081                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8082                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8083                     curscore = -curscore;
8084                 }
8085
8086                 cpstats.depth = plylev;
8087                 cpstats.nodes = nodes;
8088                 cpstats.time = time;
8089                 cpstats.score = curscore;
8090                 cpstats.got_only_move = 0;
8091                 cpstats.movelist[0] = '\0';
8092
8093                 if (buf1[0] != NULLCHAR) {
8094                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8095                 }
8096
8097                 cpstats.ok_to_send = 0;
8098                 cpstats.line_is_book = 0;
8099                 cpstats.nr_moves = 0;
8100                 cpstats.moves_left = 0;
8101
8102                 SendProgramStatsToFrontend( cps, &cpstats );
8103             }
8104         }
8105     }
8106 }
8107
8108
8109 /* Parse a game score from the character string "game", and
8110    record it as the history of the current game.  The game
8111    score is NOT assumed to start from the standard position. 
8112    The display is not updated in any way.
8113    */
8114 void
8115 ParseGameHistory(game)
8116      char *game;
8117 {
8118     ChessMove moveType;
8119     int fromX, fromY, toX, toY, boardIndex;
8120     char promoChar;
8121     char *p, *q;
8122     char buf[MSG_SIZ];
8123
8124     if (appData.debugMode)
8125       fprintf(debugFP, "Parsing game history: %s\n", game);
8126
8127     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8128     gameInfo.site = StrSave(appData.icsHost);
8129     gameInfo.date = PGNDate();
8130     gameInfo.round = StrSave("-");
8131
8132     /* Parse out names of players */
8133     while (*game == ' ') game++;
8134     p = buf;
8135     while (*game != ' ') *p++ = *game++;
8136     *p = NULLCHAR;
8137     gameInfo.white = StrSave(buf);
8138     while (*game == ' ') game++;
8139     p = buf;
8140     while (*game != ' ' && *game != '\n') *p++ = *game++;
8141     *p = NULLCHAR;
8142     gameInfo.black = StrSave(buf);
8143
8144     /* Parse moves */
8145     boardIndex = blackPlaysFirst ? 1 : 0;
8146     yynewstr(game);
8147     for (;;) {
8148         yyboardindex = boardIndex;
8149         moveType = (ChessMove) yylex();
8150         switch (moveType) {
8151           case IllegalMove:             /* maybe suicide chess, etc. */
8152   if (appData.debugMode) {
8153     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8154     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8155     setbuf(debugFP, NULL);
8156   }
8157           case WhitePromotionChancellor:
8158           case BlackPromotionChancellor:
8159           case WhitePromotionArchbishop:
8160           case BlackPromotionArchbishop:
8161           case WhitePromotionQueen:
8162           case BlackPromotionQueen:
8163           case WhitePromotionRook:
8164           case BlackPromotionRook:
8165           case WhitePromotionBishop:
8166           case BlackPromotionBishop:
8167           case WhitePromotionKnight:
8168           case BlackPromotionKnight:
8169           case WhitePromotionKing:
8170           case BlackPromotionKing:
8171           case NormalMove:
8172           case WhiteCapturesEnPassant:
8173           case BlackCapturesEnPassant:
8174           case WhiteKingSideCastle:
8175           case WhiteQueenSideCastle:
8176           case BlackKingSideCastle:
8177           case BlackQueenSideCastle:
8178           case WhiteKingSideCastleWild:
8179           case WhiteQueenSideCastleWild:
8180           case BlackKingSideCastleWild:
8181           case BlackQueenSideCastleWild:
8182           /* PUSH Fabien */
8183           case WhiteHSideCastleFR:
8184           case WhiteASideCastleFR:
8185           case BlackHSideCastleFR:
8186           case BlackASideCastleFR:
8187           /* POP Fabien */
8188             fromX = currentMoveString[0] - AAA;
8189             fromY = currentMoveString[1] - ONE;
8190             toX = currentMoveString[2] - AAA;
8191             toY = currentMoveString[3] - ONE;
8192             promoChar = currentMoveString[4];
8193             break;
8194           case WhiteDrop:
8195           case BlackDrop:
8196             fromX = moveType == WhiteDrop ?
8197               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8198             (int) CharToPiece(ToLower(currentMoveString[0]));
8199             fromY = DROP_RANK;
8200             toX = currentMoveString[2] - AAA;
8201             toY = currentMoveString[3] - ONE;
8202             promoChar = NULLCHAR;
8203             break;
8204           case AmbiguousMove:
8205             /* bug? */
8206             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8207   if (appData.debugMode) {
8208     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8209     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8210     setbuf(debugFP, NULL);
8211   }
8212             DisplayError(buf, 0);
8213             return;
8214           case ImpossibleMove:
8215             /* bug? */
8216             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8217   if (appData.debugMode) {
8218     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8219     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8220     setbuf(debugFP, NULL);
8221   }
8222             DisplayError(buf, 0);
8223             return;
8224           case (ChessMove) 0:   /* end of file */
8225             if (boardIndex < backwardMostMove) {
8226                 /* Oops, gap.  How did that happen? */
8227                 DisplayError(_("Gap in move list"), 0);
8228                 return;
8229             }
8230             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8231             if (boardIndex > forwardMostMove) {
8232                 forwardMostMove = boardIndex;
8233             }
8234             return;
8235           case ElapsedTime:
8236             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8237                 strcat(parseList[boardIndex-1], " ");
8238                 strcat(parseList[boardIndex-1], yy_text);
8239             }
8240             continue;
8241           case Comment:
8242           case PGNTag:
8243           case NAG:
8244           default:
8245             /* ignore */
8246             continue;
8247           case WhiteWins:
8248           case BlackWins:
8249           case GameIsDrawn:
8250           case GameUnfinished:
8251             if (gameMode == IcsExamining) {
8252                 if (boardIndex < backwardMostMove) {
8253                     /* Oops, gap.  How did that happen? */
8254                     return;
8255                 }
8256                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8257                 return;
8258             }
8259             gameInfo.result = moveType;
8260             p = strchr(yy_text, '{');
8261             if (p == NULL) p = strchr(yy_text, '(');
8262             if (p == NULL) {
8263                 p = yy_text;
8264                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8265             } else {
8266                 q = strchr(p, *p == '{' ? '}' : ')');
8267                 if (q != NULL) *q = NULLCHAR;
8268                 p++;
8269             }
8270             gameInfo.resultDetails = StrSave(p);
8271             continue;
8272         }
8273         if (boardIndex >= forwardMostMove &&
8274             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8275             backwardMostMove = blackPlaysFirst ? 1 : 0;
8276             return;
8277         }
8278         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8279                                  fromY, fromX, toY, toX, promoChar,
8280                                  parseList[boardIndex]);
8281         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8282         /* currentMoveString is set as a side-effect of yylex */
8283         strcpy(moveList[boardIndex], currentMoveString);
8284         strcat(moveList[boardIndex], "\n");
8285         boardIndex++;
8286         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8287         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8288           case MT_NONE:
8289           case MT_STALEMATE:
8290           default:
8291             break;
8292           case MT_CHECK:
8293             if(gameInfo.variant != VariantShogi)
8294                 strcat(parseList[boardIndex - 1], "+");
8295             break;
8296           case MT_CHECKMATE:
8297           case MT_STAINMATE:
8298             strcat(parseList[boardIndex - 1], "#");
8299             break;
8300         }
8301     }
8302 }
8303
8304
8305 /* Apply a move to the given board  */
8306 void
8307 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8308      int fromX, fromY, toX, toY;
8309      int promoChar;
8310      Board board;
8311 {
8312   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8313   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8314
8315     /* [HGM] compute & store e.p. status and castling rights for new position */
8316     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8317     { int i;
8318
8319       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8320       oldEP = (signed char)board[EP_STATUS];
8321       board[EP_STATUS] = EP_NONE;
8322
8323       if( board[toY][toX] != EmptySquare ) 
8324            board[EP_STATUS] = EP_CAPTURE;  
8325
8326       if( board[fromY][fromX] == WhitePawn ) {
8327            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8328                board[EP_STATUS] = EP_PAWN_MOVE;
8329            if( toY-fromY==2) {
8330                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8331                         gameInfo.variant != VariantBerolina || toX < fromX)
8332                       board[EP_STATUS] = toX | berolina;
8333                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8334                         gameInfo.variant != VariantBerolina || toX > fromX) 
8335                       board[EP_STATUS] = toX;
8336            }
8337       } else 
8338       if( board[fromY][fromX] == BlackPawn ) {
8339            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8340                board[EP_STATUS] = EP_PAWN_MOVE; 
8341            if( toY-fromY== -2) {
8342                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8343                         gameInfo.variant != VariantBerolina || toX < fromX)
8344                       board[EP_STATUS] = toX | berolina;
8345                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8346                         gameInfo.variant != VariantBerolina || toX > fromX) 
8347                       board[EP_STATUS] = toX;
8348            }
8349        }
8350
8351        for(i=0; i<nrCastlingRights; i++) {
8352            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8353               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8354              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8355        }
8356
8357     }
8358
8359   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8360   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8361        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8362          
8363   if (fromX == toX && fromY == toY) return;
8364
8365   if (fromY == DROP_RANK) {
8366         /* must be first */
8367         piece = board[toY][toX] = (ChessSquare) fromX;
8368   } else {
8369      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8370      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8371      if(gameInfo.variant == VariantKnightmate)
8372          king += (int) WhiteUnicorn - (int) WhiteKing;
8373
8374     /* Code added by Tord: */
8375     /* FRC castling assumed when king captures friendly rook. */
8376     if (board[fromY][fromX] == WhiteKing &&
8377              board[toY][toX] == WhiteRook) {
8378       board[fromY][fromX] = EmptySquare;
8379       board[toY][toX] = EmptySquare;
8380       if(toX > fromX) {
8381         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8382       } else {
8383         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8384       }
8385     } else if (board[fromY][fromX] == BlackKing &&
8386                board[toY][toX] == BlackRook) {
8387       board[fromY][fromX] = EmptySquare;
8388       board[toY][toX] = EmptySquare;
8389       if(toX > fromX) {
8390         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8391       } else {
8392         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8393       }
8394     /* End of code added by Tord */
8395
8396     } else if (board[fromY][fromX] == king
8397         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8398         && toY == fromY && toX > fromX+1) {
8399         board[fromY][fromX] = EmptySquare;
8400         board[toY][toX] = king;
8401         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8402         board[fromY][BOARD_RGHT-1] = EmptySquare;
8403     } else if (board[fromY][fromX] == king
8404         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8405                && toY == fromY && toX < fromX-1) {
8406         board[fromY][fromX] = EmptySquare;
8407         board[toY][toX] = king;
8408         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8409         board[fromY][BOARD_LEFT] = EmptySquare;
8410     } else if (board[fromY][fromX] == WhitePawn
8411                && toY >= BOARD_HEIGHT-promoRank
8412                && gameInfo.variant != VariantXiangqi
8413                ) {
8414         /* white pawn promotion */
8415         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8416         if (board[toY][toX] == EmptySquare) {
8417             board[toY][toX] = WhiteQueen;
8418         }
8419         if(gameInfo.variant==VariantBughouse ||
8420            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8421             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8422         board[fromY][fromX] = EmptySquare;
8423     } else if ((fromY == BOARD_HEIGHT-4)
8424                && (toX != fromX)
8425                && gameInfo.variant != VariantXiangqi
8426                && gameInfo.variant != VariantBerolina
8427                && (board[fromY][fromX] == WhitePawn)
8428                && (board[toY][toX] == EmptySquare)) {
8429         board[fromY][fromX] = EmptySquare;
8430         board[toY][toX] = WhitePawn;
8431         captured = board[toY - 1][toX];
8432         board[toY - 1][toX] = EmptySquare;
8433     } else if ((fromY == BOARD_HEIGHT-4)
8434                && (toX == fromX)
8435                && gameInfo.variant == VariantBerolina
8436                && (board[fromY][fromX] == WhitePawn)
8437                && (board[toY][toX] == EmptySquare)) {
8438         board[fromY][fromX] = EmptySquare;
8439         board[toY][toX] = WhitePawn;
8440         if(oldEP & EP_BEROLIN_A) {
8441                 captured = board[fromY][fromX-1];
8442                 board[fromY][fromX-1] = EmptySquare;
8443         }else{  captured = board[fromY][fromX+1];
8444                 board[fromY][fromX+1] = EmptySquare;
8445         }
8446     } else if (board[fromY][fromX] == king
8447         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8448                && toY == fromY && toX > fromX+1) {
8449         board[fromY][fromX] = EmptySquare;
8450         board[toY][toX] = king;
8451         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8452         board[fromY][BOARD_RGHT-1] = EmptySquare;
8453     } else if (board[fromY][fromX] == king
8454         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8455                && toY == fromY && toX < fromX-1) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = king;
8458         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8459         board[fromY][BOARD_LEFT] = EmptySquare;
8460     } else if (fromY == 7 && fromX == 3
8461                && board[fromY][fromX] == BlackKing
8462                && toY == 7 && toX == 5) {
8463         board[fromY][fromX] = EmptySquare;
8464         board[toY][toX] = BlackKing;
8465         board[fromY][7] = EmptySquare;
8466         board[toY][4] = BlackRook;
8467     } else if (fromY == 7 && fromX == 3
8468                && board[fromY][fromX] == BlackKing
8469                && toY == 7 && toX == 1) {
8470         board[fromY][fromX] = EmptySquare;
8471         board[toY][toX] = BlackKing;
8472         board[fromY][0] = EmptySquare;
8473         board[toY][2] = BlackRook;
8474     } else if (board[fromY][fromX] == BlackPawn
8475                && toY < promoRank
8476                && gameInfo.variant != VariantXiangqi
8477                ) {
8478         /* black pawn promotion */
8479         board[toY][toX] = CharToPiece(ToLower(promoChar));
8480         if (board[toY][toX] == EmptySquare) {
8481             board[toY][toX] = BlackQueen;
8482         }
8483         if(gameInfo.variant==VariantBughouse ||
8484            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8485             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8486         board[fromY][fromX] = EmptySquare;
8487     } else if ((fromY == 3)
8488                && (toX != fromX)
8489                && gameInfo.variant != VariantXiangqi
8490                && gameInfo.variant != VariantBerolina
8491                && (board[fromY][fromX] == BlackPawn)
8492                && (board[toY][toX] == EmptySquare)) {
8493         board[fromY][fromX] = EmptySquare;
8494         board[toY][toX] = BlackPawn;
8495         captured = board[toY + 1][toX];
8496         board[toY + 1][toX] = EmptySquare;
8497     } else if ((fromY == 3)
8498                && (toX == fromX)
8499                && gameInfo.variant == VariantBerolina
8500                && (board[fromY][fromX] == BlackPawn)
8501                && (board[toY][toX] == EmptySquare)) {
8502         board[fromY][fromX] = EmptySquare;
8503         board[toY][toX] = BlackPawn;
8504         if(oldEP & EP_BEROLIN_A) {
8505                 captured = board[fromY][fromX-1];
8506                 board[fromY][fromX-1] = EmptySquare;
8507         }else{  captured = board[fromY][fromX+1];
8508                 board[fromY][fromX+1] = EmptySquare;
8509         }
8510     } else {
8511         board[toY][toX] = board[fromY][fromX];
8512         board[fromY][fromX] = EmptySquare;
8513     }
8514
8515     /* [HGM] now we promote for Shogi, if needed */
8516     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8517         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8518   }
8519
8520     if (gameInfo.holdingsWidth != 0) {
8521
8522       /* !!A lot more code needs to be written to support holdings  */
8523       /* [HGM] OK, so I have written it. Holdings are stored in the */
8524       /* penultimate board files, so they are automaticlly stored   */
8525       /* in the game history.                                       */
8526       if (fromY == DROP_RANK) {
8527         /* Delete from holdings, by decreasing count */
8528         /* and erasing image if necessary            */
8529         p = (int) fromX;
8530         if(p < (int) BlackPawn) { /* white drop */
8531              p -= (int)WhitePawn;
8532                  p = PieceToNumber((ChessSquare)p);
8533              if(p >= gameInfo.holdingsSize) p = 0;
8534              if(--board[p][BOARD_WIDTH-2] <= 0)
8535                   board[p][BOARD_WIDTH-1] = EmptySquare;
8536              if((int)board[p][BOARD_WIDTH-2] < 0)
8537                         board[p][BOARD_WIDTH-2] = 0;
8538         } else {                  /* black drop */
8539              p -= (int)BlackPawn;
8540                  p = PieceToNumber((ChessSquare)p);
8541              if(p >= gameInfo.holdingsSize) p = 0;
8542              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8543                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8544              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8545                         board[BOARD_HEIGHT-1-p][1] = 0;
8546         }
8547       }
8548       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8549           && gameInfo.variant != VariantBughouse        ) {
8550         /* [HGM] holdings: Add to holdings, if holdings exist */
8551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8552                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8553                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8554         }
8555         p = (int) captured;
8556         if (p >= (int) BlackPawn) {
8557           p -= (int)BlackPawn;
8558           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8559                   /* in Shogi restore piece to its original  first */
8560                   captured = (ChessSquare) (DEMOTED captured);
8561                   p = DEMOTED p;
8562           }
8563           p = PieceToNumber((ChessSquare)p);
8564           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8565           board[p][BOARD_WIDTH-2]++;
8566           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8567         } else {
8568           p -= (int)WhitePawn;
8569           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8570                   captured = (ChessSquare) (DEMOTED captured);
8571                   p = DEMOTED p;
8572           }
8573           p = PieceToNumber((ChessSquare)p);
8574           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8575           board[BOARD_HEIGHT-1-p][1]++;
8576           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8577         }
8578       }
8579     } else if (gameInfo.variant == VariantAtomic) {
8580       if (captured != EmptySquare) {
8581         int y, x;
8582         for (y = toY-1; y <= toY+1; y++) {
8583           for (x = toX-1; x <= toX+1; x++) {
8584             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8585                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8586               board[y][x] = EmptySquare;
8587             }
8588           }
8589         }
8590         board[toY][toX] = EmptySquare;
8591       }
8592     }
8593     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8594         /* [HGM] Shogi promotions */
8595         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8596     }
8597
8598     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8599                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8600         // [HGM] superchess: take promotion piece out of holdings
8601         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8602         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8603             if(!--board[k][BOARD_WIDTH-2])
8604                 board[k][BOARD_WIDTH-1] = EmptySquare;
8605         } else {
8606             if(!--board[BOARD_HEIGHT-1-k][1])
8607                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8608         }
8609     }
8610
8611 }
8612
8613 /* Updates forwardMostMove */
8614 void
8615 MakeMove(fromX, fromY, toX, toY, promoChar)
8616      int fromX, fromY, toX, toY;
8617      int promoChar;
8618 {
8619 //    forwardMostMove++; // [HGM] bare: moved downstream
8620
8621     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8622         int timeLeft; static int lastLoadFlag=0; int king, piece;
8623         piece = boards[forwardMostMove][fromY][fromX];
8624         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8625         if(gameInfo.variant == VariantKnightmate)
8626             king += (int) WhiteUnicorn - (int) WhiteKing;
8627         if(forwardMostMove == 0) {
8628             if(blackPlaysFirst) 
8629                 fprintf(serverMoves, "%s;", second.tidy);
8630             fprintf(serverMoves, "%s;", first.tidy);
8631             if(!blackPlaysFirst) 
8632                 fprintf(serverMoves, "%s;", second.tidy);
8633         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8634         lastLoadFlag = loadFlag;
8635         // print base move
8636         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8637         // print castling suffix
8638         if( toY == fromY && piece == king ) {
8639             if(toX-fromX > 1)
8640                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8641             if(fromX-toX >1)
8642                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8643         }
8644         // e.p. suffix
8645         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8646              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8647              boards[forwardMostMove][toY][toX] == EmptySquare
8648              && fromX != toX && fromY != toY)
8649                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8650         // promotion suffix
8651         if(promoChar != NULLCHAR)
8652                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8653         if(!loadFlag) {
8654             fprintf(serverMoves, "/%d/%d",
8655                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8656             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8657             else                      timeLeft = blackTimeRemaining/1000;
8658             fprintf(serverMoves, "/%d", timeLeft);
8659         }
8660         fflush(serverMoves);
8661     }
8662
8663     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8664       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8665                         0, 1);
8666       return;
8667     }
8668     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8669     if (commentList[forwardMostMove+1] != NULL) {
8670         free(commentList[forwardMostMove+1]);
8671         commentList[forwardMostMove+1] = NULL;
8672     }
8673     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8674     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8675     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8676     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8677     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8678     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8679     gameInfo.result = GameUnfinished;
8680     if (gameInfo.resultDetails != NULL) {
8681         free(gameInfo.resultDetails);
8682         gameInfo.resultDetails = NULL;
8683     }
8684     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8685                               moveList[forwardMostMove - 1]);
8686     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8687                              PosFlags(forwardMostMove - 1),
8688                              fromY, fromX, toY, toX, promoChar,
8689                              parseList[forwardMostMove - 1]);
8690     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8691       case MT_NONE:
8692       case MT_STALEMATE:
8693       default:
8694         break;
8695       case MT_CHECK:
8696         if(gameInfo.variant != VariantShogi)
8697             strcat(parseList[forwardMostMove - 1], "+");
8698         break;
8699       case MT_CHECKMATE:
8700       case MT_STAINMATE:
8701         strcat(parseList[forwardMostMove - 1], "#");
8702         break;
8703     }
8704     if (appData.debugMode) {
8705         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8706     }
8707
8708 }
8709
8710 /* Updates currentMove if not pausing */
8711 void
8712 ShowMove(fromX, fromY, toX, toY)
8713 {
8714     int instant = (gameMode == PlayFromGameFile) ?
8715         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8716     if(appData.noGUI) return;
8717     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8718         if (!instant) {
8719             if (forwardMostMove == currentMove + 1) {
8720                 AnimateMove(boards[forwardMostMove - 1],
8721                             fromX, fromY, toX, toY);
8722             }
8723             if (appData.highlightLastMove) {
8724                 SetHighlights(fromX, fromY, toX, toY);
8725             }
8726         }
8727         currentMove = forwardMostMove;
8728     }
8729
8730     if (instant) return;
8731
8732     DisplayMove(currentMove - 1);
8733     DrawPosition(FALSE, boards[currentMove]);
8734     DisplayBothClocks();
8735     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8736 }
8737
8738 void SendEgtPath(ChessProgramState *cps)
8739 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8740         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8741
8742         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8743
8744         while(*p) {
8745             char c, *q = name+1, *r, *s;
8746
8747             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8748             while(*p && *p != ',') *q++ = *p++;
8749             *q++ = ':'; *q = 0;
8750             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8751                 strcmp(name, ",nalimov:") == 0 ) {
8752                 // take nalimov path from the menu-changeable option first, if it is defined
8753                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8754                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8755             } else
8756             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8757                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8758                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8759                 s = r = StrStr(s, ":") + 1; // beginning of path info
8760                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8761                 c = *r; *r = 0;             // temporarily null-terminate path info
8762                     *--q = 0;               // strip of trailig ':' from name
8763                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8764                 *r = c;
8765                 SendToProgram(buf,cps);     // send egtbpath command for this format
8766             }
8767             if(*p == ',') p++; // read away comma to position for next format name
8768         }
8769 }
8770
8771 void
8772 InitChessProgram(cps, setup)
8773      ChessProgramState *cps;
8774      int setup; /* [HGM] needed to setup FRC opening position */
8775 {
8776     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8777     if (appData.noChessProgram) return;
8778     hintRequested = FALSE;
8779     bookRequested = FALSE;
8780
8781     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8782     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8783     if(cps->memSize) { /* [HGM] memory */
8784         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8785         SendToProgram(buf, cps);
8786     }
8787     SendEgtPath(cps); /* [HGM] EGT */
8788     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8789         sprintf(buf, "cores %d\n", appData.smpCores);
8790         SendToProgram(buf, cps);
8791     }
8792
8793     SendToProgram(cps->initString, cps);
8794     if (gameInfo.variant != VariantNormal &&
8795         gameInfo.variant != VariantLoadable
8796         /* [HGM] also send variant if board size non-standard */
8797         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8798                                             ) {
8799       char *v = VariantName(gameInfo.variant);
8800       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8801         /* [HGM] in protocol 1 we have to assume all variants valid */
8802         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8803         DisplayFatalError(buf, 0, 1);
8804         return;
8805       }
8806
8807       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8808       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8809       if( gameInfo.variant == VariantXiangqi )
8810            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8811       if( gameInfo.variant == VariantShogi )
8812            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8813       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8814            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8815       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8816                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8817            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8818       if( gameInfo.variant == VariantCourier )
8819            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8820       if( gameInfo.variant == VariantSuper )
8821            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8822       if( gameInfo.variant == VariantGreat )
8823            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8824
8825       if(overruled) {
8826            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8827                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8828            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8829            if(StrStr(cps->variants, b) == NULL) { 
8830                // specific sized variant not known, check if general sizing allowed
8831                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8832                    if(StrStr(cps->variants, "boardsize") == NULL) {
8833                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8834                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8835                        DisplayFatalError(buf, 0, 1);
8836                        return;
8837                    }
8838                    /* [HGM] here we really should compare with the maximum supported board size */
8839                }
8840            }
8841       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8842       sprintf(buf, "variant %s\n", b);
8843       SendToProgram(buf, cps);
8844     }
8845     currentlyInitializedVariant = gameInfo.variant;
8846
8847     /* [HGM] send opening position in FRC to first engine */
8848     if(setup) {
8849           SendToProgram("force\n", cps);
8850           SendBoard(cps, 0);
8851           /* engine is now in force mode! Set flag to wake it up after first move. */
8852           setboardSpoiledMachineBlack = 1;
8853     }
8854
8855     if (cps->sendICS) {
8856       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8857       SendToProgram(buf, cps);
8858     }
8859     cps->maybeThinking = FALSE;
8860     cps->offeredDraw = 0;
8861     if (!appData.icsActive) {
8862         SendTimeControl(cps, movesPerSession, timeControl,
8863                         timeIncrement, appData.searchDepth,
8864                         searchTime);
8865     }
8866     if (appData.showThinking 
8867         // [HGM] thinking: four options require thinking output to be sent
8868         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8869                                 ) {
8870         SendToProgram("post\n", cps);
8871     }
8872     SendToProgram("hard\n", cps);
8873     if (!appData.ponderNextMove) {
8874         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8875            it without being sure what state we are in first.  "hard"
8876            is not a toggle, so that one is OK.
8877          */
8878         SendToProgram("easy\n", cps);
8879     }
8880     if (cps->usePing) {
8881       sprintf(buf, "ping %d\n", ++cps->lastPing);
8882       SendToProgram(buf, cps);
8883     }
8884     cps->initDone = TRUE;
8885 }   
8886
8887
8888 void
8889 StartChessProgram(cps)
8890      ChessProgramState *cps;
8891 {
8892     char buf[MSG_SIZ];
8893     int err;
8894
8895     if (appData.noChessProgram) return;
8896     cps->initDone = FALSE;
8897
8898     if (strcmp(cps->host, "localhost") == 0) {
8899         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8900     } else if (*appData.remoteShell == NULLCHAR) {
8901         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8902     } else {
8903         if (*appData.remoteUser == NULLCHAR) {
8904           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8905                     cps->program);
8906         } else {
8907           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8908                     cps->host, appData.remoteUser, cps->program);
8909         }
8910         err = StartChildProcess(buf, "", &cps->pr);
8911     }
8912     
8913     if (err != 0) {
8914         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8915         DisplayFatalError(buf, err, 1);
8916         cps->pr = NoProc;
8917         cps->isr = NULL;
8918         return;
8919     }
8920     
8921     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8922     if (cps->protocolVersion > 1) {
8923       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8924       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8925       cps->comboCnt = 0;  //                and values of combo boxes
8926       SendToProgram(buf, cps);
8927     } else {
8928       SendToProgram("xboard\n", cps);
8929     }
8930 }
8931
8932
8933 void
8934 TwoMachinesEventIfReady P((void))
8935 {
8936   if (first.lastPing != first.lastPong) {
8937     DisplayMessage("", _("Waiting for first chess program"));
8938     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8939     return;
8940   }
8941   if (second.lastPing != second.lastPong) {
8942     DisplayMessage("", _("Waiting for second chess program"));
8943     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8944     return;
8945   }
8946   ThawUI();
8947   TwoMachinesEvent();
8948 }
8949
8950 void
8951 NextMatchGame P((void))
8952 {
8953     int index; /* [HGM] autoinc: step load index during match */
8954     Reset(FALSE, TRUE);
8955     if (*appData.loadGameFile != NULLCHAR) {
8956         index = appData.loadGameIndex;
8957         if(index < 0) { // [HGM] autoinc
8958             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8959             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8960         } 
8961         LoadGameFromFile(appData.loadGameFile,
8962                          index,
8963                          appData.loadGameFile, FALSE);
8964     } else if (*appData.loadPositionFile != NULLCHAR) {
8965         index = appData.loadPositionIndex;
8966         if(index < 0) { // [HGM] autoinc
8967             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8968             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8969         } 
8970         LoadPositionFromFile(appData.loadPositionFile,
8971                              index,
8972                              appData.loadPositionFile);
8973     }
8974     TwoMachinesEventIfReady();
8975 }
8976
8977 void UserAdjudicationEvent( int result )
8978 {
8979     ChessMove gameResult = GameIsDrawn;
8980
8981     if( result > 0 ) {
8982         gameResult = WhiteWins;
8983     }
8984     else if( result < 0 ) {
8985         gameResult = BlackWins;
8986     }
8987
8988     if( gameMode == TwoMachinesPlay ) {
8989         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8990     }
8991 }
8992
8993
8994 // [HGM] save: calculate checksum of game to make games easily identifiable
8995 int StringCheckSum(char *s)
8996 {
8997         int i = 0;
8998         if(s==NULL) return 0;
8999         while(*s) i = i*259 + *s++;
9000         return i;
9001 }
9002
9003 int GameCheckSum()
9004 {
9005         int i, sum=0;
9006         for(i=backwardMostMove; i<forwardMostMove; i++) {
9007                 sum += pvInfoList[i].depth;
9008                 sum += StringCheckSum(parseList[i]);
9009                 sum += StringCheckSum(commentList[i]);
9010                 sum *= 261;
9011         }
9012         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9013         return sum + StringCheckSum(commentList[i]);
9014 } // end of save patch
9015
9016 void
9017 GameEnds(result, resultDetails, whosays)
9018      ChessMove result;
9019      char *resultDetails;
9020      int whosays;
9021 {
9022     GameMode nextGameMode;
9023     int isIcsGame;
9024     char buf[MSG_SIZ], popupRequested = 0;
9025
9026     if(endingGame) return; /* [HGM] crash: forbid recursion */
9027     endingGame = 1;
9028     if(twoBoards) { // [HGM] dual: switch back to one board
9029         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9030         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9031     }
9032     if (appData.debugMode) {
9033       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9034               result, resultDetails ? resultDetails : "(null)", whosays);
9035     }
9036
9037     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9038
9039     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9040         /* If we are playing on ICS, the server decides when the
9041            game is over, but the engine can offer to draw, claim 
9042            a draw, or resign. 
9043          */
9044 #if ZIPPY
9045         if (appData.zippyPlay && first.initDone) {
9046             if (result == GameIsDrawn) {
9047                 /* In case draw still needs to be claimed */
9048                 SendToICS(ics_prefix);
9049                 SendToICS("draw\n");
9050             } else if (StrCaseStr(resultDetails, "resign")) {
9051                 SendToICS(ics_prefix);
9052                 SendToICS("resign\n");
9053             }
9054         }
9055 #endif
9056         endingGame = 0; /* [HGM] crash */
9057         return;
9058     }
9059
9060     /* If we're loading the game from a file, stop */
9061     if (whosays == GE_FILE) {
9062       (void) StopLoadGameTimer();
9063       gameFileFP = NULL;
9064     }
9065
9066     /* Cancel draw offers */
9067     first.offeredDraw = second.offeredDraw = 0;
9068
9069     /* If this is an ICS game, only ICS can really say it's done;
9070        if not, anyone can. */
9071     isIcsGame = (gameMode == IcsPlayingWhite || 
9072                  gameMode == IcsPlayingBlack || 
9073                  gameMode == IcsObserving    || 
9074                  gameMode == IcsExamining);
9075
9076     if (!isIcsGame || whosays == GE_ICS) {
9077         /* OK -- not an ICS game, or ICS said it was done */
9078         StopClocks();
9079         if (!isIcsGame && !appData.noChessProgram) 
9080           SetUserThinkingEnables();
9081     
9082         /* [HGM] if a machine claims the game end we verify this claim */
9083         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9084             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9085                 char claimer;
9086                 ChessMove trueResult = (ChessMove) -1;
9087
9088                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9089                                             first.twoMachinesColor[0] :
9090                                             second.twoMachinesColor[0] ;
9091
9092                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9093                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9094                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9095                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9096                 } else
9097                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9098                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9099                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9100                 } else
9101                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9102                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9103                 }
9104
9105                 // now verify win claims, but not in drop games, as we don't understand those yet
9106                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9107                                                  || gameInfo.variant == VariantGreat) &&
9108                     (result == WhiteWins && claimer == 'w' ||
9109                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9110                       if (appData.debugMode) {
9111                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9112                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9113                       }
9114                       if(result != trueResult) {
9115                               sprintf(buf, "False win claim: '%s'", resultDetails);
9116                               result = claimer == 'w' ? BlackWins : WhiteWins;
9117                               resultDetails = buf;
9118                       }
9119                 } else
9120                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9121                     && (forwardMostMove <= backwardMostMove ||
9122                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9123                         (claimer=='b')==(forwardMostMove&1))
9124                                                                                   ) {
9125                       /* [HGM] verify: draws that were not flagged are false claims */
9126                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9127                       result = claimer == 'w' ? BlackWins : WhiteWins;
9128                       resultDetails = buf;
9129                 }
9130                 /* (Claiming a loss is accepted no questions asked!) */
9131             }
9132             /* [HGM] bare: don't allow bare King to win */
9133             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9134                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9135                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9136                && result != GameIsDrawn)
9137             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9138                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9139                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9140                         if(p >= 0 && p <= (int)WhiteKing) k++;
9141                 }
9142                 if (appData.debugMode) {
9143                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9144                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9145                 }
9146                 if(k <= 1) {
9147                         result = GameIsDrawn;
9148                         sprintf(buf, "%s but bare king", resultDetails);
9149                         resultDetails = buf;
9150                 }
9151             }
9152         }
9153
9154
9155         if(serverMoves != NULL && !loadFlag) { char c = '=';
9156             if(result==WhiteWins) c = '+';
9157             if(result==BlackWins) c = '-';
9158             if(resultDetails != NULL)
9159                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9160         }
9161         if (resultDetails != NULL) {
9162             gameInfo.result = result;
9163             gameInfo.resultDetails = StrSave(resultDetails);
9164
9165             /* display last move only if game was not loaded from file */
9166             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9167                 DisplayMove(currentMove - 1);
9168     
9169             if (forwardMostMove != 0) {
9170                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9171                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9172                                                                 ) {
9173                     if (*appData.saveGameFile != NULLCHAR) {
9174                         SaveGameToFile(appData.saveGameFile, TRUE);
9175                     } else if (appData.autoSaveGames) {
9176                         AutoSaveGame();
9177                     }
9178                     if (*appData.savePositionFile != NULLCHAR) {
9179                         SavePositionToFile(appData.savePositionFile);
9180                     }
9181                 }
9182             }
9183
9184             /* Tell program how game ended in case it is learning */
9185             /* [HGM] Moved this to after saving the PGN, just in case */
9186             /* engine died and we got here through time loss. In that */
9187             /* case we will get a fatal error writing the pipe, which */
9188             /* would otherwise lose us the PGN.                       */
9189             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9190             /* output during GameEnds should never be fatal anymore   */
9191             if (gameMode == MachinePlaysWhite ||
9192                 gameMode == MachinePlaysBlack ||
9193                 gameMode == TwoMachinesPlay ||
9194                 gameMode == IcsPlayingWhite ||
9195                 gameMode == IcsPlayingBlack ||
9196                 gameMode == BeginningOfGame) {
9197                 char buf[MSG_SIZ];
9198                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9199                         resultDetails);
9200                 if (first.pr != NoProc) {
9201                     SendToProgram(buf, &first);
9202                 }
9203                 if (second.pr != NoProc &&
9204                     gameMode == TwoMachinesPlay) {
9205                     SendToProgram(buf, &second);
9206                 }
9207             }
9208         }
9209
9210         if (appData.icsActive) {
9211             if (appData.quietPlay &&
9212                 (gameMode == IcsPlayingWhite ||
9213                  gameMode == IcsPlayingBlack)) {
9214                 SendToICS(ics_prefix);
9215                 SendToICS("set shout 1\n");
9216             }
9217             nextGameMode = IcsIdle;
9218             ics_user_moved = FALSE;
9219             /* clean up premove.  It's ugly when the game has ended and the
9220              * premove highlights are still on the board.
9221              */
9222             if (gotPremove) {
9223               gotPremove = FALSE;
9224               ClearPremoveHighlights();
9225               DrawPosition(FALSE, boards[currentMove]);
9226             }
9227             if (whosays == GE_ICS) {
9228                 switch (result) {
9229                 case WhiteWins:
9230                     if (gameMode == IcsPlayingWhite)
9231                         PlayIcsWinSound();
9232                     else if(gameMode == IcsPlayingBlack)
9233                         PlayIcsLossSound();
9234                     break;
9235                 case BlackWins:
9236                     if (gameMode == IcsPlayingBlack)
9237                         PlayIcsWinSound();
9238                     else if(gameMode == IcsPlayingWhite)
9239                         PlayIcsLossSound();
9240                     break;
9241                 case GameIsDrawn:
9242                     PlayIcsDrawSound();
9243                     break;
9244                 default:
9245                     PlayIcsUnfinishedSound();
9246                 }
9247             }
9248         } else if (gameMode == EditGame ||
9249                    gameMode == PlayFromGameFile || 
9250                    gameMode == AnalyzeMode || 
9251                    gameMode == AnalyzeFile) {
9252             nextGameMode = gameMode;
9253         } else {
9254             nextGameMode = EndOfGame;
9255         }
9256         pausing = FALSE;
9257         ModeHighlight();
9258     } else {
9259         nextGameMode = gameMode;
9260     }
9261
9262     if (appData.noChessProgram) {
9263         gameMode = nextGameMode;
9264         ModeHighlight();
9265         endingGame = 0; /* [HGM] crash */
9266         return;
9267     }
9268
9269     if (first.reuse) {
9270         /* Put first chess program into idle state */
9271         if (first.pr != NoProc &&
9272             (gameMode == MachinePlaysWhite ||
9273              gameMode == MachinePlaysBlack ||
9274              gameMode == TwoMachinesPlay ||
9275              gameMode == IcsPlayingWhite ||
9276              gameMode == IcsPlayingBlack ||
9277              gameMode == BeginningOfGame)) {
9278             SendToProgram("force\n", &first);
9279             if (first.usePing) {
9280               char buf[MSG_SIZ];
9281               sprintf(buf, "ping %d\n", ++first.lastPing);
9282               SendToProgram(buf, &first);
9283             }
9284         }
9285     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9286         /* Kill off first chess program */
9287         if (first.isr != NULL)
9288           RemoveInputSource(first.isr);
9289         first.isr = NULL;
9290     
9291         if (first.pr != NoProc) {
9292             ExitAnalyzeMode();
9293             DoSleep( appData.delayBeforeQuit );
9294             SendToProgram("quit\n", &first);
9295             DoSleep( appData.delayAfterQuit );
9296             DestroyChildProcess(first.pr, first.useSigterm);
9297         }
9298         first.pr = NoProc;
9299     }
9300     if (second.reuse) {
9301         /* Put second chess program into idle state */
9302         if (second.pr != NoProc &&
9303             gameMode == TwoMachinesPlay) {
9304             SendToProgram("force\n", &second);
9305             if (second.usePing) {
9306               char buf[MSG_SIZ];
9307               sprintf(buf, "ping %d\n", ++second.lastPing);
9308               SendToProgram(buf, &second);
9309             }
9310         }
9311     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9312         /* Kill off second chess program */
9313         if (second.isr != NULL)
9314           RemoveInputSource(second.isr);
9315         second.isr = NULL;
9316     
9317         if (second.pr != NoProc) {
9318             DoSleep( appData.delayBeforeQuit );
9319             SendToProgram("quit\n", &second);
9320             DoSleep( appData.delayAfterQuit );
9321             DestroyChildProcess(second.pr, second.useSigterm);
9322         }
9323         second.pr = NoProc;
9324     }
9325
9326     if (matchMode && gameMode == TwoMachinesPlay) {
9327         switch (result) {
9328         case WhiteWins:
9329           if (first.twoMachinesColor[0] == 'w') {
9330             first.matchWins++;
9331           } else {
9332             second.matchWins++;
9333           }
9334           break;
9335         case BlackWins:
9336           if (first.twoMachinesColor[0] == 'b') {
9337             first.matchWins++;
9338           } else {
9339             second.matchWins++;
9340           }
9341           break;
9342         default:
9343           break;
9344         }
9345         if (matchGame < appData.matchGames) {
9346             char *tmp;
9347             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9348                 tmp = first.twoMachinesColor;
9349                 first.twoMachinesColor = second.twoMachinesColor;
9350                 second.twoMachinesColor = tmp;
9351             }
9352             gameMode = nextGameMode;
9353             matchGame++;
9354             if(appData.matchPause>10000 || appData.matchPause<10)
9355                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9356             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9357             endingGame = 0; /* [HGM] crash */
9358             return;
9359         } else {
9360             gameMode = nextGameMode;
9361             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9362                     first.tidy, second.tidy,
9363                     first.matchWins, second.matchWins,
9364                     appData.matchGames - (first.matchWins + second.matchWins));
9365             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9366         }
9367     }
9368     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9369         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9370       ExitAnalyzeMode();
9371     gameMode = nextGameMode;
9372     ModeHighlight();
9373     endingGame = 0;  /* [HGM] crash */
9374     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9375       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9376         matchMode = FALSE; appData.matchGames = matchGame = 0;
9377         DisplayNote(buf);
9378       }
9379     }
9380 }
9381
9382 /* Assumes program was just initialized (initString sent).
9383    Leaves program in force mode. */
9384 void
9385 FeedMovesToProgram(cps, upto) 
9386      ChessProgramState *cps;
9387      int upto;
9388 {
9389     int i;
9390     
9391     if (appData.debugMode)
9392       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9393               startedFromSetupPosition ? "position and " : "",
9394               backwardMostMove, upto, cps->which);
9395     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9396         // [HGM] variantswitch: make engine aware of new variant
9397         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9398                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9399         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9400         SendToProgram(buf, cps);
9401         currentlyInitializedVariant = gameInfo.variant;
9402     }
9403     SendToProgram("force\n", cps);
9404     if (startedFromSetupPosition) {
9405         SendBoard(cps, backwardMostMove);
9406     if (appData.debugMode) {
9407         fprintf(debugFP, "feedMoves\n");
9408     }
9409     }
9410     for (i = backwardMostMove; i < upto; i++) {
9411         SendMoveToProgram(i, cps);
9412     }
9413 }
9414
9415
9416 void
9417 ResurrectChessProgram()
9418 {
9419      /* The chess program may have exited.
9420         If so, restart it and feed it all the moves made so far. */
9421
9422     if (appData.noChessProgram || first.pr != NoProc) return;
9423     
9424     StartChessProgram(&first);
9425     InitChessProgram(&first, FALSE);
9426     FeedMovesToProgram(&first, currentMove);
9427
9428     if (!first.sendTime) {
9429         /* can't tell gnuchess what its clock should read,
9430            so we bow to its notion. */
9431         ResetClocks();
9432         timeRemaining[0][currentMove] = whiteTimeRemaining;
9433         timeRemaining[1][currentMove] = blackTimeRemaining;
9434     }
9435
9436     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9437                 appData.icsEngineAnalyze) && first.analysisSupport) {
9438       SendToProgram("analyze\n", &first);
9439       first.analyzing = TRUE;
9440     }
9441 }
9442
9443 /*
9444  * Button procedures
9445  */
9446 void
9447 Reset(redraw, init)
9448      int redraw, init;
9449 {
9450     int i;
9451
9452     if (appData.debugMode) {
9453         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9454                 redraw, init, gameMode);
9455     }
9456     CleanupTail(); // [HGM] vari: delete any stored variations
9457     pausing = pauseExamInvalid = FALSE;
9458     startedFromSetupPosition = blackPlaysFirst = FALSE;
9459     firstMove = TRUE;
9460     whiteFlag = blackFlag = FALSE;
9461     userOfferedDraw = FALSE;
9462     hintRequested = bookRequested = FALSE;
9463     first.maybeThinking = FALSE;
9464     second.maybeThinking = FALSE;
9465     first.bookSuspend = FALSE; // [HGM] book
9466     second.bookSuspend = FALSE;
9467     thinkOutput[0] = NULLCHAR;
9468     lastHint[0] = NULLCHAR;
9469     ClearGameInfo(&gameInfo);
9470     gameInfo.variant = StringToVariant(appData.variant);
9471     ics_user_moved = ics_clock_paused = FALSE;
9472     ics_getting_history = H_FALSE;
9473     ics_gamenum = -1;
9474     white_holding[0] = black_holding[0] = NULLCHAR;
9475     ClearProgramStats();
9476     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9477     
9478     ResetFrontEnd();
9479     ClearHighlights();
9480     flipView = appData.flipView;
9481     ClearPremoveHighlights();
9482     gotPremove = FALSE;
9483     alarmSounded = FALSE;
9484
9485     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9486     if(appData.serverMovesName != NULL) {
9487         /* [HGM] prepare to make moves file for broadcasting */
9488         clock_t t = clock();
9489         if(serverMoves != NULL) fclose(serverMoves);
9490         serverMoves = fopen(appData.serverMovesName, "r");
9491         if(serverMoves != NULL) {
9492             fclose(serverMoves);
9493             /* delay 15 sec before overwriting, so all clients can see end */
9494             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9495         }
9496         serverMoves = fopen(appData.serverMovesName, "w");
9497     }
9498
9499     ExitAnalyzeMode();
9500     gameMode = BeginningOfGame;
9501     ModeHighlight();
9502     if(appData.icsActive) gameInfo.variant = VariantNormal;
9503     currentMove = forwardMostMove = backwardMostMove = 0;
9504     InitPosition(redraw);
9505     for (i = 0; i < MAX_MOVES; i++) {
9506         if (commentList[i] != NULL) {
9507             free(commentList[i]);
9508             commentList[i] = NULL;
9509         }
9510     }
9511     ResetClocks();
9512     timeRemaining[0][0] = whiteTimeRemaining;
9513     timeRemaining[1][0] = blackTimeRemaining;
9514     if (first.pr == NULL) {
9515         StartChessProgram(&first);
9516     }
9517     if (init) {
9518             InitChessProgram(&first, startedFromSetupPosition);
9519     }
9520     DisplayTitle("");
9521     DisplayMessage("", "");
9522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9523     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9524 }
9525
9526 void
9527 AutoPlayGameLoop()
9528 {
9529     for (;;) {
9530         if (!AutoPlayOneMove())
9531           return;
9532         if (matchMode || appData.timeDelay == 0)
9533           continue;
9534         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9535           return;
9536         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9537         break;
9538     }
9539 }
9540
9541
9542 int
9543 AutoPlayOneMove()
9544 {
9545     int fromX, fromY, toX, toY;
9546
9547     if (appData.debugMode) {
9548       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9549     }
9550
9551     if (gameMode != PlayFromGameFile)
9552       return FALSE;
9553
9554     if (currentMove >= forwardMostMove) {
9555       gameMode = EditGame;
9556       ModeHighlight();
9557
9558       /* [AS] Clear current move marker at the end of a game */
9559       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9560
9561       return FALSE;
9562     }
9563     
9564     toX = moveList[currentMove][2] - AAA;
9565     toY = moveList[currentMove][3] - ONE;
9566
9567     if (moveList[currentMove][1] == '@') {
9568         if (appData.highlightLastMove) {
9569             SetHighlights(-1, -1, toX, toY);
9570         }
9571     } else {
9572         fromX = moveList[currentMove][0] - AAA;
9573         fromY = moveList[currentMove][1] - ONE;
9574
9575         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9576
9577         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9578
9579         if (appData.highlightLastMove) {
9580             SetHighlights(fromX, fromY, toX, toY);
9581         }
9582     }
9583     DisplayMove(currentMove);
9584     SendMoveToProgram(currentMove++, &first);
9585     DisplayBothClocks();
9586     DrawPosition(FALSE, boards[currentMove]);
9587     // [HGM] PV info: always display, routine tests if empty
9588     DisplayComment(currentMove - 1, commentList[currentMove]);
9589     return TRUE;
9590 }
9591
9592
9593 int
9594 LoadGameOneMove(readAhead)
9595      ChessMove readAhead;
9596 {
9597     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9598     char promoChar = NULLCHAR;
9599     ChessMove moveType;
9600     char move[MSG_SIZ];
9601     char *p, *q;
9602     
9603     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9604         gameMode != AnalyzeMode && gameMode != Training) {
9605         gameFileFP = NULL;
9606         return FALSE;
9607     }
9608     
9609     yyboardindex = forwardMostMove;
9610     if (readAhead != (ChessMove)0) {
9611       moveType = readAhead;
9612     } else {
9613       if (gameFileFP == NULL)
9614           return FALSE;
9615       moveType = (ChessMove) yylex();
9616     }
9617     
9618     done = FALSE;
9619     switch (moveType) {
9620       case Comment:
9621         if (appData.debugMode) 
9622           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9623         p = yy_text;
9624
9625         /* append the comment but don't display it */
9626         AppendComment(currentMove, p, FALSE);
9627         return TRUE;
9628
9629       case WhiteCapturesEnPassant:
9630       case BlackCapturesEnPassant:
9631       case WhitePromotionChancellor:
9632       case BlackPromotionChancellor:
9633       case WhitePromotionArchbishop:
9634       case BlackPromotionArchbishop:
9635       case WhitePromotionCentaur:
9636       case BlackPromotionCentaur:
9637       case WhitePromotionQueen:
9638       case BlackPromotionQueen:
9639       case WhitePromotionRook:
9640       case BlackPromotionRook:
9641       case WhitePromotionBishop:
9642       case BlackPromotionBishop:
9643       case WhitePromotionKnight:
9644       case BlackPromotionKnight:
9645       case WhitePromotionKing:
9646       case BlackPromotionKing:
9647       case NormalMove:
9648       case WhiteKingSideCastle:
9649       case WhiteQueenSideCastle:
9650       case BlackKingSideCastle:
9651       case BlackQueenSideCastle:
9652       case WhiteKingSideCastleWild:
9653       case WhiteQueenSideCastleWild:
9654       case BlackKingSideCastleWild:
9655       case BlackQueenSideCastleWild:
9656       /* PUSH Fabien */
9657       case WhiteHSideCastleFR:
9658       case WhiteASideCastleFR:
9659       case BlackHSideCastleFR:
9660       case BlackASideCastleFR:
9661       /* POP Fabien */
9662         if (appData.debugMode)
9663           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9664         fromX = currentMoveString[0] - AAA;
9665         fromY = currentMoveString[1] - ONE;
9666         toX = currentMoveString[2] - AAA;
9667         toY = currentMoveString[3] - ONE;
9668         promoChar = currentMoveString[4];
9669         break;
9670
9671       case WhiteDrop:
9672       case BlackDrop:
9673         if (appData.debugMode)
9674           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9675         fromX = moveType == WhiteDrop ?
9676           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9677         (int) CharToPiece(ToLower(currentMoveString[0]));
9678         fromY = DROP_RANK;
9679         toX = currentMoveString[2] - AAA;
9680         toY = currentMoveString[3] - ONE;
9681         break;
9682
9683       case WhiteWins:
9684       case BlackWins:
9685       case GameIsDrawn:
9686       case GameUnfinished:
9687         if (appData.debugMode)
9688           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9689         p = strchr(yy_text, '{');
9690         if (p == NULL) p = strchr(yy_text, '(');
9691         if (p == NULL) {
9692             p = yy_text;
9693             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9694         } else {
9695             q = strchr(p, *p == '{' ? '}' : ')');
9696             if (q != NULL) *q = NULLCHAR;
9697             p++;
9698         }
9699         GameEnds(moveType, p, GE_FILE);
9700         done = TRUE;
9701         if (cmailMsgLoaded) {
9702             ClearHighlights();
9703             flipView = WhiteOnMove(currentMove);
9704             if (moveType == GameUnfinished) flipView = !flipView;
9705             if (appData.debugMode)
9706               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9707         }
9708         break;
9709
9710       case (ChessMove) 0:       /* end of file */
9711         if (appData.debugMode)
9712           fprintf(debugFP, "Parser hit end of file\n");
9713         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9714           case MT_NONE:
9715           case MT_CHECK:
9716             break;
9717           case MT_CHECKMATE:
9718           case MT_STAINMATE:
9719             if (WhiteOnMove(currentMove)) {
9720                 GameEnds(BlackWins, "Black mates", GE_FILE);
9721             } else {
9722                 GameEnds(WhiteWins, "White mates", GE_FILE);
9723             }
9724             break;
9725           case MT_STALEMATE:
9726             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9727             break;
9728         }
9729         done = TRUE;
9730         break;
9731
9732       case MoveNumberOne:
9733         if (lastLoadGameStart == GNUChessGame) {
9734             /* GNUChessGames have numbers, but they aren't move numbers */
9735             if (appData.debugMode)
9736               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9737                       yy_text, (int) moveType);
9738             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9739         }
9740         /* else fall thru */
9741
9742       case XBoardGame:
9743       case GNUChessGame:
9744       case PGNTag:
9745         /* Reached start of next game in file */
9746         if (appData.debugMode)
9747           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9748         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9749           case MT_NONE:
9750           case MT_CHECK:
9751             break;
9752           case MT_CHECKMATE:
9753           case MT_STAINMATE:
9754             if (WhiteOnMove(currentMove)) {
9755                 GameEnds(BlackWins, "Black mates", GE_FILE);
9756             } else {
9757                 GameEnds(WhiteWins, "White mates", GE_FILE);
9758             }
9759             break;
9760           case MT_STALEMATE:
9761             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9762             break;
9763         }
9764         done = TRUE;
9765         break;
9766
9767       case PositionDiagram:     /* should not happen; ignore */
9768       case ElapsedTime:         /* ignore */
9769       case NAG:                 /* ignore */
9770         if (appData.debugMode)
9771           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9772                   yy_text, (int) moveType);
9773         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9774
9775       case IllegalMove:
9776         if (appData.testLegality) {
9777             if (appData.debugMode)
9778               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9779             sprintf(move, _("Illegal move: %d.%s%s"),
9780                     (forwardMostMove / 2) + 1,
9781                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9782             DisplayError(move, 0);
9783             done = TRUE;
9784         } else {
9785             if (appData.debugMode)
9786               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9787                       yy_text, currentMoveString);
9788             fromX = currentMoveString[0] - AAA;
9789             fromY = currentMoveString[1] - ONE;
9790             toX = currentMoveString[2] - AAA;
9791             toY = currentMoveString[3] - ONE;
9792             promoChar = currentMoveString[4];
9793         }
9794         break;
9795
9796       case AmbiguousMove:
9797         if (appData.debugMode)
9798           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9799         sprintf(move, _("Ambiguous move: %d.%s%s"),
9800                 (forwardMostMove / 2) + 1,
9801                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9802         DisplayError(move, 0);
9803         done = TRUE;
9804         break;
9805
9806       default:
9807       case ImpossibleMove:
9808         if (appData.debugMode)
9809           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9810         sprintf(move, _("Illegal move: %d.%s%s"),
9811                 (forwardMostMove / 2) + 1,
9812                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9813         DisplayError(move, 0);
9814         done = TRUE;
9815         break;
9816     }
9817
9818     if (done) {
9819         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9820             DrawPosition(FALSE, boards[currentMove]);
9821             DisplayBothClocks();
9822             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9823               DisplayComment(currentMove - 1, commentList[currentMove]);
9824         }
9825         (void) StopLoadGameTimer();
9826         gameFileFP = NULL;
9827         cmailOldMove = forwardMostMove;
9828         return FALSE;
9829     } else {
9830         /* currentMoveString is set as a side-effect of yylex */
9831         strcat(currentMoveString, "\n");
9832         strcpy(moveList[forwardMostMove], currentMoveString);
9833         
9834         thinkOutput[0] = NULLCHAR;
9835         MakeMove(fromX, fromY, toX, toY, promoChar);
9836         currentMove = forwardMostMove;
9837         return TRUE;
9838     }
9839 }
9840
9841 /* Load the nth game from the given file */
9842 int
9843 LoadGameFromFile(filename, n, title, useList)
9844      char *filename;
9845      int n;
9846      char *title;
9847      /*Boolean*/ int useList;
9848 {
9849     FILE *f;
9850     char buf[MSG_SIZ];
9851
9852     if (strcmp(filename, "-") == 0) {
9853         f = stdin;
9854         title = "stdin";
9855     } else {
9856         f = fopen(filename, "rb");
9857         if (f == NULL) {
9858           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9859             DisplayError(buf, errno);
9860             return FALSE;
9861         }
9862     }
9863     if (fseek(f, 0, 0) == -1) {
9864         /* f is not seekable; probably a pipe */
9865         useList = FALSE;
9866     }
9867     if (useList && n == 0) {
9868         int error = GameListBuild(f);
9869         if (error) {
9870             DisplayError(_("Cannot build game list"), error);
9871         } else if (!ListEmpty(&gameList) &&
9872                    ((ListGame *) gameList.tailPred)->number > 1) {
9873             GameListPopUp(f, title);
9874             return TRUE;
9875         }
9876         GameListDestroy();
9877         n = 1;
9878     }
9879     if (n == 0) n = 1;
9880     return LoadGame(f, n, title, FALSE);
9881 }
9882
9883
9884 void
9885 MakeRegisteredMove()
9886 {
9887     int fromX, fromY, toX, toY;
9888     char promoChar;
9889     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9890         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9891           case CMAIL_MOVE:
9892           case CMAIL_DRAW:
9893             if (appData.debugMode)
9894               fprintf(debugFP, "Restoring %s for game %d\n",
9895                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9896     
9897             thinkOutput[0] = NULLCHAR;
9898             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9899             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9900             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9901             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9902             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9903             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9904             MakeMove(fromX, fromY, toX, toY, promoChar);
9905             ShowMove(fromX, fromY, toX, toY);
9906               
9907             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9908               case MT_NONE:
9909               case MT_CHECK:
9910                 break;
9911                 
9912               case MT_CHECKMATE:
9913               case MT_STAINMATE:
9914                 if (WhiteOnMove(currentMove)) {
9915                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9916                 } else {
9917                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9918                 }
9919                 break;
9920                 
9921               case MT_STALEMATE:
9922                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9923                 break;
9924             }
9925
9926             break;
9927             
9928           case CMAIL_RESIGN:
9929             if (WhiteOnMove(currentMove)) {
9930                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9931             } else {
9932                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9933             }
9934             break;
9935             
9936           case CMAIL_ACCEPT:
9937             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9938             break;
9939               
9940           default:
9941             break;
9942         }
9943     }
9944
9945     return;
9946 }
9947
9948 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9949 int
9950 CmailLoadGame(f, gameNumber, title, useList)
9951      FILE *f;
9952      int gameNumber;
9953      char *title;
9954      int useList;
9955 {
9956     int retVal;
9957
9958     if (gameNumber > nCmailGames) {
9959         DisplayError(_("No more games in this message"), 0);
9960         return FALSE;
9961     }
9962     if (f == lastLoadGameFP) {
9963         int offset = gameNumber - lastLoadGameNumber;
9964         if (offset == 0) {
9965             cmailMsg[0] = NULLCHAR;
9966             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9967                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9968                 nCmailMovesRegistered--;
9969             }
9970             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9971             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9972                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9973             }
9974         } else {
9975             if (! RegisterMove()) return FALSE;
9976         }
9977     }
9978
9979     retVal = LoadGame(f, gameNumber, title, useList);
9980
9981     /* Make move registered during previous look at this game, if any */
9982     MakeRegisteredMove();
9983
9984     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9985         commentList[currentMove]
9986           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9987         DisplayComment(currentMove - 1, commentList[currentMove]);
9988     }
9989
9990     return retVal;
9991 }
9992
9993 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9994 int
9995 ReloadGame(offset)
9996      int offset;
9997 {
9998     int gameNumber = lastLoadGameNumber + offset;
9999     if (lastLoadGameFP == NULL) {
10000         DisplayError(_("No game has been loaded yet"), 0);
10001         return FALSE;
10002     }
10003     if (gameNumber <= 0) {
10004         DisplayError(_("Can't back up any further"), 0);
10005         return FALSE;
10006     }
10007     if (cmailMsgLoaded) {
10008         return CmailLoadGame(lastLoadGameFP, gameNumber,
10009                              lastLoadGameTitle, lastLoadGameUseList);
10010     } else {
10011         return LoadGame(lastLoadGameFP, gameNumber,
10012                         lastLoadGameTitle, lastLoadGameUseList);
10013     }
10014 }
10015
10016
10017
10018 /* Load the nth game from open file f */
10019 int
10020 LoadGame(f, gameNumber, title, useList)
10021      FILE *f;
10022      int gameNumber;
10023      char *title;
10024      int useList;
10025 {
10026     ChessMove cm;
10027     char buf[MSG_SIZ];
10028     int gn = gameNumber;
10029     ListGame *lg = NULL;
10030     int numPGNTags = 0;
10031     int err;
10032     GameMode oldGameMode;
10033     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10034
10035     if (appData.debugMode) 
10036         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10037
10038     if (gameMode == Training )
10039         SetTrainingModeOff();
10040
10041     oldGameMode = gameMode;
10042     if (gameMode != BeginningOfGame) {
10043       Reset(FALSE, TRUE);
10044     }
10045
10046     gameFileFP = f;
10047     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10048         fclose(lastLoadGameFP);
10049     }
10050
10051     if (useList) {
10052         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10053         
10054         if (lg) {
10055             fseek(f, lg->offset, 0);
10056             GameListHighlight(gameNumber);
10057             gn = 1;
10058         }
10059         else {
10060             DisplayError(_("Game number out of range"), 0);
10061             return FALSE;
10062         }
10063     } else {
10064         GameListDestroy();
10065         if (fseek(f, 0, 0) == -1) {
10066             if (f == lastLoadGameFP ?
10067                 gameNumber == lastLoadGameNumber + 1 :
10068                 gameNumber == 1) {
10069                 gn = 1;
10070             } else {
10071                 DisplayError(_("Can't seek on game file"), 0);
10072                 return FALSE;
10073             }
10074         }
10075     }
10076     lastLoadGameFP = f;
10077     lastLoadGameNumber = gameNumber;
10078     strcpy(lastLoadGameTitle, title);
10079     lastLoadGameUseList = useList;
10080
10081     yynewfile(f);
10082
10083     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10084       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10085                 lg->gameInfo.black);
10086             DisplayTitle(buf);
10087     } else if (*title != NULLCHAR) {
10088         if (gameNumber > 1) {
10089             sprintf(buf, "%s %d", title, gameNumber);
10090             DisplayTitle(buf);
10091         } else {
10092             DisplayTitle(title);
10093         }
10094     }
10095
10096     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10097         gameMode = PlayFromGameFile;
10098         ModeHighlight();
10099     }
10100
10101     currentMove = forwardMostMove = backwardMostMove = 0;
10102     CopyBoard(boards[0], initialPosition);
10103     StopClocks();
10104
10105     /*
10106      * Skip the first gn-1 games in the file.
10107      * Also skip over anything that precedes an identifiable 
10108      * start of game marker, to avoid being confused by 
10109      * garbage at the start of the file.  Currently 
10110      * recognized start of game markers are the move number "1",
10111      * the pattern "gnuchess .* game", the pattern
10112      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10113      * A game that starts with one of the latter two patterns
10114      * will also have a move number 1, possibly
10115      * following a position diagram.
10116      * 5-4-02: Let's try being more lenient and allowing a game to
10117      * start with an unnumbered move.  Does that break anything?
10118      */
10119     cm = lastLoadGameStart = (ChessMove) 0;
10120     while (gn > 0) {
10121         yyboardindex = forwardMostMove;
10122         cm = (ChessMove) yylex();
10123         switch (cm) {
10124           case (ChessMove) 0:
10125             if (cmailMsgLoaded) {
10126                 nCmailGames = CMAIL_MAX_GAMES - gn;
10127             } else {
10128                 Reset(TRUE, TRUE);
10129                 DisplayError(_("Game not found in file"), 0);
10130             }
10131             return FALSE;
10132
10133           case GNUChessGame:
10134           case XBoardGame:
10135             gn--;
10136             lastLoadGameStart = cm;
10137             break;
10138             
10139           case MoveNumberOne:
10140             switch (lastLoadGameStart) {
10141               case GNUChessGame:
10142               case XBoardGame:
10143               case PGNTag:
10144                 break;
10145               case MoveNumberOne:
10146               case (ChessMove) 0:
10147                 gn--;           /* count this game */
10148                 lastLoadGameStart = cm;
10149                 break;
10150               default:
10151                 /* impossible */
10152                 break;
10153             }
10154             break;
10155
10156           case PGNTag:
10157             switch (lastLoadGameStart) {
10158               case GNUChessGame:
10159               case PGNTag:
10160               case MoveNumberOne:
10161               case (ChessMove) 0:
10162                 gn--;           /* count this game */
10163                 lastLoadGameStart = cm;
10164                 break;
10165               case XBoardGame:
10166                 lastLoadGameStart = cm; /* game counted already */
10167                 break;
10168               default:
10169                 /* impossible */
10170                 break;
10171             }
10172             if (gn > 0) {
10173                 do {
10174                     yyboardindex = forwardMostMove;
10175                     cm = (ChessMove) yylex();
10176                 } while (cm == PGNTag || cm == Comment);
10177             }
10178             break;
10179
10180           case WhiteWins:
10181           case BlackWins:
10182           case GameIsDrawn:
10183             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10184                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10185                     != CMAIL_OLD_RESULT) {
10186                     nCmailResults ++ ;
10187                     cmailResult[  CMAIL_MAX_GAMES
10188                                 - gn - 1] = CMAIL_OLD_RESULT;
10189                 }
10190             }
10191             break;
10192
10193           case NormalMove:
10194             /* Only a NormalMove can be at the start of a game
10195              * without a position diagram. */
10196             if (lastLoadGameStart == (ChessMove) 0) {
10197               gn--;
10198               lastLoadGameStart = MoveNumberOne;
10199             }
10200             break;
10201
10202           default:
10203             break;
10204         }
10205     }
10206     
10207     if (appData.debugMode)
10208       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10209
10210     if (cm == XBoardGame) {
10211         /* Skip any header junk before position diagram and/or move 1 */
10212         for (;;) {
10213             yyboardindex = forwardMostMove;
10214             cm = (ChessMove) yylex();
10215
10216             if (cm == (ChessMove) 0 ||
10217                 cm == GNUChessGame || cm == XBoardGame) {
10218                 /* Empty game; pretend end-of-file and handle later */
10219                 cm = (ChessMove) 0;
10220                 break;
10221             }
10222
10223             if (cm == MoveNumberOne || cm == PositionDiagram ||
10224                 cm == PGNTag || cm == Comment)
10225               break;
10226         }
10227     } else if (cm == GNUChessGame) {
10228         if (gameInfo.event != NULL) {
10229             free(gameInfo.event);
10230         }
10231         gameInfo.event = StrSave(yy_text);
10232     }   
10233
10234     startedFromSetupPosition = FALSE;
10235     while (cm == PGNTag) {
10236         if (appData.debugMode) 
10237           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10238         err = ParsePGNTag(yy_text, &gameInfo);
10239         if (!err) numPGNTags++;
10240
10241         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10242         if(gameInfo.variant != oldVariant) {
10243             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10244             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10245             InitPosition(TRUE);
10246             oldVariant = gameInfo.variant;
10247             if (appData.debugMode) 
10248               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10249         }
10250
10251
10252         if (gameInfo.fen != NULL) {
10253           Board initial_position;
10254           startedFromSetupPosition = TRUE;
10255           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10256             Reset(TRUE, TRUE);
10257             DisplayError(_("Bad FEN position in file"), 0);
10258             return FALSE;
10259           }
10260           CopyBoard(boards[0], initial_position);
10261           if (blackPlaysFirst) {
10262             currentMove = forwardMostMove = backwardMostMove = 1;
10263             CopyBoard(boards[1], initial_position);
10264             strcpy(moveList[0], "");
10265             strcpy(parseList[0], "");
10266             timeRemaining[0][1] = whiteTimeRemaining;
10267             timeRemaining[1][1] = blackTimeRemaining;
10268             if (commentList[0] != NULL) {
10269               commentList[1] = commentList[0];
10270               commentList[0] = NULL;
10271             }
10272           } else {
10273             currentMove = forwardMostMove = backwardMostMove = 0;
10274           }
10275           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10276           {   int i;
10277               initialRulePlies = FENrulePlies;
10278               for( i=0; i< nrCastlingRights; i++ )
10279                   initialRights[i] = initial_position[CASTLING][i];
10280           }
10281           yyboardindex = forwardMostMove;
10282           free(gameInfo.fen);
10283           gameInfo.fen = NULL;
10284         }
10285
10286         yyboardindex = forwardMostMove;
10287         cm = (ChessMove) yylex();
10288
10289         /* Handle comments interspersed among the tags */
10290         while (cm == Comment) {
10291             char *p;
10292             if (appData.debugMode) 
10293               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10294             p = yy_text;
10295             AppendComment(currentMove, p, FALSE);
10296             yyboardindex = forwardMostMove;
10297             cm = (ChessMove) yylex();
10298         }
10299     }
10300
10301     /* don't rely on existence of Event tag since if game was
10302      * pasted from clipboard the Event tag may not exist
10303      */
10304     if (numPGNTags > 0){
10305         char *tags;
10306         if (gameInfo.variant == VariantNormal) {
10307           VariantClass v = StringToVariant(gameInfo.event);
10308           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10309           if(v < VariantShogi) gameInfo.variant = v;
10310         }
10311         if (!matchMode) {
10312           if( appData.autoDisplayTags ) {
10313             tags = PGNTags(&gameInfo);
10314             TagsPopUp(tags, CmailMsg());
10315             free(tags);
10316           }
10317         }
10318     } else {
10319         /* Make something up, but don't display it now */
10320         SetGameInfo();
10321         TagsPopDown();
10322     }
10323
10324     if (cm == PositionDiagram) {
10325         int i, j;
10326         char *p;
10327         Board initial_position;
10328
10329         if (appData.debugMode)
10330           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10331
10332         if (!startedFromSetupPosition) {
10333             p = yy_text;
10334             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10335               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10336                 switch (*p) {
10337                   case '[':
10338                   case '-':
10339                   case ' ':
10340                   case '\t':
10341                   case '\n':
10342                   case '\r':
10343                     break;
10344                   default:
10345                     initial_position[i][j++] = CharToPiece(*p);
10346                     break;
10347                 }
10348             while (*p == ' ' || *p == '\t' ||
10349                    *p == '\n' || *p == '\r') p++;
10350         
10351             if (strncmp(p, "black", strlen("black"))==0)
10352               blackPlaysFirst = TRUE;
10353             else
10354               blackPlaysFirst = FALSE;
10355             startedFromSetupPosition = TRUE;
10356         
10357             CopyBoard(boards[0], initial_position);
10358             if (blackPlaysFirst) {
10359                 currentMove = forwardMostMove = backwardMostMove = 1;
10360                 CopyBoard(boards[1], initial_position);
10361                 strcpy(moveList[0], "");
10362                 strcpy(parseList[0], "");
10363                 timeRemaining[0][1] = whiteTimeRemaining;
10364                 timeRemaining[1][1] = blackTimeRemaining;
10365                 if (commentList[0] != NULL) {
10366                     commentList[1] = commentList[0];
10367                     commentList[0] = NULL;
10368                 }
10369             } else {
10370                 currentMove = forwardMostMove = backwardMostMove = 0;
10371             }
10372         }
10373         yyboardindex = forwardMostMove;
10374         cm = (ChessMove) yylex();
10375     }
10376
10377     if (first.pr == NoProc) {
10378         StartChessProgram(&first);
10379     }
10380     InitChessProgram(&first, FALSE);
10381     SendToProgram("force\n", &first);
10382     if (startedFromSetupPosition) {
10383         SendBoard(&first, forwardMostMove);
10384     if (appData.debugMode) {
10385         fprintf(debugFP, "Load Game\n");
10386     }
10387         DisplayBothClocks();
10388     }      
10389
10390     /* [HGM] server: flag to write setup moves in broadcast file as one */
10391     loadFlag = appData.suppressLoadMoves;
10392
10393     while (cm == Comment) {
10394         char *p;
10395         if (appData.debugMode) 
10396           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10397         p = yy_text;
10398         AppendComment(currentMove, p, FALSE);
10399         yyboardindex = forwardMostMove;
10400         cm = (ChessMove) yylex();
10401     }
10402
10403     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10404         cm == WhiteWins || cm == BlackWins ||
10405         cm == GameIsDrawn || cm == GameUnfinished) {
10406         DisplayMessage("", _("No moves in game"));
10407         if (cmailMsgLoaded) {
10408             if (appData.debugMode)
10409               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10410             ClearHighlights();
10411             flipView = FALSE;
10412         }
10413         DrawPosition(FALSE, boards[currentMove]);
10414         DisplayBothClocks();
10415         gameMode = EditGame;
10416         ModeHighlight();
10417         gameFileFP = NULL;
10418         cmailOldMove = 0;
10419         return TRUE;
10420     }
10421
10422     // [HGM] PV info: routine tests if comment empty
10423     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10424         DisplayComment(currentMove - 1, commentList[currentMove]);
10425     }
10426     if (!matchMode && appData.timeDelay != 0) 
10427       DrawPosition(FALSE, boards[currentMove]);
10428
10429     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10430       programStats.ok_to_send = 1;
10431     }
10432
10433     /* if the first token after the PGN tags is a move
10434      * and not move number 1, retrieve it from the parser 
10435      */
10436     if (cm != MoveNumberOne)
10437         LoadGameOneMove(cm);
10438
10439     /* load the remaining moves from the file */
10440     while (LoadGameOneMove((ChessMove)0)) {
10441       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10442       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10443     }
10444
10445     /* rewind to the start of the game */
10446     currentMove = backwardMostMove;
10447
10448     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10449
10450     if (oldGameMode == AnalyzeFile ||
10451         oldGameMode == AnalyzeMode) {
10452       AnalyzeFileEvent();
10453     }
10454
10455     if (matchMode || appData.timeDelay == 0) {
10456       ToEndEvent();
10457       gameMode = EditGame;
10458       ModeHighlight();
10459     } else if (appData.timeDelay > 0) {
10460       AutoPlayGameLoop();
10461     }
10462
10463     if (appData.debugMode) 
10464         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10465
10466     loadFlag = 0; /* [HGM] true game starts */
10467     return TRUE;
10468 }
10469
10470 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10471 int
10472 ReloadPosition(offset)
10473      int offset;
10474 {
10475     int positionNumber = lastLoadPositionNumber + offset;
10476     if (lastLoadPositionFP == NULL) {
10477         DisplayError(_("No position has been loaded yet"), 0);
10478         return FALSE;
10479     }
10480     if (positionNumber <= 0) {
10481         DisplayError(_("Can't back up any further"), 0);
10482         return FALSE;
10483     }
10484     return LoadPosition(lastLoadPositionFP, positionNumber,
10485                         lastLoadPositionTitle);
10486 }
10487
10488 /* Load the nth position from the given file */
10489 int
10490 LoadPositionFromFile(filename, n, title)
10491      char *filename;
10492      int n;
10493      char *title;
10494 {
10495     FILE *f;
10496     char buf[MSG_SIZ];
10497
10498     if (strcmp(filename, "-") == 0) {
10499         return LoadPosition(stdin, n, "stdin");
10500     } else {
10501         f = fopen(filename, "rb");
10502         if (f == NULL) {
10503             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10504             DisplayError(buf, errno);
10505             return FALSE;
10506         } else {
10507             return LoadPosition(f, n, title);
10508         }
10509     }
10510 }
10511
10512 /* Load the nth position from the given open file, and close it */
10513 int
10514 LoadPosition(f, positionNumber, title)
10515      FILE *f;
10516      int positionNumber;
10517      char *title;
10518 {
10519     char *p, line[MSG_SIZ];
10520     Board initial_position;
10521     int i, j, fenMode, pn;
10522     
10523     if (gameMode == Training )
10524         SetTrainingModeOff();
10525
10526     if (gameMode != BeginningOfGame) {
10527         Reset(FALSE, TRUE);
10528     }
10529     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10530         fclose(lastLoadPositionFP);
10531     }
10532     if (positionNumber == 0) positionNumber = 1;
10533     lastLoadPositionFP = f;
10534     lastLoadPositionNumber = positionNumber;
10535     strcpy(lastLoadPositionTitle, title);
10536     if (first.pr == NoProc) {
10537       StartChessProgram(&first);
10538       InitChessProgram(&first, FALSE);
10539     }    
10540     pn = positionNumber;
10541     if (positionNumber < 0) {
10542         /* Negative position number means to seek to that byte offset */
10543         if (fseek(f, -positionNumber, 0) == -1) {
10544             DisplayError(_("Can't seek on position file"), 0);
10545             return FALSE;
10546         };
10547         pn = 1;
10548     } else {
10549         if (fseek(f, 0, 0) == -1) {
10550             if (f == lastLoadPositionFP ?
10551                 positionNumber == lastLoadPositionNumber + 1 :
10552                 positionNumber == 1) {
10553                 pn = 1;
10554             } else {
10555                 DisplayError(_("Can't seek on position file"), 0);
10556                 return FALSE;
10557             }
10558         }
10559     }
10560     /* See if this file is FEN or old-style xboard */
10561     if (fgets(line, MSG_SIZ, f) == NULL) {
10562         DisplayError(_("Position not found in file"), 0);
10563         return FALSE;
10564     }
10565     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10566     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10567
10568     if (pn >= 2) {
10569         if (fenMode || line[0] == '#') pn--;
10570         while (pn > 0) {
10571             /* skip positions before number pn */
10572             if (fgets(line, MSG_SIZ, f) == NULL) {
10573                 Reset(TRUE, TRUE);
10574                 DisplayError(_("Position not found in file"), 0);
10575                 return FALSE;
10576             }
10577             if (fenMode || line[0] == '#') pn--;
10578         }
10579     }
10580
10581     if (fenMode) {
10582         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10583             DisplayError(_("Bad FEN position in file"), 0);
10584             return FALSE;
10585         }
10586     } else {
10587         (void) fgets(line, MSG_SIZ, f);
10588         (void) fgets(line, MSG_SIZ, f);
10589     
10590         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10591             (void) fgets(line, MSG_SIZ, f);
10592             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10593                 if (*p == ' ')
10594                   continue;
10595                 initial_position[i][j++] = CharToPiece(*p);
10596             }
10597         }
10598     
10599         blackPlaysFirst = FALSE;
10600         if (!feof(f)) {
10601             (void) fgets(line, MSG_SIZ, f);
10602             if (strncmp(line, "black", strlen("black"))==0)
10603               blackPlaysFirst = TRUE;
10604         }
10605     }
10606     startedFromSetupPosition = TRUE;
10607     
10608     SendToProgram("force\n", &first);
10609     CopyBoard(boards[0], initial_position);
10610     if (blackPlaysFirst) {
10611         currentMove = forwardMostMove = backwardMostMove = 1;
10612         strcpy(moveList[0], "");
10613         strcpy(parseList[0], "");
10614         CopyBoard(boards[1], initial_position);
10615         DisplayMessage("", _("Black to play"));
10616     } else {
10617         currentMove = forwardMostMove = backwardMostMove = 0;
10618         DisplayMessage("", _("White to play"));
10619     }
10620     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10621     SendBoard(&first, forwardMostMove);
10622     if (appData.debugMode) {
10623 int i, j;
10624   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10625   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10626         fprintf(debugFP, "Load Position\n");
10627     }
10628
10629     if (positionNumber > 1) {
10630         sprintf(line, "%s %d", title, positionNumber);
10631         DisplayTitle(line);
10632     } else {
10633         DisplayTitle(title);
10634     }
10635     gameMode = EditGame;
10636     ModeHighlight();
10637     ResetClocks();
10638     timeRemaining[0][1] = whiteTimeRemaining;
10639     timeRemaining[1][1] = blackTimeRemaining;
10640     DrawPosition(FALSE, boards[currentMove]);
10641    
10642     return TRUE;
10643 }
10644
10645
10646 void
10647 CopyPlayerNameIntoFileName(dest, src)
10648      char **dest, *src;
10649 {
10650     while (*src != NULLCHAR && *src != ',') {
10651         if (*src == ' ') {
10652             *(*dest)++ = '_';
10653             src++;
10654         } else {
10655             *(*dest)++ = *src++;
10656         }
10657     }
10658 }
10659
10660 char *DefaultFileName(ext)
10661      char *ext;
10662 {
10663     static char def[MSG_SIZ];
10664     char *p;
10665
10666     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10667         p = def;
10668         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10669         *p++ = '-';
10670         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10671         *p++ = '.';
10672         strcpy(p, ext);
10673     } else {
10674         def[0] = NULLCHAR;
10675     }
10676     return def;
10677 }
10678
10679 /* Save the current game to the given file */
10680 int
10681 SaveGameToFile(filename, append)
10682      char *filename;
10683      int append;
10684 {
10685     FILE *f;
10686     char buf[MSG_SIZ];
10687
10688     if (strcmp(filename, "-") == 0) {
10689         return SaveGame(stdout, 0, NULL);
10690     } else {
10691         f = fopen(filename, append ? "a" : "w");
10692         if (f == NULL) {
10693             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10694             DisplayError(buf, errno);
10695             return FALSE;
10696         } else {
10697             return SaveGame(f, 0, NULL);
10698         }
10699     }
10700 }
10701
10702 char *
10703 SavePart(str)
10704      char *str;
10705 {
10706     static char buf[MSG_SIZ];
10707     char *p;
10708     
10709     p = strchr(str, ' ');
10710     if (p == NULL) return str;
10711     strncpy(buf, str, p - str);
10712     buf[p - str] = NULLCHAR;
10713     return buf;
10714 }
10715
10716 #define PGN_MAX_LINE 75
10717
10718 #define PGN_SIDE_WHITE  0
10719 #define PGN_SIDE_BLACK  1
10720
10721 /* [AS] */
10722 static int FindFirstMoveOutOfBook( int side )
10723 {
10724     int result = -1;
10725
10726     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10727         int index = backwardMostMove;
10728         int has_book_hit = 0;
10729
10730         if( (index % 2) != side ) {
10731             index++;
10732         }
10733
10734         while( index < forwardMostMove ) {
10735             /* Check to see if engine is in book */
10736             int depth = pvInfoList[index].depth;
10737             int score = pvInfoList[index].score;
10738             int in_book = 0;
10739
10740             if( depth <= 2 ) {
10741                 in_book = 1;
10742             }
10743             else if( score == 0 && depth == 63 ) {
10744                 in_book = 1; /* Zappa */
10745             }
10746             else if( score == 2 && depth == 99 ) {
10747                 in_book = 1; /* Abrok */
10748             }
10749
10750             has_book_hit += in_book;
10751
10752             if( ! in_book ) {
10753                 result = index;
10754
10755                 break;
10756             }
10757
10758             index += 2;
10759         }
10760     }
10761
10762     return result;
10763 }
10764
10765 /* [AS] */
10766 void GetOutOfBookInfo( char * buf )
10767 {
10768     int oob[2];
10769     int i;
10770     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10771
10772     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10773     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10774
10775     *buf = '\0';
10776
10777     if( oob[0] >= 0 || oob[1] >= 0 ) {
10778         for( i=0; i<2; i++ ) {
10779             int idx = oob[i];
10780
10781             if( idx >= 0 ) {
10782                 if( i > 0 && oob[0] >= 0 ) {
10783                     strcat( buf, "   " );
10784                 }
10785
10786                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10787                 sprintf( buf+strlen(buf), "%s%.2f", 
10788                     pvInfoList[idx].score >= 0 ? "+" : "",
10789                     pvInfoList[idx].score / 100.0 );
10790             }
10791         }
10792     }
10793 }
10794
10795 /* Save game in PGN style and close the file */
10796 int
10797 SaveGamePGN(f)
10798      FILE *f;
10799 {
10800     int i, offset, linelen, newblock;
10801     time_t tm;
10802 //    char *movetext;
10803     char numtext[32];
10804     int movelen, numlen, blank;
10805     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10806
10807     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10808     
10809     tm = time((time_t *) NULL);
10810     
10811     PrintPGNTags(f, &gameInfo);
10812     
10813     if (backwardMostMove > 0 || startedFromSetupPosition) {
10814         char *fen = PositionToFEN(backwardMostMove, NULL);
10815         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10816         fprintf(f, "\n{--------------\n");
10817         PrintPosition(f, backwardMostMove);
10818         fprintf(f, "--------------}\n");
10819         free(fen);
10820     }
10821     else {
10822         /* [AS] Out of book annotation */
10823         if( appData.saveOutOfBookInfo ) {
10824             char buf[64];
10825
10826             GetOutOfBookInfo( buf );
10827
10828             if( buf[0] != '\0' ) {
10829                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10830             }
10831         }
10832
10833         fprintf(f, "\n");
10834     }
10835
10836     i = backwardMostMove;
10837     linelen = 0;
10838     newblock = TRUE;
10839
10840     while (i < forwardMostMove) {
10841         /* Print comments preceding this move */
10842         if (commentList[i] != NULL) {
10843             if (linelen > 0) fprintf(f, "\n");
10844             fprintf(f, "%s", commentList[i]);
10845             linelen = 0;
10846             newblock = TRUE;
10847         }
10848
10849         /* Format move number */
10850         if ((i % 2) == 0) {
10851             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10852         } else {
10853             if (newblock) {
10854                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10855             } else {
10856                 numtext[0] = NULLCHAR;
10857             }
10858         }
10859         numlen = strlen(numtext);
10860         newblock = FALSE;
10861
10862         /* Print move number */
10863         blank = linelen > 0 && numlen > 0;
10864         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10865             fprintf(f, "\n");
10866             linelen = 0;
10867             blank = 0;
10868         }
10869         if (blank) {
10870             fprintf(f, " ");
10871             linelen++;
10872         }
10873         fprintf(f, "%s", numtext);
10874         linelen += numlen;
10875
10876         /* Get move */
10877         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10878         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10879
10880         /* Print move */
10881         blank = linelen > 0 && movelen > 0;
10882         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10883             fprintf(f, "\n");
10884             linelen = 0;
10885             blank = 0;
10886         }
10887         if (blank) {
10888             fprintf(f, " ");
10889             linelen++;
10890         }
10891         fprintf(f, "%s", move_buffer);
10892         linelen += movelen;
10893
10894         /* [AS] Add PV info if present */
10895         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10896             /* [HGM] add time */
10897             char buf[MSG_SIZ]; int seconds;
10898
10899             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10900
10901             if( seconds <= 0) buf[0] = 0; else
10902             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10903                 seconds = (seconds + 4)/10; // round to full seconds
10904                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10905                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10906             }
10907
10908             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10909                 pvInfoList[i].score >= 0 ? "+" : "",
10910                 pvInfoList[i].score / 100.0,
10911                 pvInfoList[i].depth,
10912                 buf );
10913
10914             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10915
10916             /* Print score/depth */
10917             blank = linelen > 0 && movelen > 0;
10918             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10919                 fprintf(f, "\n");
10920                 linelen = 0;
10921                 blank = 0;
10922             }
10923             if (blank) {
10924                 fprintf(f, " ");
10925                 linelen++;
10926             }
10927             fprintf(f, "%s", move_buffer);
10928             linelen += movelen;
10929         }
10930
10931         i++;
10932     }
10933     
10934     /* Start a new line */
10935     if (linelen > 0) fprintf(f, "\n");
10936
10937     /* Print comments after last move */
10938     if (commentList[i] != NULL) {
10939         fprintf(f, "%s\n", commentList[i]);
10940     }
10941
10942     /* Print result */
10943     if (gameInfo.resultDetails != NULL &&
10944         gameInfo.resultDetails[0] != NULLCHAR) {
10945         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10946                 PGNResult(gameInfo.result));
10947     } else {
10948         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10949     }
10950
10951     fclose(f);
10952     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10953     return TRUE;
10954 }
10955
10956 /* Save game in old style and close the file */
10957 int
10958 SaveGameOldStyle(f)
10959      FILE *f;
10960 {
10961     int i, offset;
10962     time_t tm;
10963     
10964     tm = time((time_t *) NULL);
10965     
10966     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10967     PrintOpponents(f);
10968     
10969     if (backwardMostMove > 0 || startedFromSetupPosition) {
10970         fprintf(f, "\n[--------------\n");
10971         PrintPosition(f, backwardMostMove);
10972         fprintf(f, "--------------]\n");
10973     } else {
10974         fprintf(f, "\n");
10975     }
10976
10977     i = backwardMostMove;
10978     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10979
10980     while (i < forwardMostMove) {
10981         if (commentList[i] != NULL) {
10982             fprintf(f, "[%s]\n", commentList[i]);
10983         }
10984
10985         if ((i % 2) == 1) {
10986             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10987             i++;
10988         } else {
10989             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10990             i++;
10991             if (commentList[i] != NULL) {
10992                 fprintf(f, "\n");
10993                 continue;
10994             }
10995             if (i >= forwardMostMove) {
10996                 fprintf(f, "\n");
10997                 break;
10998             }
10999             fprintf(f, "%s\n", parseList[i]);
11000             i++;
11001         }
11002     }
11003     
11004     if (commentList[i] != NULL) {
11005         fprintf(f, "[%s]\n", commentList[i]);
11006     }
11007
11008     /* This isn't really the old style, but it's close enough */
11009     if (gameInfo.resultDetails != NULL &&
11010         gameInfo.resultDetails[0] != NULLCHAR) {
11011         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11012                 gameInfo.resultDetails);
11013     } else {
11014         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11015     }
11016
11017     fclose(f);
11018     return TRUE;
11019 }
11020
11021 /* Save the current game to open file f and close the file */
11022 int
11023 SaveGame(f, dummy, dummy2)
11024      FILE *f;
11025      int dummy;
11026      char *dummy2;
11027 {
11028     if (gameMode == EditPosition) EditPositionDone(TRUE);
11029     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11030     if (appData.oldSaveStyle)
11031       return SaveGameOldStyle(f);
11032     else
11033       return SaveGamePGN(f);
11034 }
11035
11036 /* Save the current position to the given file */
11037 int
11038 SavePositionToFile(filename)
11039      char *filename;
11040 {
11041     FILE *f;
11042     char buf[MSG_SIZ];
11043
11044     if (strcmp(filename, "-") == 0) {
11045         return SavePosition(stdout, 0, NULL);
11046     } else {
11047         f = fopen(filename, "a");
11048         if (f == NULL) {
11049             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11050             DisplayError(buf, errno);
11051             return FALSE;
11052         } else {
11053             SavePosition(f, 0, NULL);
11054             return TRUE;
11055         }
11056     }
11057 }
11058
11059 /* Save the current position to the given open file and close the file */
11060 int
11061 SavePosition(f, dummy, dummy2)
11062      FILE *f;
11063      int dummy;
11064      char *dummy2;
11065 {
11066     time_t tm;
11067     char *fen;
11068     
11069     if (gameMode == EditPosition) EditPositionDone(TRUE);
11070     if (appData.oldSaveStyle) {
11071         tm = time((time_t *) NULL);
11072     
11073         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11074         PrintOpponents(f);
11075         fprintf(f, "[--------------\n");
11076         PrintPosition(f, currentMove);
11077         fprintf(f, "--------------]\n");
11078     } else {
11079         fen = PositionToFEN(currentMove, NULL);
11080         fprintf(f, "%s\n", fen);
11081         free(fen);
11082     }
11083     fclose(f);
11084     return TRUE;
11085 }
11086
11087 void
11088 ReloadCmailMsgEvent(unregister)
11089      int unregister;
11090 {
11091 #if !WIN32
11092     static char *inFilename = NULL;
11093     static char *outFilename;
11094     int i;
11095     struct stat inbuf, outbuf;
11096     int status;
11097     
11098     /* Any registered moves are unregistered if unregister is set, */
11099     /* i.e. invoked by the signal handler */
11100     if (unregister) {
11101         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11102             cmailMoveRegistered[i] = FALSE;
11103             if (cmailCommentList[i] != NULL) {
11104                 free(cmailCommentList[i]);
11105                 cmailCommentList[i] = NULL;
11106             }
11107         }
11108         nCmailMovesRegistered = 0;
11109     }
11110
11111     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11112         cmailResult[i] = CMAIL_NOT_RESULT;
11113     }
11114     nCmailResults = 0;
11115
11116     if (inFilename == NULL) {
11117         /* Because the filenames are static they only get malloced once  */
11118         /* and they never get freed                                      */
11119         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11120         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11121
11122         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11123         sprintf(outFilename, "%s.out", appData.cmailGameName);
11124     }
11125     
11126     status = stat(outFilename, &outbuf);
11127     if (status < 0) {
11128         cmailMailedMove = FALSE;
11129     } else {
11130         status = stat(inFilename, &inbuf);
11131         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11132     }
11133     
11134     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11135        counts the games, notes how each one terminated, etc.
11136        
11137        It would be nice to remove this kludge and instead gather all
11138        the information while building the game list.  (And to keep it
11139        in the game list nodes instead of having a bunch of fixed-size
11140        parallel arrays.)  Note this will require getting each game's
11141        termination from the PGN tags, as the game list builder does
11142        not process the game moves.  --mann
11143        */
11144     cmailMsgLoaded = TRUE;
11145     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11146     
11147     /* Load first game in the file or popup game menu */
11148     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11149
11150 #endif /* !WIN32 */
11151     return;
11152 }
11153
11154 int
11155 RegisterMove()
11156 {
11157     FILE *f;
11158     char string[MSG_SIZ];
11159
11160     if (   cmailMailedMove
11161         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11162         return TRUE;            /* Allow free viewing  */
11163     }
11164
11165     /* Unregister move to ensure that we don't leave RegisterMove        */
11166     /* with the move registered when the conditions for registering no   */
11167     /* longer hold                                                       */
11168     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11169         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11170         nCmailMovesRegistered --;
11171
11172         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11173           {
11174               free(cmailCommentList[lastLoadGameNumber - 1]);
11175               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11176           }
11177     }
11178
11179     if (cmailOldMove == -1) {
11180         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11181         return FALSE;
11182     }
11183
11184     if (currentMove > cmailOldMove + 1) {
11185         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11186         return FALSE;
11187     }
11188
11189     if (currentMove < cmailOldMove) {
11190         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11191         return FALSE;
11192     }
11193
11194     if (forwardMostMove > currentMove) {
11195         /* Silently truncate extra moves */
11196         TruncateGame();
11197     }
11198
11199     if (   (currentMove == cmailOldMove + 1)
11200         || (   (currentMove == cmailOldMove)
11201             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11202                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11203         if (gameInfo.result != GameUnfinished) {
11204             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11205         }
11206
11207         if (commentList[currentMove] != NULL) {
11208             cmailCommentList[lastLoadGameNumber - 1]
11209               = StrSave(commentList[currentMove]);
11210         }
11211         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11212
11213         if (appData.debugMode)
11214           fprintf(debugFP, "Saving %s for game %d\n",
11215                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11216
11217         sprintf(string,
11218                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11219         
11220         f = fopen(string, "w");
11221         if (appData.oldSaveStyle) {
11222             SaveGameOldStyle(f); /* also closes the file */
11223             
11224             sprintf(string, "%s.pos.out", appData.cmailGameName);
11225             f = fopen(string, "w");
11226             SavePosition(f, 0, NULL); /* also closes the file */
11227         } else {
11228             fprintf(f, "{--------------\n");
11229             PrintPosition(f, currentMove);
11230             fprintf(f, "--------------}\n\n");
11231             
11232             SaveGame(f, 0, NULL); /* also closes the file*/
11233         }
11234         
11235         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11236         nCmailMovesRegistered ++;
11237     } else if (nCmailGames == 1) {
11238         DisplayError(_("You have not made a move yet"), 0);
11239         return FALSE;
11240     }
11241
11242     return TRUE;
11243 }
11244
11245 void
11246 MailMoveEvent()
11247 {
11248 #if !WIN32
11249     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11250     FILE *commandOutput;
11251     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11252     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11253     int nBuffers;
11254     int i;
11255     int archived;
11256     char *arcDir;
11257
11258     if (! cmailMsgLoaded) {
11259         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11260         return;
11261     }
11262
11263     if (nCmailGames == nCmailResults) {
11264         DisplayError(_("No unfinished games"), 0);
11265         return;
11266     }
11267
11268 #if CMAIL_PROHIBIT_REMAIL
11269     if (cmailMailedMove) {
11270         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);
11271         DisplayError(msg, 0);
11272         return;
11273     }
11274 #endif
11275
11276     if (! (cmailMailedMove || RegisterMove())) return;
11277     
11278     if (   cmailMailedMove
11279         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11280         sprintf(string, partCommandString,
11281                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11282         commandOutput = popen(string, "r");
11283
11284         if (commandOutput == NULL) {
11285             DisplayError(_("Failed to invoke cmail"), 0);
11286         } else {
11287             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11288                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11289             }
11290             if (nBuffers > 1) {
11291                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11292                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11293                 nBytes = MSG_SIZ - 1;
11294             } else {
11295                 (void) memcpy(msg, buffer, nBytes);
11296             }
11297             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11298
11299             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11300                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11301
11302                 archived = TRUE;
11303                 for (i = 0; i < nCmailGames; i ++) {
11304                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11305                         archived = FALSE;
11306                     }
11307                 }
11308                 if (   archived
11309                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11310                         != NULL)) {
11311                     sprintf(buffer, "%s/%s.%s.archive",
11312                             arcDir,
11313                             appData.cmailGameName,
11314                             gameInfo.date);
11315                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11316                     cmailMsgLoaded = FALSE;
11317                 }
11318             }
11319
11320             DisplayInformation(msg);
11321             pclose(commandOutput);
11322         }
11323     } else {
11324         if ((*cmailMsg) != '\0') {
11325             DisplayInformation(cmailMsg);
11326         }
11327     }
11328
11329     return;
11330 #endif /* !WIN32 */
11331 }
11332
11333 char *
11334 CmailMsg()
11335 {
11336 #if WIN32
11337     return NULL;
11338 #else
11339     int  prependComma = 0;
11340     char number[5];
11341     char string[MSG_SIZ];       /* Space for game-list */
11342     int  i;
11343     
11344     if (!cmailMsgLoaded) return "";
11345
11346     if (cmailMailedMove) {
11347         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11348     } else {
11349         /* Create a list of games left */
11350         sprintf(string, "[");
11351         for (i = 0; i < nCmailGames; i ++) {
11352             if (! (   cmailMoveRegistered[i]
11353                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11354                 if (prependComma) {
11355                     sprintf(number, ",%d", i + 1);
11356                 } else {
11357                     sprintf(number, "%d", i + 1);
11358                     prependComma = 1;
11359                 }
11360                 
11361                 strcat(string, number);
11362             }
11363         }
11364         strcat(string, "]");
11365
11366         if (nCmailMovesRegistered + nCmailResults == 0) {
11367             switch (nCmailGames) {
11368               case 1:
11369                 sprintf(cmailMsg,
11370                         _("Still need to make move for game\n"));
11371                 break;
11372                 
11373               case 2:
11374                 sprintf(cmailMsg,
11375                         _("Still need to make moves for both games\n"));
11376                 break;
11377                 
11378               default:
11379                 sprintf(cmailMsg,
11380                         _("Still need to make moves for all %d games\n"),
11381                         nCmailGames);
11382                 break;
11383             }
11384         } else {
11385             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11386               case 1:
11387                 sprintf(cmailMsg,
11388                         _("Still need to make a move for game %s\n"),
11389                         string);
11390                 break;
11391                 
11392               case 0:
11393                 if (nCmailResults == nCmailGames) {
11394                     sprintf(cmailMsg, _("No unfinished games\n"));
11395                 } else {
11396                     sprintf(cmailMsg, _("Ready to send mail\n"));
11397                 }
11398                 break;
11399                 
11400               default:
11401                 sprintf(cmailMsg,
11402                         _("Still need to make moves for games %s\n"),
11403                         string);
11404             }
11405         }
11406     }
11407     return cmailMsg;
11408 #endif /* WIN32 */
11409 }
11410
11411 void
11412 ResetGameEvent()
11413 {
11414     if (gameMode == Training)
11415       SetTrainingModeOff();
11416
11417     Reset(TRUE, TRUE);
11418     cmailMsgLoaded = FALSE;
11419     if (appData.icsActive) {
11420       SendToICS(ics_prefix);
11421       SendToICS("refresh\n");
11422     }
11423 }
11424
11425 void
11426 ExitEvent(status)
11427      int status;
11428 {
11429     exiting++;
11430     if (exiting > 2) {
11431       /* Give up on clean exit */
11432       exit(status);
11433     }
11434     if (exiting > 1) {
11435       /* Keep trying for clean exit */
11436       return;
11437     }
11438
11439     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11440
11441     if (telnetISR != NULL) {
11442       RemoveInputSource(telnetISR);
11443     }
11444     if (icsPR != NoProc) {
11445       DestroyChildProcess(icsPR, TRUE);
11446     }
11447
11448     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11449     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11450
11451     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11452     /* make sure this other one finishes before killing it!                  */
11453     if(endingGame) { int count = 0;
11454         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11455         while(endingGame && count++ < 10) DoSleep(1);
11456         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11457     }
11458
11459     /* Kill off chess programs */
11460     if (first.pr != NoProc) {
11461         ExitAnalyzeMode();
11462         
11463         DoSleep( appData.delayBeforeQuit );
11464         SendToProgram("quit\n", &first);
11465         DoSleep( appData.delayAfterQuit );
11466         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11467     }
11468     if (second.pr != NoProc) {
11469         DoSleep( appData.delayBeforeQuit );
11470         SendToProgram("quit\n", &second);
11471         DoSleep( appData.delayAfterQuit );
11472         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11473     }
11474     if (first.isr != NULL) {
11475         RemoveInputSource(first.isr);
11476     }
11477     if (second.isr != NULL) {
11478         RemoveInputSource(second.isr);
11479     }
11480
11481     ShutDownFrontEnd();
11482     exit(status);
11483 }
11484
11485 void
11486 PauseEvent()
11487 {
11488     if (appData.debugMode)
11489         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11490     if (pausing) {
11491         pausing = FALSE;
11492         ModeHighlight();
11493         if (gameMode == MachinePlaysWhite ||
11494             gameMode == MachinePlaysBlack) {
11495             StartClocks();
11496         } else {
11497             DisplayBothClocks();
11498         }
11499         if (gameMode == PlayFromGameFile) {
11500             if (appData.timeDelay >= 0) 
11501                 AutoPlayGameLoop();
11502         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11503             Reset(FALSE, TRUE);
11504             SendToICS(ics_prefix);
11505             SendToICS("refresh\n");
11506         } else if (currentMove < forwardMostMove) {
11507             ForwardInner(forwardMostMove);
11508         }
11509         pauseExamInvalid = FALSE;
11510     } else {
11511         switch (gameMode) {
11512           default:
11513             return;
11514           case IcsExamining:
11515             pauseExamForwardMostMove = forwardMostMove;
11516             pauseExamInvalid = FALSE;
11517             /* fall through */
11518           case IcsObserving:
11519           case IcsPlayingWhite:
11520           case IcsPlayingBlack:
11521             pausing = TRUE;
11522             ModeHighlight();
11523             return;
11524           case PlayFromGameFile:
11525             (void) StopLoadGameTimer();
11526             pausing = TRUE;
11527             ModeHighlight();
11528             break;
11529           case BeginningOfGame:
11530             if (appData.icsActive) return;
11531             /* else fall through */
11532           case MachinePlaysWhite:
11533           case MachinePlaysBlack:
11534           case TwoMachinesPlay:
11535             if (forwardMostMove == 0)
11536               return;           /* don't pause if no one has moved */
11537             if ((gameMode == MachinePlaysWhite &&
11538                  !WhiteOnMove(forwardMostMove)) ||
11539                 (gameMode == MachinePlaysBlack &&
11540                  WhiteOnMove(forwardMostMove))) {
11541                 StopClocks();
11542             }
11543             pausing = TRUE;
11544             ModeHighlight();
11545             break;
11546         }
11547     }
11548 }
11549
11550 void
11551 EditCommentEvent()
11552 {
11553     char title[MSG_SIZ];
11554
11555     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11556         strcpy(title, _("Edit comment"));
11557     } else {
11558         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11559                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11560                 parseList[currentMove - 1]);
11561     }
11562
11563     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11564 }
11565
11566
11567 void
11568 EditTagsEvent()
11569 {
11570     char *tags = PGNTags(&gameInfo);
11571     EditTagsPopUp(tags);
11572     free(tags);
11573 }
11574
11575 void
11576 AnalyzeModeEvent()
11577 {
11578     if (appData.noChessProgram || gameMode == AnalyzeMode)
11579       return;
11580
11581     if (gameMode != AnalyzeFile) {
11582         if (!appData.icsEngineAnalyze) {
11583                EditGameEvent();
11584                if (gameMode != EditGame) return;
11585         }
11586         ResurrectChessProgram();
11587         SendToProgram("analyze\n", &first);
11588         first.analyzing = TRUE;
11589         /*first.maybeThinking = TRUE;*/
11590         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11591         EngineOutputPopUp();
11592     }
11593     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11594     pausing = FALSE;
11595     ModeHighlight();
11596     SetGameInfo();
11597
11598     StartAnalysisClock();
11599     GetTimeMark(&lastNodeCountTime);
11600     lastNodeCount = 0;
11601 }
11602
11603 void
11604 AnalyzeFileEvent()
11605 {
11606     if (appData.noChessProgram || gameMode == AnalyzeFile)
11607       return;
11608
11609     if (gameMode != AnalyzeMode) {
11610         EditGameEvent();
11611         if (gameMode != EditGame) return;
11612         ResurrectChessProgram();
11613         SendToProgram("analyze\n", &first);
11614         first.analyzing = TRUE;
11615         /*first.maybeThinking = TRUE;*/
11616         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11617         EngineOutputPopUp();
11618     }
11619     gameMode = AnalyzeFile;
11620     pausing = FALSE;
11621     ModeHighlight();
11622     SetGameInfo();
11623
11624     StartAnalysisClock();
11625     GetTimeMark(&lastNodeCountTime);
11626     lastNodeCount = 0;
11627 }
11628
11629 void
11630 MachineWhiteEvent()
11631 {
11632     char buf[MSG_SIZ];
11633     char *bookHit = NULL;
11634
11635     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11636       return;
11637
11638
11639     if (gameMode == PlayFromGameFile || 
11640         gameMode == TwoMachinesPlay  || 
11641         gameMode == Training         || 
11642         gameMode == AnalyzeMode      || 
11643         gameMode == EndOfGame)
11644         EditGameEvent();
11645
11646     if (gameMode == EditPosition) 
11647         EditPositionDone(TRUE);
11648
11649     if (!WhiteOnMove(currentMove)) {
11650         DisplayError(_("It is not White's turn"), 0);
11651         return;
11652     }
11653   
11654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11655       ExitAnalyzeMode();
11656
11657     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11658         gameMode == AnalyzeFile)
11659         TruncateGame();
11660
11661     ResurrectChessProgram();    /* in case it isn't running */
11662     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11663         gameMode = MachinePlaysWhite;
11664         ResetClocks();
11665     } else
11666     gameMode = MachinePlaysWhite;
11667     pausing = FALSE;
11668     ModeHighlight();
11669     SetGameInfo();
11670     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11671     DisplayTitle(buf);
11672     if (first.sendName) {
11673       sprintf(buf, "name %s\n", gameInfo.black);
11674       SendToProgram(buf, &first);
11675     }
11676     if (first.sendTime) {
11677       if (first.useColors) {
11678         SendToProgram("black\n", &first); /*gnu kludge*/
11679       }
11680       SendTimeRemaining(&first, TRUE);
11681     }
11682     if (first.useColors) {
11683       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11684     }
11685     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11686     SetMachineThinkingEnables();
11687     first.maybeThinking = TRUE;
11688     StartClocks();
11689     firstMove = FALSE;
11690
11691     if (appData.autoFlipView && !flipView) {
11692       flipView = !flipView;
11693       DrawPosition(FALSE, NULL);
11694       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11695     }
11696
11697     if(bookHit) { // [HGM] book: simulate book reply
11698         static char bookMove[MSG_SIZ]; // a bit generous?
11699
11700         programStats.nodes = programStats.depth = programStats.time = 
11701         programStats.score = programStats.got_only_move = 0;
11702         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11703
11704         strcpy(bookMove, "move ");
11705         strcat(bookMove, bookHit);
11706         HandleMachineMove(bookMove, &first);
11707     }
11708 }
11709
11710 void
11711 MachineBlackEvent()
11712 {
11713     char buf[MSG_SIZ];
11714    char *bookHit = NULL;
11715
11716     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11717         return;
11718
11719
11720     if (gameMode == PlayFromGameFile || 
11721         gameMode == TwoMachinesPlay  || 
11722         gameMode == Training         || 
11723         gameMode == AnalyzeMode      || 
11724         gameMode == EndOfGame)
11725         EditGameEvent();
11726
11727     if (gameMode == EditPosition) 
11728         EditPositionDone(TRUE);
11729
11730     if (WhiteOnMove(currentMove)) {
11731         DisplayError(_("It is not Black's turn"), 0);
11732         return;
11733     }
11734     
11735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11736       ExitAnalyzeMode();
11737
11738     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11739         gameMode == AnalyzeFile)
11740         TruncateGame();
11741
11742     ResurrectChessProgram();    /* in case it isn't running */
11743     gameMode = MachinePlaysBlack;
11744     pausing = FALSE;
11745     ModeHighlight();
11746     SetGameInfo();
11747     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11748     DisplayTitle(buf);
11749     if (first.sendName) {
11750       sprintf(buf, "name %s\n", gameInfo.white);
11751       SendToProgram(buf, &first);
11752     }
11753     if (first.sendTime) {
11754       if (first.useColors) {
11755         SendToProgram("white\n", &first); /*gnu kludge*/
11756       }
11757       SendTimeRemaining(&first, FALSE);
11758     }
11759     if (first.useColors) {
11760       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11761     }
11762     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11763     SetMachineThinkingEnables();
11764     first.maybeThinking = TRUE;
11765     StartClocks();
11766
11767     if (appData.autoFlipView && flipView) {
11768       flipView = !flipView;
11769       DrawPosition(FALSE, NULL);
11770       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11771     }
11772     if(bookHit) { // [HGM] book: simulate book reply
11773         static char bookMove[MSG_SIZ]; // a bit generous?
11774
11775         programStats.nodes = programStats.depth = programStats.time = 
11776         programStats.score = programStats.got_only_move = 0;
11777         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11778
11779         strcpy(bookMove, "move ");
11780         strcat(bookMove, bookHit);
11781         HandleMachineMove(bookMove, &first);
11782     }
11783 }
11784
11785
11786 void
11787 DisplayTwoMachinesTitle()
11788 {
11789     char buf[MSG_SIZ];
11790     if (appData.matchGames > 0) {
11791         if (first.twoMachinesColor[0] == 'w') {
11792             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11793                     gameInfo.white, gameInfo.black,
11794                     first.matchWins, second.matchWins,
11795                     matchGame - 1 - (first.matchWins + second.matchWins));
11796         } else {
11797             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11798                     gameInfo.white, gameInfo.black,
11799                     second.matchWins, first.matchWins,
11800                     matchGame - 1 - (first.matchWins + second.matchWins));
11801         }
11802     } else {
11803         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11804     }
11805     DisplayTitle(buf);
11806 }
11807
11808 void
11809 TwoMachinesEvent P((void))
11810 {
11811     int i;
11812     char buf[MSG_SIZ];
11813     ChessProgramState *onmove;
11814     char *bookHit = NULL;
11815     
11816     if (appData.noChessProgram) return;
11817
11818     switch (gameMode) {
11819       case TwoMachinesPlay:
11820         return;
11821       case MachinePlaysWhite:
11822       case MachinePlaysBlack:
11823         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11824             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11825             return;
11826         }
11827         /* fall through */
11828       case BeginningOfGame:
11829       case PlayFromGameFile:
11830       case EndOfGame:
11831         EditGameEvent();
11832         if (gameMode != EditGame) return;
11833         break;
11834       case EditPosition:
11835         EditPositionDone(TRUE);
11836         break;
11837       case AnalyzeMode:
11838       case AnalyzeFile:
11839         ExitAnalyzeMode();
11840         break;
11841       case EditGame:
11842       default:
11843         break;
11844     }
11845
11846 //    forwardMostMove = currentMove;
11847     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11848     ResurrectChessProgram();    /* in case first program isn't running */
11849
11850     if (second.pr == NULL) {
11851         StartChessProgram(&second);
11852         if (second.protocolVersion == 1) {
11853           TwoMachinesEventIfReady();
11854         } else {
11855           /* kludge: allow timeout for initial "feature" command */
11856           FreezeUI();
11857           DisplayMessage("", _("Starting second chess program"));
11858           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11859         }
11860         return;
11861     }
11862     DisplayMessage("", "");
11863     InitChessProgram(&second, FALSE);
11864     SendToProgram("force\n", &second);
11865     if (startedFromSetupPosition) {
11866         SendBoard(&second, backwardMostMove);
11867     if (appData.debugMode) {
11868         fprintf(debugFP, "Two Machines\n");
11869     }
11870     }
11871     for (i = backwardMostMove; i < forwardMostMove; i++) {
11872         SendMoveToProgram(i, &second);
11873     }
11874
11875     gameMode = TwoMachinesPlay;
11876     pausing = FALSE;
11877     ModeHighlight();
11878     SetGameInfo();
11879     DisplayTwoMachinesTitle();
11880     firstMove = TRUE;
11881     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11882         onmove = &first;
11883     } else {
11884         onmove = &second;
11885     }
11886
11887     SendToProgram(first.computerString, &first);
11888     if (first.sendName) {
11889       sprintf(buf, "name %s\n", second.tidy);
11890       SendToProgram(buf, &first);
11891     }
11892     SendToProgram(second.computerString, &second);
11893     if (second.sendName) {
11894       sprintf(buf, "name %s\n", first.tidy);
11895       SendToProgram(buf, &second);
11896     }
11897
11898     ResetClocks();
11899     if (!first.sendTime || !second.sendTime) {
11900         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11901         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11902     }
11903     if (onmove->sendTime) {
11904       if (onmove->useColors) {
11905         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11906       }
11907       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11908     }
11909     if (onmove->useColors) {
11910       SendToProgram(onmove->twoMachinesColor, onmove);
11911     }
11912     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11913 //    SendToProgram("go\n", onmove);
11914     onmove->maybeThinking = TRUE;
11915     SetMachineThinkingEnables();
11916
11917     StartClocks();
11918
11919     if(bookHit) { // [HGM] book: simulate book reply
11920         static char bookMove[MSG_SIZ]; // a bit generous?
11921
11922         programStats.nodes = programStats.depth = programStats.time = 
11923         programStats.score = programStats.got_only_move = 0;
11924         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11925
11926         strcpy(bookMove, "move ");
11927         strcat(bookMove, bookHit);
11928         savedMessage = bookMove; // args for deferred call
11929         savedState = onmove;
11930         ScheduleDelayedEvent(DeferredBookMove, 1);
11931     }
11932 }
11933
11934 void
11935 TrainingEvent()
11936 {
11937     if (gameMode == Training) {
11938       SetTrainingModeOff();
11939       gameMode = PlayFromGameFile;
11940       DisplayMessage("", _("Training mode off"));
11941     } else {
11942       gameMode = Training;
11943       animateTraining = appData.animate;
11944
11945       /* make sure we are not already at the end of the game */
11946       if (currentMove < forwardMostMove) {
11947         SetTrainingModeOn();
11948         DisplayMessage("", _("Training mode on"));
11949       } else {
11950         gameMode = PlayFromGameFile;
11951         DisplayError(_("Already at end of game"), 0);
11952       }
11953     }
11954     ModeHighlight();
11955 }
11956
11957 void
11958 IcsClientEvent()
11959 {
11960     if (!appData.icsActive) return;
11961     switch (gameMode) {
11962       case IcsPlayingWhite:
11963       case IcsPlayingBlack:
11964       case IcsObserving:
11965       case IcsIdle:
11966       case BeginningOfGame:
11967       case IcsExamining:
11968         return;
11969
11970       case EditGame:
11971         break;
11972
11973       case EditPosition:
11974         EditPositionDone(TRUE);
11975         break;
11976
11977       case AnalyzeMode:
11978       case AnalyzeFile:
11979         ExitAnalyzeMode();
11980         break;
11981         
11982       default:
11983         EditGameEvent();
11984         break;
11985     }
11986
11987     gameMode = IcsIdle;
11988     ModeHighlight();
11989     return;
11990 }
11991
11992
11993 void
11994 EditGameEvent()
11995 {
11996     int i;
11997
11998     switch (gameMode) {
11999       case Training:
12000         SetTrainingModeOff();
12001         break;
12002       case MachinePlaysWhite:
12003       case MachinePlaysBlack:
12004       case BeginningOfGame:
12005         SendToProgram("force\n", &first);
12006         SetUserThinkingEnables();
12007         break;
12008       case PlayFromGameFile:
12009         (void) StopLoadGameTimer();
12010         if (gameFileFP != NULL) {
12011             gameFileFP = NULL;
12012         }
12013         break;
12014       case EditPosition:
12015         EditPositionDone(TRUE);
12016         break;
12017       case AnalyzeMode:
12018       case AnalyzeFile:
12019         ExitAnalyzeMode();
12020         SendToProgram("force\n", &first);
12021         break;
12022       case TwoMachinesPlay:
12023         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12024         ResurrectChessProgram();
12025         SetUserThinkingEnables();
12026         break;
12027       case EndOfGame:
12028         ResurrectChessProgram();
12029         break;
12030       case IcsPlayingBlack:
12031       case IcsPlayingWhite:
12032         DisplayError(_("Warning: You are still playing a game"), 0);
12033         break;
12034       case IcsObserving:
12035         DisplayError(_("Warning: You are still observing a game"), 0);
12036         break;
12037       case IcsExamining:
12038         DisplayError(_("Warning: You are still examining a game"), 0);
12039         break;
12040       case IcsIdle:
12041         break;
12042       case EditGame:
12043       default:
12044         return;
12045     }
12046     
12047     pausing = FALSE;
12048     StopClocks();
12049     first.offeredDraw = second.offeredDraw = 0;
12050
12051     if (gameMode == PlayFromGameFile) {
12052         whiteTimeRemaining = timeRemaining[0][currentMove];
12053         blackTimeRemaining = timeRemaining[1][currentMove];
12054         DisplayTitle("");
12055     }
12056
12057     if (gameMode == MachinePlaysWhite ||
12058         gameMode == MachinePlaysBlack ||
12059         gameMode == TwoMachinesPlay ||
12060         gameMode == EndOfGame) {
12061         i = forwardMostMove;
12062         while (i > currentMove) {
12063             SendToProgram("undo\n", &first);
12064             i--;
12065         }
12066         whiteTimeRemaining = timeRemaining[0][currentMove];
12067         blackTimeRemaining = timeRemaining[1][currentMove];
12068         DisplayBothClocks();
12069         if (whiteFlag || blackFlag) {
12070             whiteFlag = blackFlag = 0;
12071         }
12072         DisplayTitle("");
12073     }           
12074     
12075     gameMode = EditGame;
12076     ModeHighlight();
12077     SetGameInfo();
12078 }
12079
12080
12081 void
12082 EditPositionEvent()
12083 {
12084     if (gameMode == EditPosition) {
12085         EditGameEvent();
12086         return;
12087     }
12088     
12089     EditGameEvent();
12090     if (gameMode != EditGame) return;
12091     
12092     gameMode = EditPosition;
12093     ModeHighlight();
12094     SetGameInfo();
12095     if (currentMove > 0)
12096       CopyBoard(boards[0], boards[currentMove]);
12097     
12098     blackPlaysFirst = !WhiteOnMove(currentMove);
12099     ResetClocks();
12100     currentMove = forwardMostMove = backwardMostMove = 0;
12101     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12102     DisplayMove(-1);
12103 }
12104
12105 void
12106 ExitAnalyzeMode()
12107 {
12108     /* [DM] icsEngineAnalyze - possible call from other functions */
12109     if (appData.icsEngineAnalyze) {
12110         appData.icsEngineAnalyze = FALSE;
12111
12112         DisplayMessage("",_("Close ICS engine analyze..."));
12113     }
12114     if (first.analysisSupport && first.analyzing) {
12115       SendToProgram("exit\n", &first);
12116       first.analyzing = FALSE;
12117     }
12118     thinkOutput[0] = NULLCHAR;
12119 }
12120
12121 void
12122 EditPositionDone(Boolean fakeRights)
12123 {
12124     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12125
12126     startedFromSetupPosition = TRUE;
12127     InitChessProgram(&first, FALSE);
12128     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12129       boards[0][EP_STATUS] = EP_NONE;
12130       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12131     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12132         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12133         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12134       } else boards[0][CASTLING][2] = NoRights;
12135     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12136         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12137         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12138       } else boards[0][CASTLING][5] = NoRights;
12139     }
12140     SendToProgram("force\n", &first);
12141     if (blackPlaysFirst) {
12142         strcpy(moveList[0], "");
12143         strcpy(parseList[0], "");
12144         currentMove = forwardMostMove = backwardMostMove = 1;
12145         CopyBoard(boards[1], boards[0]);
12146     } else {
12147         currentMove = forwardMostMove = backwardMostMove = 0;
12148     }
12149     SendBoard(&first, forwardMostMove);
12150     if (appData.debugMode) {
12151         fprintf(debugFP, "EditPosDone\n");
12152     }
12153     DisplayTitle("");
12154     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12155     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12156     gameMode = EditGame;
12157     ModeHighlight();
12158     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12159     ClearHighlights(); /* [AS] */
12160 }
12161
12162 /* Pause for `ms' milliseconds */
12163 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12164 void
12165 TimeDelay(ms)
12166      long ms;
12167 {
12168     TimeMark m1, m2;
12169
12170     GetTimeMark(&m1);
12171     do {
12172         GetTimeMark(&m2);
12173     } while (SubtractTimeMarks(&m2, &m1) < ms);
12174 }
12175
12176 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12177 void
12178 SendMultiLineToICS(buf)
12179      char *buf;
12180 {
12181     char temp[MSG_SIZ+1], *p;
12182     int len;
12183
12184     len = strlen(buf);
12185     if (len > MSG_SIZ)
12186       len = MSG_SIZ;
12187   
12188     strncpy(temp, buf, len);
12189     temp[len] = 0;
12190
12191     p = temp;
12192     while (*p) {
12193         if (*p == '\n' || *p == '\r')
12194           *p = ' ';
12195         ++p;
12196     }
12197
12198     strcat(temp, "\n");
12199     SendToICS(temp);
12200     SendToPlayer(temp, strlen(temp));
12201 }
12202
12203 void
12204 SetWhiteToPlayEvent()
12205 {
12206     if (gameMode == EditPosition) {
12207         blackPlaysFirst = FALSE;
12208         DisplayBothClocks();    /* works because currentMove is 0 */
12209     } else if (gameMode == IcsExamining) {
12210         SendToICS(ics_prefix);
12211         SendToICS("tomove white\n");
12212     }
12213 }
12214
12215 void
12216 SetBlackToPlayEvent()
12217 {
12218     if (gameMode == EditPosition) {
12219         blackPlaysFirst = TRUE;
12220         currentMove = 1;        /* kludge */
12221         DisplayBothClocks();
12222         currentMove = 0;
12223     } else if (gameMode == IcsExamining) {
12224         SendToICS(ics_prefix);
12225         SendToICS("tomove black\n");
12226     }
12227 }
12228
12229 void
12230 EditPositionMenuEvent(selection, x, y)
12231      ChessSquare selection;
12232      int x, y;
12233 {
12234     char buf[MSG_SIZ];
12235     ChessSquare piece = boards[0][y][x];
12236
12237     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12238
12239     switch (selection) {
12240       case ClearBoard:
12241         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12242             SendToICS(ics_prefix);
12243             SendToICS("bsetup clear\n");
12244         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12245             SendToICS(ics_prefix);
12246             SendToICS("clearboard\n");
12247         } else {
12248             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12249                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12250                 for (y = 0; y < BOARD_HEIGHT; y++) {
12251                     if (gameMode == IcsExamining) {
12252                         if (boards[currentMove][y][x] != EmptySquare) {
12253                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12254                                     AAA + x, ONE + y);
12255                             SendToICS(buf);
12256                         }
12257                     } else {
12258                         boards[0][y][x] = p;
12259                     }
12260                 }
12261             }
12262         }
12263         if (gameMode == EditPosition) {
12264             DrawPosition(FALSE, boards[0]);
12265         }
12266         break;
12267
12268       case WhitePlay:
12269         SetWhiteToPlayEvent();
12270         break;
12271
12272       case BlackPlay:
12273         SetBlackToPlayEvent();
12274         break;
12275
12276       case EmptySquare:
12277         if (gameMode == IcsExamining) {
12278             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12279             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12280             SendToICS(buf);
12281         } else {
12282             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12283                 if(x == BOARD_LEFT-2) {
12284                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12285                     boards[0][y][1] = 0;
12286                 } else
12287                 if(x == BOARD_RGHT+1) {
12288                     if(y >= gameInfo.holdingsSize) break;
12289                     boards[0][y][BOARD_WIDTH-2] = 0;
12290                 } else break;
12291             }
12292             boards[0][y][x] = EmptySquare;
12293             DrawPosition(FALSE, boards[0]);
12294         }
12295         break;
12296
12297       case PromotePiece:
12298         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12299            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12300             selection = (ChessSquare) (PROMOTED piece);
12301         } else if(piece == EmptySquare) selection = WhiteSilver;
12302         else selection = (ChessSquare)((int)piece - 1);
12303         goto defaultlabel;
12304
12305       case DemotePiece:
12306         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12307            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12308             selection = (ChessSquare) (DEMOTED piece);
12309         } else if(piece == EmptySquare) selection = BlackSilver;
12310         else selection = (ChessSquare)((int)piece + 1);       
12311         goto defaultlabel;
12312
12313       case WhiteQueen:
12314       case BlackQueen:
12315         if(gameInfo.variant == VariantShatranj ||
12316            gameInfo.variant == VariantXiangqi  ||
12317            gameInfo.variant == VariantCourier  ||
12318            gameInfo.variant == VariantMakruk     )
12319             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12320         goto defaultlabel;
12321
12322       case WhiteKing:
12323       case BlackKing:
12324         if(gameInfo.variant == VariantXiangqi)
12325             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12326         if(gameInfo.variant == VariantKnightmate)
12327             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12328       default:
12329         defaultlabel:
12330         if (gameMode == IcsExamining) {
12331             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12332             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12333                     PieceToChar(selection), AAA + x, ONE + y);
12334             SendToICS(buf);
12335         } else {
12336             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12337                 int n;
12338                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12339                     n = PieceToNumber(selection - BlackPawn);
12340                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12341                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12342                     boards[0][BOARD_HEIGHT-1-n][1]++;
12343                 } else
12344                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12345                     n = PieceToNumber(selection);
12346                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12347                     boards[0][n][BOARD_WIDTH-1] = selection;
12348                     boards[0][n][BOARD_WIDTH-2]++;
12349                 }
12350             } else
12351             boards[0][y][x] = selection;
12352             DrawPosition(TRUE, boards[0]);
12353         }
12354         break;
12355     }
12356 }
12357
12358
12359 void
12360 DropMenuEvent(selection, x, y)
12361      ChessSquare selection;
12362      int x, y;
12363 {
12364     ChessMove moveType;
12365
12366     switch (gameMode) {
12367       case IcsPlayingWhite:
12368       case MachinePlaysBlack:
12369         if (!WhiteOnMove(currentMove)) {
12370             DisplayMoveError(_("It is Black's turn"));
12371             return;
12372         }
12373         moveType = WhiteDrop;
12374         break;
12375       case IcsPlayingBlack:
12376       case MachinePlaysWhite:
12377         if (WhiteOnMove(currentMove)) {
12378             DisplayMoveError(_("It is White's turn"));
12379             return;
12380         }
12381         moveType = BlackDrop;
12382         break;
12383       case EditGame:
12384         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12385         break;
12386       default:
12387         return;
12388     }
12389
12390     if (moveType == BlackDrop && selection < BlackPawn) {
12391       selection = (ChessSquare) ((int) selection
12392                                  + (int) BlackPawn - (int) WhitePawn);
12393     }
12394     if (boards[currentMove][y][x] != EmptySquare) {
12395         DisplayMoveError(_("That square is occupied"));
12396         return;
12397     }
12398
12399     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12400 }
12401
12402 void
12403 AcceptEvent()
12404 {
12405     /* Accept a pending offer of any kind from opponent */
12406     
12407     if (appData.icsActive) {
12408         SendToICS(ics_prefix);
12409         SendToICS("accept\n");
12410     } else if (cmailMsgLoaded) {
12411         if (currentMove == cmailOldMove &&
12412             commentList[cmailOldMove] != NULL &&
12413             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12414                    "Black offers a draw" : "White offers a draw")) {
12415             TruncateGame();
12416             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12417             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12418         } else {
12419             DisplayError(_("There is no pending offer on this move"), 0);
12420             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12421         }
12422     } else {
12423         /* Not used for offers from chess program */
12424     }
12425 }
12426
12427 void
12428 DeclineEvent()
12429 {
12430     /* Decline a pending offer of any kind from opponent */
12431     
12432     if (appData.icsActive) {
12433         SendToICS(ics_prefix);
12434         SendToICS("decline\n");
12435     } else if (cmailMsgLoaded) {
12436         if (currentMove == cmailOldMove &&
12437             commentList[cmailOldMove] != NULL &&
12438             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12439                    "Black offers a draw" : "White offers a draw")) {
12440 #ifdef NOTDEF
12441             AppendComment(cmailOldMove, "Draw declined", TRUE);
12442             DisplayComment(cmailOldMove - 1, "Draw declined");
12443 #endif /*NOTDEF*/
12444         } else {
12445             DisplayError(_("There is no pending offer on this move"), 0);
12446         }
12447     } else {
12448         /* Not used for offers from chess program */
12449     }
12450 }
12451
12452 void
12453 RematchEvent()
12454 {
12455     /* Issue ICS rematch command */
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("rematch\n");
12459     }
12460 }
12461
12462 void
12463 CallFlagEvent()
12464 {
12465     /* Call your opponent's flag (claim a win on time) */
12466     if (appData.icsActive) {
12467         SendToICS(ics_prefix);
12468         SendToICS("flag\n");
12469     } else {
12470         switch (gameMode) {
12471           default:
12472             return;
12473           case MachinePlaysWhite:
12474             if (whiteFlag) {
12475                 if (blackFlag)
12476                   GameEnds(GameIsDrawn, "Both players ran out of time",
12477                            GE_PLAYER);
12478                 else
12479                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12480             } else {
12481                 DisplayError(_("Your opponent is not out of time"), 0);
12482             }
12483             break;
12484           case MachinePlaysBlack:
12485             if (blackFlag) {
12486                 if (whiteFlag)
12487                   GameEnds(GameIsDrawn, "Both players ran out of time",
12488                            GE_PLAYER);
12489                 else
12490                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12491             } else {
12492                 DisplayError(_("Your opponent is not out of time"), 0);
12493             }
12494             break;
12495         }
12496     }
12497 }
12498
12499 void
12500 DrawEvent()
12501 {
12502     /* Offer draw or accept pending draw offer from opponent */
12503     
12504     if (appData.icsActive) {
12505         /* Note: tournament rules require draw offers to be
12506            made after you make your move but before you punch
12507            your clock.  Currently ICS doesn't let you do that;
12508            instead, you immediately punch your clock after making
12509            a move, but you can offer a draw at any time. */
12510         
12511         SendToICS(ics_prefix);
12512         SendToICS("draw\n");
12513         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12514     } else if (cmailMsgLoaded) {
12515         if (currentMove == cmailOldMove &&
12516             commentList[cmailOldMove] != NULL &&
12517             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12518                    "Black offers a draw" : "White offers a draw")) {
12519             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12521         } else if (currentMove == cmailOldMove + 1) {
12522             char *offer = WhiteOnMove(cmailOldMove) ?
12523               "White offers a draw" : "Black offers a draw";
12524             AppendComment(currentMove, offer, TRUE);
12525             DisplayComment(currentMove - 1, offer);
12526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12527         } else {
12528             DisplayError(_("You must make your move before offering a draw"), 0);
12529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12530         }
12531     } else if (first.offeredDraw) {
12532         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12533     } else {
12534         if (first.sendDrawOffers) {
12535             SendToProgram("draw\n", &first);
12536             userOfferedDraw = TRUE;
12537         }
12538     }
12539 }
12540
12541 void
12542 AdjournEvent()
12543 {
12544     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12545     
12546     if (appData.icsActive) {
12547         SendToICS(ics_prefix);
12548         SendToICS("adjourn\n");
12549     } else {
12550         /* Currently GNU Chess doesn't offer or accept Adjourns */
12551     }
12552 }
12553
12554
12555 void
12556 AbortEvent()
12557 {
12558     /* Offer Abort or accept pending Abort offer from opponent */
12559     
12560     if (appData.icsActive) {
12561         SendToICS(ics_prefix);
12562         SendToICS("abort\n");
12563     } else {
12564         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12565     }
12566 }
12567
12568 void
12569 ResignEvent()
12570 {
12571     /* Resign.  You can do this even if it's not your turn. */
12572     
12573     if (appData.icsActive) {
12574         SendToICS(ics_prefix);
12575         SendToICS("resign\n");
12576     } else {
12577         switch (gameMode) {
12578           case MachinePlaysWhite:
12579             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12580             break;
12581           case MachinePlaysBlack:
12582             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12583             break;
12584           case EditGame:
12585             if (cmailMsgLoaded) {
12586                 TruncateGame();
12587                 if (WhiteOnMove(cmailOldMove)) {
12588                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12589                 } else {
12590                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12591                 }
12592                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12593             }
12594             break;
12595           default:
12596             break;
12597         }
12598     }
12599 }
12600
12601
12602 void
12603 StopObservingEvent()
12604 {
12605     /* Stop observing current games */
12606     SendToICS(ics_prefix);
12607     SendToICS("unobserve\n");
12608 }
12609
12610 void
12611 StopExaminingEvent()
12612 {
12613     /* Stop observing current game */
12614     SendToICS(ics_prefix);
12615     SendToICS("unexamine\n");
12616 }
12617
12618 void
12619 ForwardInner(target)
12620      int target;
12621 {
12622     int limit;
12623
12624     if (appData.debugMode)
12625         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12626                 target, currentMove, forwardMostMove);
12627
12628     if (gameMode == EditPosition)
12629       return;
12630
12631     if (gameMode == PlayFromGameFile && !pausing)
12632       PauseEvent();
12633     
12634     if (gameMode == IcsExamining && pausing)
12635       limit = pauseExamForwardMostMove;
12636     else
12637       limit = forwardMostMove;
12638     
12639     if (target > limit) target = limit;
12640
12641     if (target > 0 && moveList[target - 1][0]) {
12642         int fromX, fromY, toX, toY;
12643         toX = moveList[target - 1][2] - AAA;
12644         toY = moveList[target - 1][3] - ONE;
12645         if (moveList[target - 1][1] == '@') {
12646             if (appData.highlightLastMove) {
12647                 SetHighlights(-1, -1, toX, toY);
12648             }
12649         } else {
12650             fromX = moveList[target - 1][0] - AAA;
12651             fromY = moveList[target - 1][1] - ONE;
12652             if (target == currentMove + 1) {
12653                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12654             }
12655             if (appData.highlightLastMove) {
12656                 SetHighlights(fromX, fromY, toX, toY);
12657             }
12658         }
12659     }
12660     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12661         gameMode == Training || gameMode == PlayFromGameFile || 
12662         gameMode == AnalyzeFile) {
12663         while (currentMove < target) {
12664             SendMoveToProgram(currentMove++, &first);
12665         }
12666     } else {
12667         currentMove = target;
12668     }
12669     
12670     if (gameMode == EditGame || gameMode == EndOfGame) {
12671         whiteTimeRemaining = timeRemaining[0][currentMove];
12672         blackTimeRemaining = timeRemaining[1][currentMove];
12673     }
12674     DisplayBothClocks();
12675     DisplayMove(currentMove - 1);
12676     DrawPosition(FALSE, boards[currentMove]);
12677     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12678     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12679         DisplayComment(currentMove - 1, commentList[currentMove]);
12680     }
12681 }
12682
12683
12684 void
12685 ForwardEvent()
12686 {
12687     if (gameMode == IcsExamining && !pausing) {
12688         SendToICS(ics_prefix);
12689         SendToICS("forward\n");
12690     } else {
12691         ForwardInner(currentMove + 1);
12692     }
12693 }
12694
12695 void
12696 ToEndEvent()
12697 {
12698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12699         /* to optimze, we temporarily turn off analysis mode while we feed
12700          * the remaining moves to the engine. Otherwise we get analysis output
12701          * after each move.
12702          */ 
12703         if (first.analysisSupport) {
12704           SendToProgram("exit\nforce\n", &first);
12705           first.analyzing = FALSE;
12706         }
12707     }
12708         
12709     if (gameMode == IcsExamining && !pausing) {
12710         SendToICS(ics_prefix);
12711         SendToICS("forward 999999\n");
12712     } else {
12713         ForwardInner(forwardMostMove);
12714     }
12715
12716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12717         /* we have fed all the moves, so reactivate analysis mode */
12718         SendToProgram("analyze\n", &first);
12719         first.analyzing = TRUE;
12720         /*first.maybeThinking = TRUE;*/
12721         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12722     }
12723 }
12724
12725 void
12726 BackwardInner(target)
12727      int target;
12728 {
12729     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12730
12731     if (appData.debugMode)
12732         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12733                 target, currentMove, forwardMostMove);
12734
12735     if (gameMode == EditPosition) return;
12736     if (currentMove <= backwardMostMove) {
12737         ClearHighlights();
12738         DrawPosition(full_redraw, boards[currentMove]);
12739         return;
12740     }
12741     if (gameMode == PlayFromGameFile && !pausing)
12742       PauseEvent();
12743     
12744     if (moveList[target][0]) {
12745         int fromX, fromY, toX, toY;
12746         toX = moveList[target][2] - AAA;
12747         toY = moveList[target][3] - ONE;
12748         if (moveList[target][1] == '@') {
12749             if (appData.highlightLastMove) {
12750                 SetHighlights(-1, -1, toX, toY);
12751             }
12752         } else {
12753             fromX = moveList[target][0] - AAA;
12754             fromY = moveList[target][1] - ONE;
12755             if (target == currentMove - 1) {
12756                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12757             }
12758             if (appData.highlightLastMove) {
12759                 SetHighlights(fromX, fromY, toX, toY);
12760             }
12761         }
12762     }
12763     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12764         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12765         while (currentMove > target) {
12766             SendToProgram("undo\n", &first);
12767             currentMove--;
12768         }
12769     } else {
12770         currentMove = target;
12771     }
12772     
12773     if (gameMode == EditGame || gameMode == EndOfGame) {
12774         whiteTimeRemaining = timeRemaining[0][currentMove];
12775         blackTimeRemaining = timeRemaining[1][currentMove];
12776     }
12777     DisplayBothClocks();
12778     DisplayMove(currentMove - 1);
12779     DrawPosition(full_redraw, boards[currentMove]);
12780     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12781     // [HGM] PV info: routine tests if comment empty
12782     DisplayComment(currentMove - 1, commentList[currentMove]);
12783 }
12784
12785 void
12786 BackwardEvent()
12787 {
12788     if (gameMode == IcsExamining && !pausing) {
12789         SendToICS(ics_prefix);
12790         SendToICS("backward\n");
12791     } else {
12792         BackwardInner(currentMove - 1);
12793     }
12794 }
12795
12796 void
12797 ToStartEvent()
12798 {
12799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12800         /* to optimize, we temporarily turn off analysis mode while we undo
12801          * all the moves. Otherwise we get analysis output after each undo.
12802          */ 
12803         if (first.analysisSupport) {
12804           SendToProgram("exit\nforce\n", &first);
12805           first.analyzing = FALSE;
12806         }
12807     }
12808
12809     if (gameMode == IcsExamining && !pausing) {
12810         SendToICS(ics_prefix);
12811         SendToICS("backward 999999\n");
12812     } else {
12813         BackwardInner(backwardMostMove);
12814     }
12815
12816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12817         /* we have fed all the moves, so reactivate analysis mode */
12818         SendToProgram("analyze\n", &first);
12819         first.analyzing = TRUE;
12820         /*first.maybeThinking = TRUE;*/
12821         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12822     }
12823 }
12824
12825 void
12826 ToNrEvent(int to)
12827 {
12828   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12829   if (to >= forwardMostMove) to = forwardMostMove;
12830   if (to <= backwardMostMove) to = backwardMostMove;
12831   if (to < currentMove) {
12832     BackwardInner(to);
12833   } else {
12834     ForwardInner(to);
12835   }
12836 }
12837
12838 void
12839 RevertEvent(Boolean annotate)
12840 {
12841     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12842         return;
12843     }
12844     if (gameMode != IcsExamining) {
12845         DisplayError(_("You are not examining a game"), 0);
12846         return;
12847     }
12848     if (pausing) {
12849         DisplayError(_("You can't revert while pausing"), 0);
12850         return;
12851     }
12852     SendToICS(ics_prefix);
12853     SendToICS("revert\n");
12854 }
12855
12856 void
12857 RetractMoveEvent()
12858 {
12859     switch (gameMode) {
12860       case MachinePlaysWhite:
12861       case MachinePlaysBlack:
12862         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12863             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12864             return;
12865         }
12866         if (forwardMostMove < 2) return;
12867         currentMove = forwardMostMove = forwardMostMove - 2;
12868         whiteTimeRemaining = timeRemaining[0][currentMove];
12869         blackTimeRemaining = timeRemaining[1][currentMove];
12870         DisplayBothClocks();
12871         DisplayMove(currentMove - 1);
12872         ClearHighlights();/*!! could figure this out*/
12873         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12874         SendToProgram("remove\n", &first);
12875         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12876         break;
12877
12878       case BeginningOfGame:
12879       default:
12880         break;
12881
12882       case IcsPlayingWhite:
12883       case IcsPlayingBlack:
12884         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12885             SendToICS(ics_prefix);
12886             SendToICS("takeback 2\n");
12887         } else {
12888             SendToICS(ics_prefix);
12889             SendToICS("takeback 1\n");
12890         }
12891         break;
12892     }
12893 }
12894
12895 void
12896 MoveNowEvent()
12897 {
12898     ChessProgramState *cps;
12899
12900     switch (gameMode) {
12901       case MachinePlaysWhite:
12902         if (!WhiteOnMove(forwardMostMove)) {
12903             DisplayError(_("It is your turn"), 0);
12904             return;
12905         }
12906         cps = &first;
12907         break;
12908       case MachinePlaysBlack:
12909         if (WhiteOnMove(forwardMostMove)) {
12910             DisplayError(_("It is your turn"), 0);
12911             return;
12912         }
12913         cps = &first;
12914         break;
12915       case TwoMachinesPlay:
12916         if (WhiteOnMove(forwardMostMove) ==
12917             (first.twoMachinesColor[0] == 'w')) {
12918             cps = &first;
12919         } else {
12920             cps = &second;
12921         }
12922         break;
12923       case BeginningOfGame:
12924       default:
12925         return;
12926     }
12927     SendToProgram("?\n", cps);
12928 }
12929
12930 void
12931 TruncateGameEvent()
12932 {
12933     EditGameEvent();
12934     if (gameMode != EditGame) return;
12935     TruncateGame();
12936 }
12937
12938 void
12939 TruncateGame()
12940 {
12941     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12942     if (forwardMostMove > currentMove) {
12943         if (gameInfo.resultDetails != NULL) {
12944             free(gameInfo.resultDetails);
12945             gameInfo.resultDetails = NULL;
12946             gameInfo.result = GameUnfinished;
12947         }
12948         forwardMostMove = currentMove;
12949         HistorySet(parseList, backwardMostMove, forwardMostMove,
12950                    currentMove-1);
12951     }
12952 }
12953
12954 void
12955 HintEvent()
12956 {
12957     if (appData.noChessProgram) return;
12958     switch (gameMode) {
12959       case MachinePlaysWhite:
12960         if (WhiteOnMove(forwardMostMove)) {
12961             DisplayError(_("Wait until your turn"), 0);
12962             return;
12963         }
12964         break;
12965       case BeginningOfGame:
12966       case MachinePlaysBlack:
12967         if (!WhiteOnMove(forwardMostMove)) {
12968             DisplayError(_("Wait until your turn"), 0);
12969             return;
12970         }
12971         break;
12972       default:
12973         DisplayError(_("No hint available"), 0);
12974         return;
12975     }
12976     SendToProgram("hint\n", &first);
12977     hintRequested = TRUE;
12978 }
12979
12980 void
12981 BookEvent()
12982 {
12983     if (appData.noChessProgram) return;
12984     switch (gameMode) {
12985       case MachinePlaysWhite:
12986         if (WhiteOnMove(forwardMostMove)) {
12987             DisplayError(_("Wait until your turn"), 0);
12988             return;
12989         }
12990         break;
12991       case BeginningOfGame:
12992       case MachinePlaysBlack:
12993         if (!WhiteOnMove(forwardMostMove)) {
12994             DisplayError(_("Wait until your turn"), 0);
12995             return;
12996         }
12997         break;
12998       case EditPosition:
12999         EditPositionDone(TRUE);
13000         break;
13001       case TwoMachinesPlay:
13002         return;
13003       default:
13004         break;
13005     }
13006     SendToProgram("bk\n", &first);
13007     bookOutput[0] = NULLCHAR;
13008     bookRequested = TRUE;
13009 }
13010
13011 void
13012 AboutGameEvent()
13013 {
13014     char *tags = PGNTags(&gameInfo);
13015     TagsPopUp(tags, CmailMsg());
13016     free(tags);
13017 }
13018
13019 /* end button procedures */
13020
13021 void
13022 PrintPosition(fp, move)
13023      FILE *fp;
13024      int move;
13025 {
13026     int i, j;
13027     
13028     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13030             char c = PieceToChar(boards[move][i][j]);
13031             fputc(c == 'x' ? '.' : c, fp);
13032             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13033         }
13034     }
13035     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13036       fprintf(fp, "white to play\n");
13037     else
13038       fprintf(fp, "black to play\n");
13039 }
13040
13041 void
13042 PrintOpponents(fp)
13043      FILE *fp;
13044 {
13045     if (gameInfo.white != NULL) {
13046         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13047     } else {
13048         fprintf(fp, "\n");
13049     }
13050 }
13051
13052 /* Find last component of program's own name, using some heuristics */
13053 void
13054 TidyProgramName(prog, host, buf)
13055      char *prog, *host, buf[MSG_SIZ];
13056 {
13057     char *p, *q;
13058     int local = (strcmp(host, "localhost") == 0);
13059     while (!local && (p = strchr(prog, ';')) != NULL) {
13060         p++;
13061         while (*p == ' ') p++;
13062         prog = p;
13063     }
13064     if (*prog == '"' || *prog == '\'') {
13065         q = strchr(prog + 1, *prog);
13066     } else {
13067         q = strchr(prog, ' ');
13068     }
13069     if (q == NULL) q = prog + strlen(prog);
13070     p = q;
13071     while (p >= prog && *p != '/' && *p != '\\') p--;
13072     p++;
13073     if(p == prog && *p == '"') p++;
13074     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13075     memcpy(buf, p, q - p);
13076     buf[q - p] = NULLCHAR;
13077     if (!local) {
13078         strcat(buf, "@");
13079         strcat(buf, host);
13080     }
13081 }
13082
13083 char *
13084 TimeControlTagValue()
13085 {
13086     char buf[MSG_SIZ];
13087     if (!appData.clockMode) {
13088         strcpy(buf, "-");
13089     } else if (movesPerSession > 0) {
13090         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13091     } else if (timeIncrement == 0) {
13092         sprintf(buf, "%ld", timeControl/1000);
13093     } else {
13094         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13095     }
13096     return StrSave(buf);
13097 }
13098
13099 void
13100 SetGameInfo()
13101 {
13102     /* This routine is used only for certain modes */
13103     VariantClass v = gameInfo.variant;
13104     ChessMove r = GameUnfinished;
13105     char *p = NULL;
13106
13107     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13108         r = gameInfo.result; 
13109         p = gameInfo.resultDetails; 
13110         gameInfo.resultDetails = NULL;
13111     }
13112     ClearGameInfo(&gameInfo);
13113     gameInfo.variant = v;
13114
13115     switch (gameMode) {
13116       case MachinePlaysWhite:
13117         gameInfo.event = StrSave( appData.pgnEventHeader );
13118         gameInfo.site = StrSave(HostName());
13119         gameInfo.date = PGNDate();
13120         gameInfo.round = StrSave("-");
13121         gameInfo.white = StrSave(first.tidy);
13122         gameInfo.black = StrSave(UserName());
13123         gameInfo.timeControl = TimeControlTagValue();
13124         break;
13125
13126       case MachinePlaysBlack:
13127         gameInfo.event = StrSave( appData.pgnEventHeader );
13128         gameInfo.site = StrSave(HostName());
13129         gameInfo.date = PGNDate();
13130         gameInfo.round = StrSave("-");
13131         gameInfo.white = StrSave(UserName());
13132         gameInfo.black = StrSave(first.tidy);
13133         gameInfo.timeControl = TimeControlTagValue();
13134         break;
13135
13136       case TwoMachinesPlay:
13137         gameInfo.event = StrSave( appData.pgnEventHeader );
13138         gameInfo.site = StrSave(HostName());
13139         gameInfo.date = PGNDate();
13140         if (matchGame > 0) {
13141             char buf[MSG_SIZ];
13142             sprintf(buf, "%d", matchGame);
13143             gameInfo.round = StrSave(buf);
13144         } else {
13145             gameInfo.round = StrSave("-");
13146         }
13147         if (first.twoMachinesColor[0] == 'w') {
13148             gameInfo.white = StrSave(first.tidy);
13149             gameInfo.black = StrSave(second.tidy);
13150         } else {
13151             gameInfo.white = StrSave(second.tidy);
13152             gameInfo.black = StrSave(first.tidy);
13153         }
13154         gameInfo.timeControl = TimeControlTagValue();
13155         break;
13156
13157       case EditGame:
13158         gameInfo.event = StrSave("Edited game");
13159         gameInfo.site = StrSave(HostName());
13160         gameInfo.date = PGNDate();
13161         gameInfo.round = StrSave("-");
13162         gameInfo.white = StrSave("-");
13163         gameInfo.black = StrSave("-");
13164         gameInfo.result = r;
13165         gameInfo.resultDetails = p;
13166         break;
13167
13168       case EditPosition:
13169         gameInfo.event = StrSave("Edited position");
13170         gameInfo.site = StrSave(HostName());
13171         gameInfo.date = PGNDate();
13172         gameInfo.round = StrSave("-");
13173         gameInfo.white = StrSave("-");
13174         gameInfo.black = StrSave("-");
13175         break;
13176
13177       case IcsPlayingWhite:
13178       case IcsPlayingBlack:
13179       case IcsObserving:
13180       case IcsExamining:
13181         break;
13182
13183       case PlayFromGameFile:
13184         gameInfo.event = StrSave("Game from non-PGN file");
13185         gameInfo.site = StrSave(HostName());
13186         gameInfo.date = PGNDate();
13187         gameInfo.round = StrSave("-");
13188         gameInfo.white = StrSave("?");
13189         gameInfo.black = StrSave("?");
13190         break;
13191
13192       default:
13193         break;
13194     }
13195 }
13196
13197 void
13198 ReplaceComment(index, text)
13199      int index;
13200      char *text;
13201 {
13202     int len;
13203
13204     while (*text == '\n') text++;
13205     len = strlen(text);
13206     while (len > 0 && text[len - 1] == '\n') len--;
13207
13208     if (commentList[index] != NULL)
13209       free(commentList[index]);
13210
13211     if (len == 0) {
13212         commentList[index] = NULL;
13213         return;
13214     }
13215   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13216       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13217       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13218     commentList[index] = (char *) malloc(len + 2);
13219     strncpy(commentList[index], text, len);
13220     commentList[index][len] = '\n';
13221     commentList[index][len + 1] = NULLCHAR;
13222   } else { 
13223     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13224     char *p;
13225     commentList[index] = (char *) malloc(len + 6);
13226     strcpy(commentList[index], "{\n");
13227     strncpy(commentList[index]+2, text, len);
13228     commentList[index][len+2] = NULLCHAR;
13229     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13230     strcat(commentList[index], "\n}\n");
13231   }
13232 }
13233
13234 void
13235 CrushCRs(text)
13236      char *text;
13237 {
13238   char *p = text;
13239   char *q = text;
13240   char ch;
13241
13242   do {
13243     ch = *p++;
13244     if (ch == '\r') continue;
13245     *q++ = ch;
13246   } while (ch != '\0');
13247 }
13248
13249 void
13250 AppendComment(index, text, addBraces)
13251      int index;
13252      char *text;
13253      Boolean addBraces; // [HGM] braces: tells if we should add {}
13254 {
13255     int oldlen, len;
13256     char *old;
13257
13258 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13259     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13260
13261     CrushCRs(text);
13262     while (*text == '\n') text++;
13263     len = strlen(text);
13264     while (len > 0 && text[len - 1] == '\n') len--;
13265
13266     if (len == 0) return;
13267
13268     if (commentList[index] != NULL) {
13269         old = commentList[index];
13270         oldlen = strlen(old);
13271         while(commentList[index][oldlen-1] ==  '\n')
13272           commentList[index][--oldlen] = NULLCHAR;
13273         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13274         strcpy(commentList[index], old);
13275         free(old);
13276         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13277         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13278           if(addBraces) addBraces = FALSE; else { text++; len--; }
13279           while (*text == '\n') { text++; len--; }
13280           commentList[index][--oldlen] = NULLCHAR;
13281       }
13282         if(addBraces) strcat(commentList[index], "\n{\n");
13283         else          strcat(commentList[index], "\n");
13284         strcat(commentList[index], text);
13285         if(addBraces) strcat(commentList[index], "\n}\n");
13286         else          strcat(commentList[index], "\n");
13287     } else {
13288         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13289         if(addBraces)
13290              strcpy(commentList[index], "{\n");
13291         else commentList[index][0] = NULLCHAR;
13292         strcat(commentList[index], text);
13293         strcat(commentList[index], "\n");
13294         if(addBraces) strcat(commentList[index], "}\n");
13295     }
13296 }
13297
13298 static char * FindStr( char * text, char * sub_text )
13299 {
13300     char * result = strstr( text, sub_text );
13301
13302     if( result != NULL ) {
13303         result += strlen( sub_text );
13304     }
13305
13306     return result;
13307 }
13308
13309 /* [AS] Try to extract PV info from PGN comment */
13310 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13311 char *GetInfoFromComment( int index, char * text )
13312 {
13313     char * sep = text;
13314
13315     if( text != NULL && index > 0 ) {
13316         int score = 0;
13317         int depth = 0;
13318         int time = -1, sec = 0, deci;
13319         char * s_eval = FindStr( text, "[%eval " );
13320         char * s_emt = FindStr( text, "[%emt " );
13321
13322         if( s_eval != NULL || s_emt != NULL ) {
13323             /* New style */
13324             char delim;
13325
13326             if( s_eval != NULL ) {
13327                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13328                     return text;
13329                 }
13330
13331                 if( delim != ']' ) {
13332                     return text;
13333                 }
13334             }
13335
13336             if( s_emt != NULL ) {
13337             }
13338                 return text;
13339         }
13340         else {
13341             /* We expect something like: [+|-]nnn.nn/dd */
13342             int score_lo = 0;
13343
13344             if(*text != '{') return text; // [HGM] braces: must be normal comment
13345
13346             sep = strchr( text, '/' );
13347             if( sep == NULL || sep < (text+4) ) {
13348                 return text;
13349             }
13350
13351             time = -1; sec = -1; deci = -1;
13352             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13353                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13354                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13355                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13356                 return text;
13357             }
13358
13359             if( score_lo < 0 || score_lo >= 100 ) {
13360                 return text;
13361             }
13362
13363             if(sec >= 0) time = 600*time + 10*sec; else
13364             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13365
13366             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13367
13368             /* [HGM] PV time: now locate end of PV info */
13369             while( *++sep >= '0' && *sep <= '9'); // strip depth
13370             if(time >= 0)
13371             while( *++sep >= '0' && *sep <= '9'); // strip time
13372             if(sec >= 0)
13373             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13374             if(deci >= 0)
13375             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13376             while(*sep == ' ') sep++;
13377         }
13378
13379         if( depth <= 0 ) {
13380             return text;
13381         }
13382
13383         if( time < 0 ) {
13384             time = -1;
13385         }
13386
13387         pvInfoList[index-1].depth = depth;
13388         pvInfoList[index-1].score = score;
13389         pvInfoList[index-1].time  = 10*time; // centi-sec
13390         if(*sep == '}') *sep = 0; else *--sep = '{';
13391     }
13392     return sep;
13393 }
13394
13395 void
13396 SendToProgram(message, cps)
13397      char *message;
13398      ChessProgramState *cps;
13399 {
13400     int count, outCount, error;
13401     char buf[MSG_SIZ];
13402
13403     if (cps->pr == NULL) return;
13404     Attention(cps);
13405     
13406     if (appData.debugMode) {
13407         TimeMark now;
13408         GetTimeMark(&now);
13409         fprintf(debugFP, "%ld >%-6s: %s", 
13410                 SubtractTimeMarks(&now, &programStartTime),
13411                 cps->which, message);
13412     }
13413     
13414     count = strlen(message);
13415     outCount = OutputToProcess(cps->pr, message, count, &error);
13416     if (outCount < count && !exiting 
13417                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13418         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13419         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13420             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13421                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13422                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13423             } else {
13424                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13425             }
13426             gameInfo.resultDetails = StrSave(buf);
13427         }
13428         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13429     }
13430 }
13431
13432 void
13433 ReceiveFromProgram(isr, closure, message, count, error)
13434      InputSourceRef isr;
13435      VOIDSTAR closure;
13436      char *message;
13437      int count;
13438      int error;
13439 {
13440     char *end_str;
13441     char buf[MSG_SIZ];
13442     ChessProgramState *cps = (ChessProgramState *)closure;
13443
13444     if (isr != cps->isr) return; /* Killed intentionally */
13445     if (count <= 0) {
13446         if (count == 0) {
13447             sprintf(buf,
13448                     _("Error: %s chess program (%s) exited unexpectedly"),
13449                     cps->which, cps->program);
13450         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13451                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13452                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13453                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13454                 } else {
13455                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13456                 }
13457                 gameInfo.resultDetails = StrSave(buf);
13458             }
13459             RemoveInputSource(cps->isr);
13460             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13461         } else {
13462             sprintf(buf,
13463                     _("Error reading from %s chess program (%s)"),
13464                     cps->which, cps->program);
13465             RemoveInputSource(cps->isr);
13466
13467             /* [AS] Program is misbehaving badly... kill it */
13468             if( count == -2 ) {
13469                 DestroyChildProcess( cps->pr, 9 );
13470                 cps->pr = NoProc;
13471             }
13472
13473             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13474         }
13475         return;
13476     }
13477     
13478     if ((end_str = strchr(message, '\r')) != NULL)
13479       *end_str = NULLCHAR;
13480     if ((end_str = strchr(message, '\n')) != NULL)
13481       *end_str = NULLCHAR;
13482     
13483     if (appData.debugMode) {
13484         TimeMark now; int print = 1;
13485         char *quote = ""; char c; int i;
13486
13487         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13488                 char start = message[0];
13489                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13490                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13491                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13492                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13493                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13494                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13495                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13496                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13497                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13498                     print = (appData.engineComments >= 2);
13499                 }
13500                 message[0] = start; // restore original message
13501         }
13502         if(print) {
13503                 GetTimeMark(&now);
13504                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13505                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13506                         quote,
13507                         message);
13508         }
13509     }
13510
13511     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13512     if (appData.icsEngineAnalyze) {
13513         if (strstr(message, "whisper") != NULL ||
13514              strstr(message, "kibitz") != NULL || 
13515             strstr(message, "tellics") != NULL) return;
13516     }
13517
13518     HandleMachineMove(message, cps);
13519 }
13520
13521
13522 void
13523 SendTimeControl(cps, mps, tc, inc, sd, st)
13524      ChessProgramState *cps;
13525      int mps, inc, sd, st;
13526      long tc;
13527 {
13528     char buf[MSG_SIZ];
13529     int seconds;
13530
13531     if( timeControl_2 > 0 ) {
13532         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13533             tc = timeControl_2;
13534         }
13535     }
13536     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13537     inc /= cps->timeOdds;
13538     st  /= cps->timeOdds;
13539
13540     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13541
13542     if (st > 0) {
13543       /* Set exact time per move, normally using st command */
13544       if (cps->stKludge) {
13545         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13546         seconds = st % 60;
13547         if (seconds == 0) {
13548           sprintf(buf, "level 1 %d\n", st/60);
13549         } else {
13550           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13551         }
13552       } else {
13553         sprintf(buf, "st %d\n", st);
13554       }
13555     } else {
13556       /* Set conventional or incremental time control, using level command */
13557       if (seconds == 0) {
13558         /* Note old gnuchess bug -- minutes:seconds used to not work.
13559            Fixed in later versions, but still avoid :seconds
13560            when seconds is 0. */
13561         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13562       } else {
13563         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13564                 seconds, inc/1000);
13565       }
13566     }
13567     SendToProgram(buf, cps);
13568
13569     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13570     /* Orthogonally, limit search to given depth */
13571     if (sd > 0) {
13572       if (cps->sdKludge) {
13573         sprintf(buf, "depth\n%d\n", sd);
13574       } else {
13575         sprintf(buf, "sd %d\n", sd);
13576       }
13577       SendToProgram(buf, cps);
13578     }
13579
13580     if(cps->nps > 0) { /* [HGM] nps */
13581         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13582         else {
13583                 sprintf(buf, "nps %d\n", cps->nps);
13584               SendToProgram(buf, cps);
13585         }
13586     }
13587 }
13588
13589 ChessProgramState *WhitePlayer()
13590 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13591 {
13592     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13593        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13594         return &second;
13595     return &first;
13596 }
13597
13598 void
13599 SendTimeRemaining(cps, machineWhite)
13600      ChessProgramState *cps;
13601      int /*boolean*/ machineWhite;
13602 {
13603     char message[MSG_SIZ];
13604     long time, otime;
13605
13606     /* Note: this routine must be called when the clocks are stopped
13607        or when they have *just* been set or switched; otherwise
13608        it will be off by the time since the current tick started.
13609     */
13610     if (machineWhite) {
13611         time = whiteTimeRemaining / 10;
13612         otime = blackTimeRemaining / 10;
13613     } else {
13614         time = blackTimeRemaining / 10;
13615         otime = whiteTimeRemaining / 10;
13616     }
13617     /* [HGM] translate opponent's time by time-odds factor */
13618     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13619     if (appData.debugMode) {
13620         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13621     }
13622
13623     if (time <= 0) time = 1;
13624     if (otime <= 0) otime = 1;
13625     
13626     sprintf(message, "time %ld\n", time);
13627     SendToProgram(message, cps);
13628
13629     sprintf(message, "otim %ld\n", otime);
13630     SendToProgram(message, cps);
13631 }
13632
13633 int
13634 BoolFeature(p, name, loc, cps)
13635      char **p;
13636      char *name;
13637      int *loc;
13638      ChessProgramState *cps;
13639 {
13640   char buf[MSG_SIZ];
13641   int len = strlen(name);
13642   int val;
13643   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13644     (*p) += len + 1;
13645     sscanf(*p, "%d", &val);
13646     *loc = (val != 0);
13647     while (**p && **p != ' ') (*p)++;
13648     sprintf(buf, "accepted %s\n", name);
13649     SendToProgram(buf, cps);
13650     return TRUE;
13651   }
13652   return FALSE;
13653 }
13654
13655 int
13656 IntFeature(p, name, loc, cps)
13657      char **p;
13658      char *name;
13659      int *loc;
13660      ChessProgramState *cps;
13661 {
13662   char buf[MSG_SIZ];
13663   int len = strlen(name);
13664   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13665     (*p) += len + 1;
13666     sscanf(*p, "%d", loc);
13667     while (**p && **p != ' ') (*p)++;
13668     sprintf(buf, "accepted %s\n", name);
13669     SendToProgram(buf, cps);
13670     return TRUE;
13671   }
13672   return FALSE;
13673 }
13674
13675 int
13676 StringFeature(p, name, loc, cps)
13677      char **p;
13678      char *name;
13679      char loc[];
13680      ChessProgramState *cps;
13681 {
13682   char buf[MSG_SIZ];
13683   int len = strlen(name);
13684   if (strncmp((*p), name, len) == 0
13685       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13686     (*p) += len + 2;
13687     sscanf(*p, "%[^\"]", loc);
13688     while (**p && **p != '\"') (*p)++;
13689     if (**p == '\"') (*p)++;
13690     sprintf(buf, "accepted %s\n", name);
13691     SendToProgram(buf, cps);
13692     return TRUE;
13693   }
13694   return FALSE;
13695 }
13696
13697 int 
13698 ParseOption(Option *opt, ChessProgramState *cps)
13699 // [HGM] options: process the string that defines an engine option, and determine
13700 // name, type, default value, and allowed value range
13701 {
13702         char *p, *q, buf[MSG_SIZ];
13703         int n, min = (-1)<<31, max = 1<<31, def;
13704
13705         if(p = strstr(opt->name, " -spin ")) {
13706             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13707             if(max < min) max = min; // enforce consistency
13708             if(def < min) def = min;
13709             if(def > max) def = max;
13710             opt->value = def;
13711             opt->min = min;
13712             opt->max = max;
13713             opt->type = Spin;
13714         } else if((p = strstr(opt->name, " -slider "))) {
13715             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13716             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13717             if(max < min) max = min; // enforce consistency
13718             if(def < min) def = min;
13719             if(def > max) def = max;
13720             opt->value = def;
13721             opt->min = min;
13722             opt->max = max;
13723             opt->type = Spin; // Slider;
13724         } else if((p = strstr(opt->name, " -string "))) {
13725             opt->textValue = p+9;
13726             opt->type = TextBox;
13727         } else if((p = strstr(opt->name, " -file "))) {
13728             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13729             opt->textValue = p+7;
13730             opt->type = TextBox; // FileName;
13731         } else if((p = strstr(opt->name, " -path "))) {
13732             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13733             opt->textValue = p+7;
13734             opt->type = TextBox; // PathName;
13735         } else if(p = strstr(opt->name, " -check ")) {
13736             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13737             opt->value = (def != 0);
13738             opt->type = CheckBox;
13739         } else if(p = strstr(opt->name, " -combo ")) {
13740             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13741             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13742             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13743             opt->value = n = 0;
13744             while(q = StrStr(q, " /// ")) {
13745                 n++; *q = 0;    // count choices, and null-terminate each of them
13746                 q += 5;
13747                 if(*q == '*') { // remember default, which is marked with * prefix
13748                     q++;
13749                     opt->value = n;
13750                 }
13751                 cps->comboList[cps->comboCnt++] = q;
13752             }
13753             cps->comboList[cps->comboCnt++] = NULL;
13754             opt->max = n + 1;
13755             opt->type = ComboBox;
13756         } else if(p = strstr(opt->name, " -button")) {
13757             opt->type = Button;
13758         } else if(p = strstr(opt->name, " -save")) {
13759             opt->type = SaveButton;
13760         } else return FALSE;
13761         *p = 0; // terminate option name
13762         // now look if the command-line options define a setting for this engine option.
13763         if(cps->optionSettings && cps->optionSettings[0])
13764             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13765         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13766                 sprintf(buf, "option %s", p);
13767                 if(p = strstr(buf, ",")) *p = 0;
13768                 strcat(buf, "\n");
13769                 SendToProgram(buf, cps);
13770         }
13771         return TRUE;
13772 }
13773
13774 void
13775 FeatureDone(cps, val)
13776      ChessProgramState* cps;
13777      int val;
13778 {
13779   DelayedEventCallback cb = GetDelayedEvent();
13780   if ((cb == InitBackEnd3 && cps == &first) ||
13781       (cb == TwoMachinesEventIfReady && cps == &second)) {
13782     CancelDelayedEvent();
13783     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13784   }
13785   cps->initDone = val;
13786 }
13787
13788 /* Parse feature command from engine */
13789 void
13790 ParseFeatures(args, cps)
13791      char* args;
13792      ChessProgramState *cps;  
13793 {
13794   char *p = args;
13795   char *q;
13796   int val;
13797   char buf[MSG_SIZ];
13798
13799   for (;;) {
13800     while (*p == ' ') p++;
13801     if (*p == NULLCHAR) return;
13802
13803     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13804     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13805     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13806     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13807     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13808     if (BoolFeature(&p, "reuse", &val, cps)) {
13809       /* Engine can disable reuse, but can't enable it if user said no */
13810       if (!val) cps->reuse = FALSE;
13811       continue;
13812     }
13813     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13814     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13815       if (gameMode == TwoMachinesPlay) {
13816         DisplayTwoMachinesTitle();
13817       } else {
13818         DisplayTitle("");
13819       }
13820       continue;
13821     }
13822     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13823     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13824     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13825     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13826     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13827     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13828     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13829     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13830     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13831     if (IntFeature(&p, "done", &val, cps)) {
13832       FeatureDone(cps, val);
13833       continue;
13834     }
13835     /* Added by Tord: */
13836     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13837     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13838     /* End of additions by Tord */
13839
13840     /* [HGM] added features: */
13841     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13842     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13843     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13844     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13845     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13846     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13847     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13848         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13849             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13850             SendToProgram(buf, cps);
13851             continue;
13852         }
13853         if(cps->nrOptions >= MAX_OPTIONS) {
13854             cps->nrOptions--;
13855             sprintf(buf, "%s engine has too many options\n", cps->which);
13856             DisplayError(buf, 0);
13857         }
13858         continue;
13859     }
13860     /* End of additions by HGM */
13861
13862     /* unknown feature: complain and skip */
13863     q = p;
13864     while (*q && *q != '=') q++;
13865     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13866     SendToProgram(buf, cps);
13867     p = q;
13868     if (*p == '=') {
13869       p++;
13870       if (*p == '\"') {
13871         p++;
13872         while (*p && *p != '\"') p++;
13873         if (*p == '\"') p++;
13874       } else {
13875         while (*p && *p != ' ') p++;
13876       }
13877     }
13878   }
13879
13880 }
13881
13882 void
13883 PeriodicUpdatesEvent(newState)
13884      int newState;
13885 {
13886     if (newState == appData.periodicUpdates)
13887       return;
13888
13889     appData.periodicUpdates=newState;
13890
13891     /* Display type changes, so update it now */
13892 //    DisplayAnalysis();
13893
13894     /* Get the ball rolling again... */
13895     if (newState) {
13896         AnalysisPeriodicEvent(1);
13897         StartAnalysisClock();
13898     }
13899 }
13900
13901 void
13902 PonderNextMoveEvent(newState)
13903      int newState;
13904 {
13905     if (newState == appData.ponderNextMove) return;
13906     if (gameMode == EditPosition) EditPositionDone(TRUE);
13907     if (newState) {
13908         SendToProgram("hard\n", &first);
13909         if (gameMode == TwoMachinesPlay) {
13910             SendToProgram("hard\n", &second);
13911         }
13912     } else {
13913         SendToProgram("easy\n", &first);
13914         thinkOutput[0] = NULLCHAR;
13915         if (gameMode == TwoMachinesPlay) {
13916             SendToProgram("easy\n", &second);
13917         }
13918     }
13919     appData.ponderNextMove = newState;
13920 }
13921
13922 void
13923 NewSettingEvent(option, feature, command, value)
13924      char *command;
13925      int option, value, *feature;
13926 {
13927     char buf[MSG_SIZ];
13928
13929     if (gameMode == EditPosition) EditPositionDone(TRUE);
13930     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13931     if(feature == NULL || *feature) SendToProgram(buf, &first);
13932     if (gameMode == TwoMachinesPlay) {
13933         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13934     }
13935 }
13936
13937 void
13938 ShowThinkingEvent()
13939 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13940 {
13941     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13942     int newState = appData.showThinking
13943         // [HGM] thinking: other features now need thinking output as well
13944         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13945     
13946     if (oldState == newState) return;
13947     oldState = newState;
13948     if (gameMode == EditPosition) EditPositionDone(TRUE);
13949     if (oldState) {
13950         SendToProgram("post\n", &first);
13951         if (gameMode == TwoMachinesPlay) {
13952             SendToProgram("post\n", &second);
13953         }
13954     } else {
13955         SendToProgram("nopost\n", &first);
13956         thinkOutput[0] = NULLCHAR;
13957         if (gameMode == TwoMachinesPlay) {
13958             SendToProgram("nopost\n", &second);
13959         }
13960     }
13961 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13962 }
13963
13964 void
13965 AskQuestionEvent(title, question, replyPrefix, which)
13966      char *title; char *question; char *replyPrefix; char *which;
13967 {
13968   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13969   if (pr == NoProc) return;
13970   AskQuestion(title, question, replyPrefix, pr);
13971 }
13972
13973 void
13974 DisplayMove(moveNumber)
13975      int moveNumber;
13976 {
13977     char message[MSG_SIZ];
13978     char res[MSG_SIZ];
13979     char cpThinkOutput[MSG_SIZ];
13980
13981     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13982     
13983     if (moveNumber == forwardMostMove - 1 || 
13984         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13985
13986         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13987
13988         if (strchr(cpThinkOutput, '\n')) {
13989             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13990         }
13991     } else {
13992         *cpThinkOutput = NULLCHAR;
13993     }
13994
13995     /* [AS] Hide thinking from human user */
13996     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13997         *cpThinkOutput = NULLCHAR;
13998         if( thinkOutput[0] != NULLCHAR ) {
13999             int i;
14000
14001             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14002                 cpThinkOutput[i] = '.';
14003             }
14004             cpThinkOutput[i] = NULLCHAR;
14005             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14006         }
14007     }
14008
14009     if (moveNumber == forwardMostMove - 1 &&
14010         gameInfo.resultDetails != NULL) {
14011         if (gameInfo.resultDetails[0] == NULLCHAR) {
14012             sprintf(res, " %s", PGNResult(gameInfo.result));
14013         } else {
14014             sprintf(res, " {%s} %s",
14015                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14016         }
14017     } else {
14018         res[0] = NULLCHAR;
14019     }
14020
14021     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14022         DisplayMessage(res, cpThinkOutput);
14023     } else {
14024         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14025                 WhiteOnMove(moveNumber) ? " " : ".. ",
14026                 parseList[moveNumber], res);
14027         DisplayMessage(message, cpThinkOutput);
14028     }
14029 }
14030
14031 void
14032 DisplayComment(moveNumber, text)
14033      int moveNumber;
14034      char *text;
14035 {
14036     char title[MSG_SIZ];
14037     char buf[8000]; // comment can be long!
14038     int score, depth;
14039     
14040     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14041       strcpy(title, "Comment");
14042     } else {
14043       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14044               WhiteOnMove(moveNumber) ? " " : ".. ",
14045               parseList[moveNumber]);
14046     }
14047     // [HGM] PV info: display PV info together with (or as) comment
14048     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14049       if(text == NULL) text = "";                                           
14050       score = pvInfoList[moveNumber].score;
14051       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14052               depth, (pvInfoList[moveNumber].time+50)/100, text);
14053       text = buf;
14054     }
14055     if (text != NULL && (appData.autoDisplayComment || commentUp))
14056         CommentPopUp(title, text);
14057 }
14058
14059 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14060  * might be busy thinking or pondering.  It can be omitted if your
14061  * gnuchess is configured to stop thinking immediately on any user
14062  * input.  However, that gnuchess feature depends on the FIONREAD
14063  * ioctl, which does not work properly on some flavors of Unix.
14064  */
14065 void
14066 Attention(cps)
14067      ChessProgramState *cps;
14068 {
14069 #if ATTENTION
14070     if (!cps->useSigint) return;
14071     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14072     switch (gameMode) {
14073       case MachinePlaysWhite:
14074       case MachinePlaysBlack:
14075       case TwoMachinesPlay:
14076       case IcsPlayingWhite:
14077       case IcsPlayingBlack:
14078       case AnalyzeMode:
14079       case AnalyzeFile:
14080         /* Skip if we know it isn't thinking */
14081         if (!cps->maybeThinking) return;
14082         if (appData.debugMode)
14083           fprintf(debugFP, "Interrupting %s\n", cps->which);
14084         InterruptChildProcess(cps->pr);
14085         cps->maybeThinking = FALSE;
14086         break;
14087       default:
14088         break;
14089     }
14090 #endif /*ATTENTION*/
14091 }
14092
14093 int
14094 CheckFlags()
14095 {
14096     if (whiteTimeRemaining <= 0) {
14097         if (!whiteFlag) {
14098             whiteFlag = TRUE;
14099             if (appData.icsActive) {
14100                 if (appData.autoCallFlag &&
14101                     gameMode == IcsPlayingBlack && !blackFlag) {
14102                   SendToICS(ics_prefix);
14103                   SendToICS("flag\n");
14104                 }
14105             } else {
14106                 if (blackFlag) {
14107                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14108                 } else {
14109                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14110                     if (appData.autoCallFlag) {
14111                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14112                         return TRUE;
14113                     }
14114                 }
14115             }
14116         }
14117     }
14118     if (blackTimeRemaining <= 0) {
14119         if (!blackFlag) {
14120             blackFlag = TRUE;
14121             if (appData.icsActive) {
14122                 if (appData.autoCallFlag &&
14123                     gameMode == IcsPlayingWhite && !whiteFlag) {
14124                   SendToICS(ics_prefix);
14125                   SendToICS("flag\n");
14126                 }
14127             } else {
14128                 if (whiteFlag) {
14129                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14130                 } else {
14131                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14132                     if (appData.autoCallFlag) {
14133                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14134                         return TRUE;
14135                     }
14136                 }
14137             }
14138         }
14139     }
14140     return FALSE;
14141 }
14142
14143 void
14144 CheckTimeControl()
14145 {
14146     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14147         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14148
14149     /*
14150      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14151      */
14152     if ( !WhiteOnMove(forwardMostMove) )
14153         /* White made time control */
14154         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14155         /* [HGM] time odds: correct new time quota for time odds! */
14156                                             / WhitePlayer()->timeOdds;
14157       else
14158         /* Black made time control */
14159         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14160                                             / WhitePlayer()->other->timeOdds;
14161 }
14162
14163 void
14164 DisplayBothClocks()
14165 {
14166     int wom = gameMode == EditPosition ?
14167       !blackPlaysFirst : WhiteOnMove(currentMove);
14168     DisplayWhiteClock(whiteTimeRemaining, wom);
14169     DisplayBlackClock(blackTimeRemaining, !wom);
14170 }
14171
14172
14173 /* Timekeeping seems to be a portability nightmare.  I think everyone
14174    has ftime(), but I'm really not sure, so I'm including some ifdefs
14175    to use other calls if you don't.  Clocks will be less accurate if
14176    you have neither ftime nor gettimeofday.
14177 */
14178
14179 /* VS 2008 requires the #include outside of the function */
14180 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14181 #include <sys/timeb.h>
14182 #endif
14183
14184 /* Get the current time as a TimeMark */
14185 void
14186 GetTimeMark(tm)
14187      TimeMark *tm;
14188 {
14189 #if HAVE_GETTIMEOFDAY
14190
14191     struct timeval timeVal;
14192     struct timezone timeZone;
14193
14194     gettimeofday(&timeVal, &timeZone);
14195     tm->sec = (long) timeVal.tv_sec; 
14196     tm->ms = (int) (timeVal.tv_usec / 1000L);
14197
14198 #else /*!HAVE_GETTIMEOFDAY*/
14199 #if HAVE_FTIME
14200
14201 // include <sys/timeb.h> / moved to just above start of function
14202     struct timeb timeB;
14203
14204     ftime(&timeB);
14205     tm->sec = (long) timeB.time;
14206     tm->ms = (int) timeB.millitm;
14207
14208 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14209     tm->sec = (long) time(NULL);
14210     tm->ms = 0;
14211 #endif
14212 #endif
14213 }
14214
14215 /* Return the difference in milliseconds between two
14216    time marks.  We assume the difference will fit in a long!
14217 */
14218 long
14219 SubtractTimeMarks(tm2, tm1)
14220      TimeMark *tm2, *tm1;
14221 {
14222     return 1000L*(tm2->sec - tm1->sec) +
14223            (long) (tm2->ms - tm1->ms);
14224 }
14225
14226
14227 /*
14228  * Code to manage the game clocks.
14229  *
14230  * In tournament play, black starts the clock and then white makes a move.
14231  * We give the human user a slight advantage if he is playing white---the
14232  * clocks don't run until he makes his first move, so it takes zero time.
14233  * Also, we don't account for network lag, so we could get out of sync
14234  * with GNU Chess's clock -- but then, referees are always right.  
14235  */
14236
14237 static TimeMark tickStartTM;
14238 static long intendedTickLength;
14239
14240 long
14241 NextTickLength(timeRemaining)
14242      long timeRemaining;
14243 {
14244     long nominalTickLength, nextTickLength;
14245
14246     if (timeRemaining > 0L && timeRemaining <= 10000L)
14247       nominalTickLength = 100L;
14248     else
14249       nominalTickLength = 1000L;
14250     nextTickLength = timeRemaining % nominalTickLength;
14251     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14252
14253     return nextTickLength;
14254 }
14255
14256 /* Adjust clock one minute up or down */
14257 void
14258 AdjustClock(Boolean which, int dir)
14259 {
14260     if(which) blackTimeRemaining += 60000*dir;
14261     else      whiteTimeRemaining += 60000*dir;
14262     DisplayBothClocks();
14263 }
14264
14265 /* Stop clocks and reset to a fresh time control */
14266 void
14267 ResetClocks() 
14268 {
14269     (void) StopClockTimer();
14270     if (appData.icsActive) {
14271         whiteTimeRemaining = blackTimeRemaining = 0;
14272     } else if (searchTime) {
14273         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14274         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14275     } else { /* [HGM] correct new time quote for time odds */
14276         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14277         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14278     }
14279     if (whiteFlag || blackFlag) {
14280         DisplayTitle("");
14281         whiteFlag = blackFlag = FALSE;
14282     }
14283     DisplayBothClocks();
14284 }
14285
14286 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14287
14288 /* Decrement running clock by amount of time that has passed */
14289 void
14290 DecrementClocks()
14291 {
14292     long timeRemaining;
14293     long lastTickLength, fudge;
14294     TimeMark now;
14295
14296     if (!appData.clockMode) return;
14297     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14298         
14299     GetTimeMark(&now);
14300
14301     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14302
14303     /* Fudge if we woke up a little too soon */
14304     fudge = intendedTickLength - lastTickLength;
14305     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14306
14307     if (WhiteOnMove(forwardMostMove)) {
14308         if(whiteNPS >= 0) lastTickLength = 0;
14309         timeRemaining = whiteTimeRemaining -= lastTickLength;
14310         DisplayWhiteClock(whiteTimeRemaining - fudge,
14311                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14312     } else {
14313         if(blackNPS >= 0) lastTickLength = 0;
14314         timeRemaining = blackTimeRemaining -= lastTickLength;
14315         DisplayBlackClock(blackTimeRemaining - fudge,
14316                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14317     }
14318
14319     if (CheckFlags()) return;
14320         
14321     tickStartTM = now;
14322     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14323     StartClockTimer(intendedTickLength);
14324
14325     /* if the time remaining has fallen below the alarm threshold, sound the
14326      * alarm. if the alarm has sounded and (due to a takeback or time control
14327      * with increment) the time remaining has increased to a level above the
14328      * threshold, reset the alarm so it can sound again. 
14329      */
14330     
14331     if (appData.icsActive && appData.icsAlarm) {
14332
14333         /* make sure we are dealing with the user's clock */
14334         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14335                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14336            )) return;
14337
14338         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14339             alarmSounded = FALSE;
14340         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14341             PlayAlarmSound();
14342             alarmSounded = TRUE;
14343         }
14344     }
14345 }
14346
14347
14348 /* A player has just moved, so stop the previously running
14349    clock and (if in clock mode) start the other one.
14350    We redisplay both clocks in case we're in ICS mode, because
14351    ICS gives us an update to both clocks after every move.
14352    Note that this routine is called *after* forwardMostMove
14353    is updated, so the last fractional tick must be subtracted
14354    from the color that is *not* on move now.
14355 */
14356 void
14357 SwitchClocks(int newMoveNr)
14358 {
14359     long lastTickLength;
14360     TimeMark now;
14361     int flagged = FALSE;
14362
14363     GetTimeMark(&now);
14364
14365     if (StopClockTimer() && appData.clockMode) {
14366         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14367         if (!WhiteOnMove(forwardMostMove)) {
14368             if(blackNPS >= 0) lastTickLength = 0;
14369             blackTimeRemaining -= lastTickLength;
14370            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14371 //         if(pvInfoList[forwardMostMove-1].time == -1)
14372                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14373                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14374         } else {
14375            if(whiteNPS >= 0) lastTickLength = 0;
14376            whiteTimeRemaining -= lastTickLength;
14377            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14378 //         if(pvInfoList[forwardMostMove-1].time == -1)
14379                  pvInfoList[forwardMostMove-1].time = 
14380                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14381         }
14382         flagged = CheckFlags();
14383     }
14384     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14385     CheckTimeControl();
14386
14387     if (flagged || !appData.clockMode) return;
14388
14389     switch (gameMode) {
14390       case MachinePlaysBlack:
14391       case MachinePlaysWhite:
14392       case BeginningOfGame:
14393         if (pausing) return;
14394         break;
14395
14396       case EditGame:
14397       case PlayFromGameFile:
14398       case IcsExamining:
14399         return;
14400
14401       default:
14402         break;
14403     }
14404
14405     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14406         if(WhiteOnMove(forwardMostMove))
14407              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14408         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14409     }
14410
14411     tickStartTM = now;
14412     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14413       whiteTimeRemaining : blackTimeRemaining);
14414     StartClockTimer(intendedTickLength);
14415 }
14416         
14417
14418 /* Stop both clocks */
14419 void
14420 StopClocks()
14421 {       
14422     long lastTickLength;
14423     TimeMark now;
14424
14425     if (!StopClockTimer()) return;
14426     if (!appData.clockMode) return;
14427
14428     GetTimeMark(&now);
14429
14430     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14431     if (WhiteOnMove(forwardMostMove)) {
14432         if(whiteNPS >= 0) lastTickLength = 0;
14433         whiteTimeRemaining -= lastTickLength;
14434         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14435     } else {
14436         if(blackNPS >= 0) lastTickLength = 0;
14437         blackTimeRemaining -= lastTickLength;
14438         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14439     }
14440     CheckFlags();
14441 }
14442         
14443 /* Start clock of player on move.  Time may have been reset, so
14444    if clock is already running, stop and restart it. */
14445 void
14446 StartClocks()
14447 {
14448     (void) StopClockTimer(); /* in case it was running already */
14449     DisplayBothClocks();
14450     if (CheckFlags()) return;
14451
14452     if (!appData.clockMode) return;
14453     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14454
14455     GetTimeMark(&tickStartTM);
14456     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14457       whiteTimeRemaining : blackTimeRemaining);
14458
14459    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14460     whiteNPS = blackNPS = -1; 
14461     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14462        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14463         whiteNPS = first.nps;
14464     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14465        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14466         blackNPS = first.nps;
14467     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14468         whiteNPS = second.nps;
14469     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14470         blackNPS = second.nps;
14471     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14472
14473     StartClockTimer(intendedTickLength);
14474 }
14475
14476 char *
14477 TimeString(ms)
14478      long ms;
14479 {
14480     long second, minute, hour, day;
14481     char *sign = "";
14482     static char buf[32];
14483     
14484     if (ms > 0 && ms <= 9900) {
14485       /* convert milliseconds to tenths, rounding up */
14486       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14487
14488       sprintf(buf, " %03.1f ", tenths/10.0);
14489       return buf;
14490     }
14491
14492     /* convert milliseconds to seconds, rounding up */
14493     /* use floating point to avoid strangeness of integer division
14494        with negative dividends on many machines */
14495     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14496
14497     if (second < 0) {
14498         sign = "-";
14499         second = -second;
14500     }
14501     
14502     day = second / (60 * 60 * 24);
14503     second = second % (60 * 60 * 24);
14504     hour = second / (60 * 60);
14505     second = second % (60 * 60);
14506     minute = second / 60;
14507     second = second % 60;
14508     
14509     if (day > 0)
14510       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14511               sign, day, hour, minute, second);
14512     else if (hour > 0)
14513       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14514     else
14515       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14516     
14517     return buf;
14518 }
14519
14520
14521 /*
14522  * This is necessary because some C libraries aren't ANSI C compliant yet.
14523  */
14524 char *
14525 StrStr(string, match)
14526      char *string, *match;
14527 {
14528     int i, length;
14529     
14530     length = strlen(match);
14531     
14532     for (i = strlen(string) - length; i >= 0; i--, string++)
14533       if (!strncmp(match, string, length))
14534         return string;
14535     
14536     return NULL;
14537 }
14538
14539 char *
14540 StrCaseStr(string, match)
14541      char *string, *match;
14542 {
14543     int i, j, length;
14544     
14545     length = strlen(match);
14546     
14547     for (i = strlen(string) - length; i >= 0; i--, string++) {
14548         for (j = 0; j < length; j++) {
14549             if (ToLower(match[j]) != ToLower(string[j]))
14550               break;
14551         }
14552         if (j == length) return string;
14553     }
14554
14555     return NULL;
14556 }
14557
14558 #ifndef _amigados
14559 int
14560 StrCaseCmp(s1, s2)
14561      char *s1, *s2;
14562 {
14563     char c1, c2;
14564     
14565     for (;;) {
14566         c1 = ToLower(*s1++);
14567         c2 = ToLower(*s2++);
14568         if (c1 > c2) return 1;
14569         if (c1 < c2) return -1;
14570         if (c1 == NULLCHAR) return 0;
14571     }
14572 }
14573
14574
14575 int
14576 ToLower(c)
14577      int c;
14578 {
14579     return isupper(c) ? tolower(c) : c;
14580 }
14581
14582
14583 int
14584 ToUpper(c)
14585      int c;
14586 {
14587     return islower(c) ? toupper(c) : c;
14588 }
14589 #endif /* !_amigados    */
14590
14591 char *
14592 StrSave(s)
14593      char *s;
14594 {
14595     char *ret;
14596
14597     if ((ret = (char *) malloc(strlen(s) + 1))) {
14598         strcpy(ret, s);
14599     }
14600     return ret;
14601 }
14602
14603 char *
14604 StrSavePtr(s, savePtr)
14605      char *s, **savePtr;
14606 {
14607     if (*savePtr) {
14608         free(*savePtr);
14609     }
14610     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14611         strcpy(*savePtr, s);
14612     }
14613     return(*savePtr);
14614 }
14615
14616 char *
14617 PGNDate()
14618 {
14619     time_t clock;
14620     struct tm *tm;
14621     char buf[MSG_SIZ];
14622
14623     clock = time((time_t *)NULL);
14624     tm = localtime(&clock);
14625     sprintf(buf, "%04d.%02d.%02d",
14626             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14627     return StrSave(buf);
14628 }
14629
14630
14631 char *
14632 PositionToFEN(move, overrideCastling)
14633      int move;
14634      char *overrideCastling;
14635 {
14636     int i, j, fromX, fromY, toX, toY;
14637     int whiteToPlay;
14638     char buf[128];
14639     char *p, *q;
14640     int emptycount;
14641     ChessSquare piece;
14642
14643     whiteToPlay = (gameMode == EditPosition) ?
14644       !blackPlaysFirst : (move % 2 == 0);
14645     p = buf;
14646
14647     /* Piece placement data */
14648     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14649         emptycount = 0;
14650         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14651             if (boards[move][i][j] == EmptySquare) {
14652                 emptycount++;
14653             } else { ChessSquare piece = boards[move][i][j];
14654                 if (emptycount > 0) {
14655                     if(emptycount<10) /* [HGM] can be >= 10 */
14656                         *p++ = '0' + emptycount;
14657                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14658                     emptycount = 0;
14659                 }
14660                 if(PieceToChar(piece) == '+') {
14661                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14662                     *p++ = '+';
14663                     piece = (ChessSquare)(DEMOTED piece);
14664                 } 
14665                 *p++ = PieceToChar(piece);
14666                 if(p[-1] == '~') {
14667                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14668                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14669                     *p++ = '~';
14670                 }
14671             }
14672         }
14673         if (emptycount > 0) {
14674             if(emptycount<10) /* [HGM] can be >= 10 */
14675                 *p++ = '0' + emptycount;
14676             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14677             emptycount = 0;
14678         }
14679         *p++ = '/';
14680     }
14681     *(p - 1) = ' ';
14682
14683     /* [HGM] print Crazyhouse or Shogi holdings */
14684     if( gameInfo.holdingsWidth ) {
14685         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14686         q = p;
14687         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14688             piece = boards[move][i][BOARD_WIDTH-1];
14689             if( piece != EmptySquare )
14690               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14691                   *p++ = PieceToChar(piece);
14692         }
14693         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14694             piece = boards[move][BOARD_HEIGHT-i-1][0];
14695             if( piece != EmptySquare )
14696               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14697                   *p++ = PieceToChar(piece);
14698         }
14699
14700         if( q == p ) *p++ = '-';
14701         *p++ = ']';
14702         *p++ = ' ';
14703     }
14704
14705     /* Active color */
14706     *p++ = whiteToPlay ? 'w' : 'b';
14707     *p++ = ' ';
14708
14709   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14710     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14711   } else {
14712   if(nrCastlingRights) {
14713      q = p;
14714      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14715        /* [HGM] write directly from rights */
14716            if(boards[move][CASTLING][2] != NoRights &&
14717               boards[move][CASTLING][0] != NoRights   )
14718                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14719            if(boards[move][CASTLING][2] != NoRights &&
14720               boards[move][CASTLING][1] != NoRights   )
14721                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14722            if(boards[move][CASTLING][5] != NoRights &&
14723               boards[move][CASTLING][3] != NoRights   )
14724                 *p++ = boards[move][CASTLING][3] + AAA;
14725            if(boards[move][CASTLING][5] != NoRights &&
14726               boards[move][CASTLING][4] != NoRights   )
14727                 *p++ = boards[move][CASTLING][4] + AAA;
14728      } else {
14729
14730         /* [HGM] write true castling rights */
14731         if( nrCastlingRights == 6 ) {
14732             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14733                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14734             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14735                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14736             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14737                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14738             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14739                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14740         }
14741      }
14742      if (q == p) *p++ = '-'; /* No castling rights */
14743      *p++ = ' ';
14744   }
14745
14746   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14747      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14748     /* En passant target square */
14749     if (move > backwardMostMove) {
14750         fromX = moveList[move - 1][0] - AAA;
14751         fromY = moveList[move - 1][1] - ONE;
14752         toX = moveList[move - 1][2] - AAA;
14753         toY = moveList[move - 1][3] - ONE;
14754         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14755             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14756             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14757             fromX == toX) {
14758             /* 2-square pawn move just happened */
14759             *p++ = toX + AAA;
14760             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14761         } else {
14762             *p++ = '-';
14763         }
14764     } else if(move == backwardMostMove) {
14765         // [HGM] perhaps we should always do it like this, and forget the above?
14766         if((signed char)boards[move][EP_STATUS] >= 0) {
14767             *p++ = boards[move][EP_STATUS] + AAA;
14768             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14769         } else {
14770             *p++ = '-';
14771         }
14772     } else {
14773         *p++ = '-';
14774     }
14775     *p++ = ' ';
14776   }
14777   }
14778
14779     /* [HGM] find reversible plies */
14780     {   int i = 0, j=move;
14781
14782         if (appData.debugMode) { int k;
14783             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14784             for(k=backwardMostMove; k<=forwardMostMove; k++)
14785                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14786
14787         }
14788
14789         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14790         if( j == backwardMostMove ) i += initialRulePlies;
14791         sprintf(p, "%d ", i);
14792         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14793     }
14794     /* Fullmove number */
14795     sprintf(p, "%d", (move / 2) + 1);
14796     
14797     return StrSave(buf);
14798 }
14799
14800 Boolean
14801 ParseFEN(board, blackPlaysFirst, fen)
14802     Board board;
14803      int *blackPlaysFirst;
14804      char *fen;
14805 {
14806     int i, j;
14807     char *p, c;
14808     int emptycount;
14809     ChessSquare piece;
14810
14811     p = fen;
14812
14813     /* [HGM] by default clear Crazyhouse holdings, if present */
14814     if(gameInfo.holdingsWidth) {
14815        for(i=0; i<BOARD_HEIGHT; i++) {
14816            board[i][0]             = EmptySquare; /* black holdings */
14817            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14818            board[i][1]             = (ChessSquare) 0; /* black counts */
14819            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14820        }
14821     }
14822
14823     /* Piece placement data */
14824     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14825         j = 0;
14826         for (;;) {
14827             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14828                 if (*p == '/') p++;
14829                 emptycount = gameInfo.boardWidth - j;
14830                 while (emptycount--)
14831                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14832                 break;
14833 #if(BOARD_FILES >= 10)
14834             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14835                 p++; emptycount=10;
14836                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14837                 while (emptycount--)
14838                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14839 #endif
14840             } else if (isdigit(*p)) {
14841                 emptycount = *p++ - '0';
14842                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14843                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14844                 while (emptycount--)
14845                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14846             } else if (*p == '+' || isalpha(*p)) {
14847                 if (j >= gameInfo.boardWidth) return FALSE;
14848                 if(*p=='+') {
14849                     piece = CharToPiece(*++p);
14850                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14851                     piece = (ChessSquare) (PROMOTED piece ); p++;
14852                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14853                 } else piece = CharToPiece(*p++);
14854
14855                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14856                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14857                     piece = (ChessSquare) (PROMOTED piece);
14858                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14859                     p++;
14860                 }
14861                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14862             } else {
14863                 return FALSE;
14864             }
14865         }
14866     }
14867     while (*p == '/' || *p == ' ') p++;
14868
14869     /* [HGM] look for Crazyhouse holdings here */
14870     while(*p==' ') p++;
14871     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14872         if(*p == '[') p++;
14873         if(*p == '-' ) *p++; /* empty holdings */ else {
14874             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14875             /* if we would allow FEN reading to set board size, we would   */
14876             /* have to add holdings and shift the board read so far here   */
14877             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14878                 *p++;
14879                 if((int) piece >= (int) BlackPawn ) {
14880                     i = (int)piece - (int)BlackPawn;
14881                     i = PieceToNumber((ChessSquare)i);
14882                     if( i >= gameInfo.holdingsSize ) return FALSE;
14883                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14884                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14885                 } else {
14886                     i = (int)piece - (int)WhitePawn;
14887                     i = PieceToNumber((ChessSquare)i);
14888                     if( i >= gameInfo.holdingsSize ) return FALSE;
14889                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14890                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14891                 }
14892             }
14893         }
14894         if(*p == ']') *p++;
14895     }
14896
14897     while(*p == ' ') p++;
14898
14899     /* Active color */
14900     c = *p++;
14901     if(appData.colorNickNames) {
14902       if( c == appData.colorNickNames[0] ) c = 'w'; else
14903       if( c == appData.colorNickNames[1] ) c = 'b';
14904     }
14905     switch (c) {
14906       case 'w':
14907         *blackPlaysFirst = FALSE;
14908         break;
14909       case 'b': 
14910         *blackPlaysFirst = TRUE;
14911         break;
14912       default:
14913         return FALSE;
14914     }
14915
14916     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14917     /* return the extra info in global variiables             */
14918
14919     /* set defaults in case FEN is incomplete */
14920     board[EP_STATUS] = EP_UNKNOWN;
14921     for(i=0; i<nrCastlingRights; i++ ) {
14922         board[CASTLING][i] =
14923             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14924     }   /* assume possible unless obviously impossible */
14925     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14926     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14927     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14928                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14929     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14930     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14931     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14932                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14933     FENrulePlies = 0;
14934
14935     while(*p==' ') p++;
14936     if(nrCastlingRights) {
14937       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14938           /* castling indicator present, so default becomes no castlings */
14939           for(i=0; i<nrCastlingRights; i++ ) {
14940                  board[CASTLING][i] = NoRights;
14941           }
14942       }
14943       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14944              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14945              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14946              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14947         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14948
14949         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14950             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14951             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14952         }
14953         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14954             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14955         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14956                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14957         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14958                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14959         switch(c) {
14960           case'K':
14961               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14962               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14963               board[CASTLING][2] = whiteKingFile;
14964               break;
14965           case'Q':
14966               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14967               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14968               board[CASTLING][2] = whiteKingFile;
14969               break;
14970           case'k':
14971               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14972               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14973               board[CASTLING][5] = blackKingFile;
14974               break;
14975           case'q':
14976               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14977               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14978               board[CASTLING][5] = blackKingFile;
14979           case '-':
14980               break;
14981           default: /* FRC castlings */
14982               if(c >= 'a') { /* black rights */
14983                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14984                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14985                   if(i == BOARD_RGHT) break;
14986                   board[CASTLING][5] = i;
14987                   c -= AAA;
14988                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14989                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14990                   if(c > i)
14991                       board[CASTLING][3] = c;
14992                   else
14993                       board[CASTLING][4] = c;
14994               } else { /* white rights */
14995                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14996                     if(board[0][i] == WhiteKing) break;
14997                   if(i == BOARD_RGHT) break;
14998                   board[CASTLING][2] = i;
14999                   c -= AAA - 'a' + 'A';
15000                   if(board[0][c] >= WhiteKing) break;
15001                   if(c > i)
15002                       board[CASTLING][0] = c;
15003                   else
15004                       board[CASTLING][1] = c;
15005               }
15006         }
15007       }
15008       for(i=0; i<nrCastlingRights; i++)
15009         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15010     if (appData.debugMode) {
15011         fprintf(debugFP, "FEN castling rights:");
15012         for(i=0; i<nrCastlingRights; i++)
15013         fprintf(debugFP, " %d", board[CASTLING][i]);
15014         fprintf(debugFP, "\n");
15015     }
15016
15017       while(*p==' ') p++;
15018     }
15019
15020     /* read e.p. field in games that know e.p. capture */
15021     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15022        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15023       if(*p=='-') {
15024         p++; board[EP_STATUS] = EP_NONE;
15025       } else {
15026          char c = *p++ - AAA;
15027
15028          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15029          if(*p >= '0' && *p <='9') *p++;
15030          board[EP_STATUS] = c;
15031       }
15032     }
15033
15034
15035     if(sscanf(p, "%d", &i) == 1) {
15036         FENrulePlies = i; /* 50-move ply counter */
15037         /* (The move number is still ignored)    */
15038     }
15039
15040     return TRUE;
15041 }
15042       
15043 void
15044 EditPositionPasteFEN(char *fen)
15045 {
15046   if (fen != NULL) {
15047     Board initial_position;
15048
15049     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15050       DisplayError(_("Bad FEN position in clipboard"), 0);
15051       return ;
15052     } else {
15053       int savedBlackPlaysFirst = blackPlaysFirst;
15054       EditPositionEvent();
15055       blackPlaysFirst = savedBlackPlaysFirst;
15056       CopyBoard(boards[0], initial_position);
15057       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15058       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15059       DisplayBothClocks();
15060       DrawPosition(FALSE, boards[currentMove]);
15061     }
15062   }
15063 }
15064
15065 static char cseq[12] = "\\   ";
15066
15067 Boolean set_cont_sequence(char *new_seq)
15068 {
15069     int len;
15070     Boolean ret;
15071
15072     // handle bad attempts to set the sequence
15073         if (!new_seq)
15074                 return 0; // acceptable error - no debug
15075
15076     len = strlen(new_seq);
15077     ret = (len > 0) && (len < sizeof(cseq));
15078     if (ret)
15079         strcpy(cseq, new_seq);
15080     else if (appData.debugMode)
15081         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15082     return ret;
15083 }
15084
15085 /*
15086     reformat a source message so words don't cross the width boundary.  internal
15087     newlines are not removed.  returns the wrapped size (no null character unless
15088     included in source message).  If dest is NULL, only calculate the size required
15089     for the dest buffer.  lp argument indicats line position upon entry, and it's
15090     passed back upon exit.
15091 */
15092 int wrap(char *dest, char *src, int count, int width, int *lp)
15093 {
15094     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15095
15096     cseq_len = strlen(cseq);
15097     old_line = line = *lp;
15098     ansi = len = clen = 0;
15099
15100     for (i=0; i < count; i++)
15101     {
15102         if (src[i] == '\033')
15103             ansi = 1;
15104
15105         // if we hit the width, back up
15106         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15107         {
15108             // store i & len in case the word is too long
15109             old_i = i, old_len = len;
15110
15111             // find the end of the last word
15112             while (i && src[i] != ' ' && src[i] != '\n')
15113             {
15114                 i--;
15115                 len--;
15116             }
15117
15118             // word too long?  restore i & len before splitting it
15119             if ((old_i-i+clen) >= width)
15120             {
15121                 i = old_i;
15122                 len = old_len;
15123             }
15124
15125             // extra space?
15126             if (i && src[i-1] == ' ')
15127                 len--;
15128
15129             if (src[i] != ' ' && src[i] != '\n')
15130             {
15131                 i--;
15132                 if (len)
15133                     len--;
15134             }
15135
15136             // now append the newline and continuation sequence
15137             if (dest)
15138                 dest[len] = '\n';
15139             len++;
15140             if (dest)
15141                 strncpy(dest+len, cseq, cseq_len);
15142             len += cseq_len;
15143             line = cseq_len;
15144             clen = cseq_len;
15145             continue;
15146         }
15147
15148         if (dest)
15149             dest[len] = src[i];
15150         len++;
15151         if (!ansi)
15152             line++;
15153         if (src[i] == '\n')
15154             line = 0;
15155         if (src[i] == 'm')
15156             ansi = 0;
15157     }
15158     if (dest && appData.debugMode)
15159     {
15160         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15161             count, width, line, len, *lp);
15162         show_bytes(debugFP, src, count);
15163         fprintf(debugFP, "\ndest: ");
15164         show_bytes(debugFP, dest, len);
15165         fprintf(debugFP, "\n");
15166     }
15167     *lp = dest ? line : old_line;
15168
15169     return len;
15170 }
15171
15172 // [HGM] vari: routines for shelving variations
15173
15174 void 
15175 PushTail(int firstMove, int lastMove)
15176 {
15177         int i, j, nrMoves = lastMove - firstMove;
15178
15179         if(appData.icsActive) { // only in local mode
15180                 forwardMostMove = currentMove; // mimic old ICS behavior
15181                 return;
15182         }
15183         if(storedGames >= MAX_VARIATIONS-1) return;
15184
15185         // push current tail of game on stack
15186         savedResult[storedGames] = gameInfo.result;
15187         savedDetails[storedGames] = gameInfo.resultDetails;
15188         gameInfo.resultDetails = NULL;
15189         savedFirst[storedGames] = firstMove;
15190         savedLast [storedGames] = lastMove;
15191         savedFramePtr[storedGames] = framePtr;
15192         framePtr -= nrMoves; // reserve space for the boards
15193         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15194             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15195             for(j=0; j<MOVE_LEN; j++)
15196                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15197             for(j=0; j<2*MOVE_LEN; j++)
15198                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15199             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15200             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15201             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15202             pvInfoList[firstMove+i-1].depth = 0;
15203             commentList[framePtr+i] = commentList[firstMove+i];
15204             commentList[firstMove+i] = NULL;
15205         }
15206
15207         storedGames++;
15208         forwardMostMove = firstMove; // truncate game so we can start variation
15209         if(storedGames == 1) GreyRevert(FALSE);
15210 }
15211
15212 Boolean
15213 PopTail(Boolean annotate)
15214 {
15215         int i, j, nrMoves;
15216         char buf[8000], moveBuf[20];
15217
15218         if(appData.icsActive) return FALSE; // only in local mode
15219         if(!storedGames) return FALSE; // sanity
15220         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15221
15222         storedGames--;
15223         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15224         nrMoves = savedLast[storedGames] - currentMove;
15225         if(annotate) {
15226                 int cnt = 10;
15227                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15228                 else strcpy(buf, "(");
15229                 for(i=currentMove; i<forwardMostMove; i++) {
15230                         if(WhiteOnMove(i))
15231                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15232                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15233                         strcat(buf, moveBuf);
15234                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15235                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15236                 }
15237                 strcat(buf, ")");
15238         }
15239         for(i=1; i<=nrMoves; i++) { // copy last variation back
15240             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15241             for(j=0; j<MOVE_LEN; j++)
15242                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15243             for(j=0; j<2*MOVE_LEN; j++)
15244                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15245             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15246             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15247             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15248             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15249             commentList[currentMove+i] = commentList[framePtr+i];
15250             commentList[framePtr+i] = NULL;
15251         }
15252         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15253         framePtr = savedFramePtr[storedGames];
15254         gameInfo.result = savedResult[storedGames];
15255         if(gameInfo.resultDetails != NULL) {
15256             free(gameInfo.resultDetails);
15257       }
15258         gameInfo.resultDetails = savedDetails[storedGames];
15259         forwardMostMove = currentMove + nrMoves;
15260         if(storedGames == 0) GreyRevert(TRUE);
15261         return TRUE;
15262 }
15263
15264 void 
15265 CleanupTail()
15266 {       // remove all shelved variations
15267         int i;
15268         for(i=0; i<storedGames; i++) {
15269             if(savedDetails[i])
15270                 free(savedDetails[i]);
15271             savedDetails[i] = NULL;
15272         }
15273         for(i=framePtr; i<MAX_MOVES; i++) {
15274                 if(commentList[i]) free(commentList[i]);
15275                 commentList[i] = NULL;
15276         }
15277         framePtr = MAX_MOVES-1;
15278         storedGames = 0;
15279 }
15280
15281 void
15282 LoadVariation(int index, char *text)
15283 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15284         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15285         int level = 0, move;
15286
15287         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15288         // first find outermost bracketing variation
15289         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15290             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15291                 if(*p == '{') wait = '}'; else
15292                 if(*p == '[') wait = ']'; else
15293                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15294                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15295             }
15296             if(*p == wait) wait = NULLCHAR; // closing ]} found
15297             p++;
15298         }
15299         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15300         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15301         end[1] = NULLCHAR; // clip off comment beyond variation
15302         ToNrEvent(currentMove-1);
15303         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15304         // kludge: use ParsePV() to append variation to game
15305         move = currentMove;
15306         ParsePV(start, TRUE);
15307         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15308         ClearPremoveHighlights();
15309         CommentPopDown();
15310         ToNrEvent(currentMove+1);
15311 }
15312