efd709c44926a8be94d0297aaa93ccaacdc93af0
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 char partnerStatus[MSG_SIZ];
247 Boolean partnerUp;
248 Boolean originalFlip;
249 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
250 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
251 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
252 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
253 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
254 int opponentKibitzes;
255 int lastSavedGame; /* [HGM] save: ID of game */
256 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
257 extern int chatCount;
258 int chattingPartner;
259 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
260
261 /* States for ics_getting_history */
262 #define H_FALSE 0
263 #define H_REQUESTED 1
264 #define H_GOT_REQ_HEADER 2
265 #define H_GOT_UNREQ_HEADER 3
266 #define H_GETTING_MOVES 4
267 #define H_GOT_UNWANTED_HEADER 5
268
269 /* whosays values for GameEnds */
270 #define GE_ICS 0
271 #define GE_ENGINE 1
272 #define GE_PLAYER 2
273 #define GE_FILE 3
274 #define GE_XBOARD 4
275 #define GE_ENGINE1 5
276 #define GE_ENGINE2 6
277
278 /* Maximum number of games in a cmail message */
279 #define CMAIL_MAX_GAMES 20
280
281 /* Different types of move when calling RegisterMove */
282 #define CMAIL_MOVE   0
283 #define CMAIL_RESIGN 1
284 #define CMAIL_DRAW   2
285 #define CMAIL_ACCEPT 3
286
287 /* Different types of result to remember for each game */
288 #define CMAIL_NOT_RESULT 0
289 #define CMAIL_OLD_RESULT 1
290 #define CMAIL_NEW_RESULT 2
291
292 /* Telnet protocol constants */
293 #define TN_WILL 0373
294 #define TN_WONT 0374
295 #define TN_DO   0375
296 #define TN_DONT 0376
297 #define TN_IAC  0377
298 #define TN_ECHO 0001
299 #define TN_SGA  0003
300 #define TN_PORT 23
301
302 /* [AS] */
303 static char * safeStrCpy( char * dst, const char * src, size_t count )
304 {
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     strncpy( dst, src, count );
310     dst[ count-1 ] = '\0';
311     return dst;
312 }
313
314 /* Some compiler can't cast u64 to double
315  * This function do the job for us:
316
317  * We use the highest bit for cast, this only
318  * works if the highest bit is not
319  * in use (This should not happen)
320  *
321  * We used this for all compiler
322  */
323 double
324 u64ToDouble(u64 value)
325 {
326   double r;
327   u64 tmp = value & u64Const(0x7fffffffffffffff);
328   r = (double)(s64)tmp;
329   if (value & u64Const(0x8000000000000000))
330        r +=  9.2233720368547758080e18; /* 2^63 */
331  return r;
332 }
333
334 /* Fake up flags for now, as we aren't keeping track of castling
335    availability yet. [HGM] Change of logic: the flag now only
336    indicates the type of castlings allowed by the rule of the game.
337    The actual rights themselves are maintained in the array
338    castlingRights, as part of the game history, and are not probed
339    by this function.
340  */
341 int
342 PosFlags(index)
343 {
344   int flags = F_ALL_CASTLE_OK;
345   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
346   switch (gameInfo.variant) {
347   case VariantSuicide:
348     flags &= ~F_ALL_CASTLE_OK;
349   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
350     flags |= F_IGNORE_CHECK;
351   case VariantLosers:
352     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
353     break;
354   case VariantAtomic:
355     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
356     break;
357   case VariantKriegspiel:
358     flags |= F_KRIEGSPIEL_CAPTURE;
359     break;
360   case VariantCapaRandom: 
361   case VariantFischeRandom:
362     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
363   case VariantNoCastle:
364   case VariantShatranj:
365   case VariantCourier:
366   case VariantMakruk:
367     flags &= ~F_ALL_CASTLE_OK;
368     break;
369   default:
370     break;
371   }
372   return flags;
373 }
374
375 FILE *gameFileFP, *debugFP;
376
377 /* 
378     [AS] Note: sometimes, the sscanf() function is used to parse the input
379     into a fixed-size buffer. Because of this, we must be prepared to
380     receive strings as long as the size of the input buffer, which is currently
381     set to 4K for Windows and 8K for the rest.
382     So, we must either allocate sufficiently large buffers here, or
383     reduce the size of the input buffer in the input reading part.
384 */
385
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
388 char thinkOutput1[MSG_SIZ*10];
389
390 ChessProgramState first, second;
391
392 /* premove variables */
393 int premoveToX = 0;
394 int premoveToY = 0;
395 int premoveFromX = 0;
396 int premoveFromY = 0;
397 int premovePromoChar = 0;
398 int gotPremove = 0;
399 Boolean alarmSounded;
400 /* end premove variables */
401
402 char *ics_prefix = "$";
403 int ics_type = ICS_GENERIC;
404
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
406 int pauseExamForwardMostMove = 0;
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
413 int whiteFlag = FALSE, blackFlag = FALSE;
414 int userOfferedDraw = FALSE;
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
417 int cmailMoveType[CMAIL_MAX_GAMES];
418 long ics_clock_paused = 0;
419 ProcRef icsPR = NoProc, cmailPR = NoProc;
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
421 GameMode gameMode = BeginningOfGame;
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
425 int hiddenThinkOutputState = 0; /* [AS] */
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
427 int adjudicateLossPlies = 6;
428 char white_holding[64], black_holding[64];
429 TimeMark lastNodeCountTime;
430 long lastNodeCount=0;
431 int have_sent_ICS_logon = 0;
432 int movesPerSession;
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
434 long timeControl_2; /* [AS] Allow separate time controls */
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
436 long timeRemaining[2][MAX_MOVES];
437 int matchGame = 0;
438 TimeMark programStartTime;
439 char ics_handle[MSG_SIZ];
440 int have_set_title = 0;
441
442 /* animateTraining preserves the state of appData.animate
443  * when Training mode is activated. This allows the
444  * response to be animated when appData.animate == TRUE and
445  * appData.animateDragging == TRUE.
446  */
447 Boolean animateTraining;
448
449 GameInfo gameInfo;
450
451 AppData appData;
452
453 Board boards[MAX_MOVES];
454 /* [HGM] Following 7 needed for accurate legality tests: */
455 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
456 signed char  initialRights[BOARD_FILES];
457 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
458 int   initialRulePlies, FENrulePlies;
459 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
460 int loadFlag = 0; 
461 int shuffleOpenings;
462 int mute; // mute all sounds
463
464 // [HGM] vari: next 12 to save and restore variations
465 #define MAX_VARIATIONS 10
466 int framePtr = MAX_MOVES-1; // points to free stack entry
467 int storedGames = 0;
468 int savedFirst[MAX_VARIATIONS];
469 int savedLast[MAX_VARIATIONS];
470 int savedFramePtr[MAX_VARIATIONS];
471 char *savedDetails[MAX_VARIATIONS];
472 ChessMove savedResult[MAX_VARIATIONS];
473
474 void PushTail P((int firstMove, int lastMove));
475 Boolean PopTail P((Boolean annotate));
476 void CleanupTail P((void));
477
478 ChessSquare  FIDEArray[2][BOARD_FILES] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare twoKingsArray[2][BOARD_FILES] = {
486     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
487         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
489         BlackKing, BlackKing, BlackKnight, BlackRook }
490 };
491
492 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
493     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
494         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
495     { BlackRook, BlackMan, BlackBishop, BlackQueen,
496         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
497 };
498
499 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
500     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
501         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
502     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
503         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
504 };
505
506 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
508         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
510         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 };
512
513 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
514     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
515         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackMan, BlackFerz,
517         BlackKing, BlackMan, BlackKnight, BlackRook }
518 };
519
520
521 #if (BOARD_FILES>=10)
522 ChessSquare ShogiArray[2][BOARD_FILES] = {
523     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
524         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
525     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
526         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
527 };
528
529 ChessSquare XiangqiArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
531         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
533         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 };
535
536 ChessSquare CapablancaArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
538         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
540         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
541 };
542
543 ChessSquare GreatArray[2][BOARD_FILES] = {
544     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
545         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
546     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
547         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
548 };
549
550 ChessSquare JanusArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
552         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
553     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
554         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
555 };
556
557 #ifdef GOTHIC
558 ChessSquare GothicArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
560         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
562         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
563 };
564 #else // !GOTHIC
565 #define GothicArray CapablancaArray
566 #endif // !GOTHIC
567
568 #ifdef FALCON
569 ChessSquare FalconArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
571         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
573         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
574 };
575 #else // !FALCON
576 #define FalconArray CapablancaArray
577 #endif // !FALCON
578
579 #else // !(BOARD_FILES>=10)
580 #define XiangqiPosition FIDEArray
581 #define CapablancaArray FIDEArray
582 #define GothicArray FIDEArray
583 #define GreatArray FIDEArray
584 #endif // !(BOARD_FILES>=10)
585
586 #if (BOARD_FILES>=12)
587 ChessSquare CourierArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
589         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
590     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
591         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
592 };
593 #else // !(BOARD_FILES>=12)
594 #define CourierArray CapablancaArray
595 #endif // !(BOARD_FILES>=12)
596
597
598 Board initialPosition;
599
600
601 /* Convert str to a rating. Checks for special cases of "----",
602
603    "++++", etc. Also strips ()'s */
604 int
605 string_to_rating(str)
606   char *str;
607 {
608   while(*str && !isdigit(*str)) ++str;
609   if (!*str)
610     return 0;   /* One of the special "no rating" cases */
611   else
612     return atoi(str);
613 }
614
615 void
616 ClearProgramStats()
617 {
618     /* Init programStats */
619     programStats.movelist[0] = 0;
620     programStats.depth = 0;
621     programStats.nr_moves = 0;
622     programStats.moves_left = 0;
623     programStats.nodes = 0;
624     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
625     programStats.score = 0;
626     programStats.got_only_move = 0;
627     programStats.got_fail = 0;
628     programStats.line_is_book = 0;
629 }
630
631 void
632 InitBackEnd1()
633 {
634     int matched, min, sec;
635
636     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
637
638     GetTimeMark(&programStartTime);
639     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
640
641     ClearProgramStats();
642     programStats.ok_to_send = 1;
643     programStats.seen_stat = 0;
644
645     /*
646      * Initialize game list
647      */
648     ListNew(&gameList);
649
650
651     /*
652      * Internet chess server status
653      */
654     if (appData.icsActive) {
655         appData.matchMode = FALSE;
656         appData.matchGames = 0;
657 #if ZIPPY       
658         appData.noChessProgram = !appData.zippyPlay;
659 #else
660         appData.zippyPlay = FALSE;
661         appData.zippyTalk = FALSE;
662         appData.noChessProgram = TRUE;
663 #endif
664         if (*appData.icsHelper != NULLCHAR) {
665             appData.useTelnet = TRUE;
666             appData.telnetProgram = appData.icsHelper;
667         }
668     } else {
669         appData.zippyTalk = appData.zippyPlay = FALSE;
670     }
671
672     /* [AS] Initialize pv info list [HGM] and game state */
673     {
674         int i, j;
675
676         for( i=0; i<=framePtr; i++ ) {
677             pvInfoList[i].depth = -1;
678             boards[i][EP_STATUS] = EP_NONE;
679             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680         }
681     }
682
683     /*
684      * Parse timeControl resource
685      */
686     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
687                           appData.movesPerSession)) {
688         char buf[MSG_SIZ];
689         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
690         DisplayFatalError(buf, 0, 2);
691     }
692
693     /*
694      * Parse searchTime resource
695      */
696     if (*appData.searchTime != NULLCHAR) {
697         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
698         if (matched == 1) {
699             searchTime = min * 60;
700         } else if (matched == 2) {
701             searchTime = min * 60 + sec;
702         } else {
703             char buf[MSG_SIZ];
704             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
705             DisplayFatalError(buf, 0, 2);
706         }
707     }
708
709     /* [AS] Adjudication threshold */
710     adjudicateLossThreshold = appData.adjudicateLossThreshold;
711     
712     first.which = "first";
713     second.which = "second";
714     first.maybeThinking = second.maybeThinking = FALSE;
715     first.pr = second.pr = NoProc;
716     first.isr = second.isr = NULL;
717     first.sendTime = second.sendTime = 2;
718     first.sendDrawOffers = 1;
719     if (appData.firstPlaysBlack) {
720         first.twoMachinesColor = "black\n";
721         second.twoMachinesColor = "white\n";
722     } else {
723         first.twoMachinesColor = "white\n";
724         second.twoMachinesColor = "black\n";
725     }
726     first.program = appData.firstChessProgram;
727     second.program = appData.secondChessProgram;
728     first.host = appData.firstHost;
729     second.host = appData.secondHost;
730     first.dir = appData.firstDirectory;
731     second.dir = appData.secondDirectory;
732     first.other = &second;
733     second.other = &first;
734     first.initString = appData.initString;
735     second.initString = appData.secondInitString;
736     first.computerString = appData.firstComputerString;
737     second.computerString = appData.secondComputerString;
738     first.useSigint = second.useSigint = TRUE;
739     first.useSigterm = second.useSigterm = TRUE;
740     first.reuse = appData.reuseFirst;
741     second.reuse = appData.reuseSecond;
742     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
743     second.nps = appData.secondNPS;
744     first.useSetboard = second.useSetboard = FALSE;
745     first.useSAN = second.useSAN = FALSE;
746     first.usePing = second.usePing = FALSE;
747     first.lastPing = second.lastPing = 0;
748     first.lastPong = second.lastPong = 0;
749     first.usePlayother = second.usePlayother = FALSE;
750     first.useColors = second.useColors = TRUE;
751     first.useUsermove = second.useUsermove = FALSE;
752     first.sendICS = second.sendICS = FALSE;
753     first.sendName = second.sendName = appData.icsActive;
754     first.sdKludge = second.sdKludge = FALSE;
755     first.stKludge = second.stKludge = FALSE;
756     TidyProgramName(first.program, first.host, first.tidy);
757     TidyProgramName(second.program, second.host, second.tidy);
758     first.matchWins = second.matchWins = 0;
759     strcpy(first.variants, appData.variant);
760     strcpy(second.variants, appData.variant);
761     first.analysisSupport = second.analysisSupport = 2; /* detect */
762     first.analyzing = second.analyzing = FALSE;
763     first.initDone = second.initDone = FALSE;
764
765     /* New features added by Tord: */
766     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
767     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
768     /* End of new features added by Tord. */
769     first.fenOverride  = appData.fenOverride1;
770     second.fenOverride = appData.fenOverride2;
771
772     /* [HGM] time odds: set factor for each machine */
773     first.timeOdds  = appData.firstTimeOdds;
774     second.timeOdds = appData.secondTimeOdds;
775     { float norm = 1;
776         if(appData.timeOddsMode) {
777             norm = first.timeOdds;
778             if(norm > second.timeOdds) norm = second.timeOdds;
779         }
780         first.timeOdds /= norm;
781         second.timeOdds /= norm;
782     }
783
784     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
785     first.accumulateTC = appData.firstAccumulateTC;
786     second.accumulateTC = appData.secondAccumulateTC;
787     first.maxNrOfSessions = second.maxNrOfSessions = 1;
788
789     /* [HGM] debug */
790     first.debug = second.debug = FALSE;
791     first.supportsNPS = second.supportsNPS = UNKNOWN;
792
793     /* [HGM] options */
794     first.optionSettings  = appData.firstOptions;
795     second.optionSettings = appData.secondOptions;
796
797     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
798     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
799     first.isUCI = appData.firstIsUCI; /* [AS] */
800     second.isUCI = appData.secondIsUCI; /* [AS] */
801     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
802     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
803
804     if (appData.firstProtocolVersion > PROTOVER ||
805         appData.firstProtocolVersion < 1) {
806       char buf[MSG_SIZ];
807       sprintf(buf, _("protocol version %d not supported"),
808               appData.firstProtocolVersion);
809       DisplayFatalError(buf, 0, 2);
810     } else {
811       first.protocolVersion = appData.firstProtocolVersion;
812     }
813
814     if (appData.secondProtocolVersion > PROTOVER ||
815         appData.secondProtocolVersion < 1) {
816       char buf[MSG_SIZ];
817       sprintf(buf, _("protocol version %d not supported"),
818               appData.secondProtocolVersion);
819       DisplayFatalError(buf, 0, 2);
820     } else {
821       second.protocolVersion = appData.secondProtocolVersion;
822     }
823
824     if (appData.icsActive) {
825         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
826 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
827     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
828         appData.clockMode = FALSE;
829         first.sendTime = second.sendTime = 0;
830     }
831     
832 #if ZIPPY
833     /* Override some settings from environment variables, for backward
834        compatibility.  Unfortunately it's not feasible to have the env
835        vars just set defaults, at least in xboard.  Ugh.
836     */
837     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838       ZippyInit();
839     }
840 #endif
841     
842     if (appData.noChessProgram) {
843         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
844         sprintf(programVersion, "%s", PACKAGE_STRING);
845     } else {
846       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
847       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
848       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849     }
850
851     if (!appData.icsActive) {
852       char buf[MSG_SIZ];
853       /* Check for variants that are supported only in ICS mode,
854          or not at all.  Some that are accepted here nevertheless
855          have bugs; see comments below.
856       */
857       VariantClass variant = StringToVariant(appData.variant);
858       switch (variant) {
859       case VariantBughouse:     /* need four players and two boards */
860       case VariantKriegspiel:   /* need to hide pieces and move details */
861       /* case VariantFischeRandom: (Fabien: moved below) */
862         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
863         DisplayFatalError(buf, 0, 2);
864         return;
865
866       case VariantUnknown:
867       case VariantLoadable:
868       case Variant29:
869       case Variant30:
870       case Variant31:
871       case Variant32:
872       case Variant33:
873       case Variant34:
874       case Variant35:
875       case Variant36:
876       default:
877         sprintf(buf, _("Unknown variant name %s"), appData.variant);
878         DisplayFatalError(buf, 0, 2);
879         return;
880
881       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
882       case VariantFairy:      /* [HGM] TestLegality definitely off! */
883       case VariantGothic:     /* [HGM] should work */
884       case VariantCapablanca: /* [HGM] should work */
885       case VariantCourier:    /* [HGM] initial forced moves not implemented */
886       case VariantShogi:      /* [HGM] drops not tested for legality */
887       case VariantKnightmate: /* [HGM] should work */
888       case VariantCylinder:   /* [HGM] untested */
889       case VariantFalcon:     /* [HGM] untested */
890       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
891                                  offboard interposition not understood */
892       case VariantNormal:     /* definitely works! */
893       case VariantWildCastle: /* pieces not automatically shuffled */
894       case VariantNoCastle:   /* pieces not automatically shuffled */
895       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
896       case VariantLosers:     /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantSuicide:    /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantGiveaway:   /* should work except for win condition,
901                                  and doesn't know captures are mandatory */
902       case VariantTwoKings:   /* should work */
903       case VariantAtomic:     /* should work except for win condition */
904       case Variant3Check:     /* should work except for win condition */
905       case VariantShatranj:   /* should work except for all win conditions */
906       case VariantMakruk:     /* should work except for daw countdown */
907       case VariantBerolina:   /* might work if TestLegality is off */
908       case VariantCapaRandom: /* should work */
909       case VariantJanus:      /* should work */
910       case VariantSuper:      /* experimental */
911       case VariantGreat:      /* experimental, requires legality testing to be off */
912         break;
913       }
914     }
915
916     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
917     InitEngineUCI( installDir, &second );
918 }
919
920 int NextIntegerFromString( char ** str, long * value )
921 {
922     int result = -1;
923     char * s = *str;
924
925     while( *s == ' ' || *s == '\t' ) {
926         s++;
927     }
928
929     *value = 0;
930
931     if( *s >= '0' && *s <= '9' ) {
932         while( *s >= '0' && *s <= '9' ) {
933             *value = *value * 10 + (*s - '0');
934             s++;
935         }
936
937         result = 0;
938     }
939
940     *str = s;
941
942     return result;
943 }
944
945 int NextTimeControlFromString( char ** str, long * value )
946 {
947     long temp;
948     int result = NextIntegerFromString( str, &temp );
949
950     if( result == 0 ) {
951         *value = temp * 60; /* Minutes */
952         if( **str == ':' ) {
953             (*str)++;
954             result = NextIntegerFromString( str, &temp );
955             *value += temp; /* Seconds */
956         }
957     }
958
959     return result;
960 }
961
962 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
963 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
964     int result = -1; long temp, temp2;
965
966     if(**str != '+') return -1; // old params remain in force!
967     (*str)++;
968     if( NextTimeControlFromString( str, &temp ) ) return -1;
969
970     if(**str != '/') {
971         /* time only: incremental or sudden-death time control */
972         if(**str == '+') { /* increment follows; read it */
973             (*str)++;
974             if(result = NextIntegerFromString( str, &temp2)) return -1;
975             *inc = temp2 * 1000;
976         } else *inc = 0;
977         *moves = 0; *tc = temp * 1000; 
978         return 0;
979     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
980
981     (*str)++; /* classical time control */
982     result = NextTimeControlFromString( str, &temp2);
983     if(result == 0) {
984         *moves = temp/60;
985         *tc    = temp2 * 1000;
986         *inc   = 0;
987     }
988     return result;
989 }
990
991 int GetTimeQuota(int movenr)
992 {   /* [HGM] get time to add from the multi-session time-control string */
993     int moves=1; /* kludge to force reading of first session */
994     long time, increment;
995     char *s = fullTimeControlString;
996
997     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
998     do {
999         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1000         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1001         if(movenr == -1) return time;    /* last move before new session     */
1002         if(!moves) return increment;     /* current session is incremental   */
1003         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1004     } while(movenr >= -1);               /* try again for next session       */
1005
1006     return 0; // no new time quota on this move
1007 }
1008
1009 int
1010 ParseTimeControl(tc, ti, mps)
1011      char *tc;
1012      int ti;
1013      int mps;
1014 {
1015   long tc1;
1016   long tc2;
1017   char buf[MSG_SIZ];
1018   
1019   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1020   if(ti > 0) {
1021     if(mps)
1022       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1023     else sprintf(buf, "+%s+%d", tc, ti);
1024   } else {
1025     if(mps)
1026              sprintf(buf, "+%d/%s", mps, tc);
1027     else sprintf(buf, "+%s", tc);
1028   }
1029   fullTimeControlString = StrSave(buf);
1030   
1031   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032     return FALSE;
1033   }
1034   
1035   if( *tc == '/' ) {
1036     /* Parse second time control */
1037     tc++;
1038     
1039     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1040       return FALSE;
1041     }
1042     
1043     if( tc2 == 0 ) {
1044       return FALSE;
1045     }
1046     
1047     timeControl_2 = tc2 * 1000;
1048   }
1049   else {
1050     timeControl_2 = 0;
1051   }
1052   
1053   if( tc1 == 0 ) {
1054     return FALSE;
1055   }
1056   
1057   timeControl = tc1 * 1000;
1058   
1059   if (ti >= 0) {
1060     timeIncrement = ti * 1000;  /* convert to ms */
1061     movesPerSession = 0;
1062   } else {
1063     timeIncrement = 0;
1064     movesPerSession = mps;
1065   }
1066   return TRUE;
1067 }
1068
1069 void
1070 InitBackEnd2()
1071 {
1072     if (appData.debugMode) {
1073         fprintf(debugFP, "%s\n", programVersion);
1074     }
1075
1076     set_cont_sequence(appData.wrapContSeq);
1077     if (appData.matchGames > 0) {
1078         appData.matchMode = TRUE;
1079     } else if (appData.matchMode) {
1080         appData.matchGames = 1;
1081     }
1082     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1083         appData.matchGames = appData.sameColorGames;
1084     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1085         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1086         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1087     }
1088     Reset(TRUE, FALSE);
1089     if (appData.noChessProgram || first.protocolVersion == 1) {
1090       InitBackEnd3();
1091     } else {
1092       /* kludge: allow timeout for initial "feature" commands */
1093       FreezeUI();
1094       DisplayMessage("", _("Starting chess program"));
1095       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096     }
1097 }
1098
1099 void
1100 InitBackEnd3 P((void))
1101 {
1102     GameMode initialMode;
1103     char buf[MSG_SIZ];
1104     int err;
1105
1106     InitChessProgram(&first, startedFromSetupPosition);
1107
1108
1109     if (appData.icsActive) {
1110 #ifdef WIN32
1111         /* [DM] Make a console window if needed [HGM] merged ifs */
1112         ConsoleCreate(); 
1113 #endif
1114         err = establish();
1115         if (err != 0) {
1116             if (*appData.icsCommPort != NULLCHAR) {
1117                 sprintf(buf, _("Could not open comm port %s"),  
1118                         appData.icsCommPort);
1119             } else {
1120                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1121                         appData.icsHost, appData.icsPort);
1122             }
1123             DisplayFatalError(buf, err, 1);
1124             return;
1125         }
1126         SetICSMode();
1127         telnetISR =
1128           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1129         fromUserISR =
1130           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1131         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1132             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1133     } else if (appData.noChessProgram) {
1134         SetNCPMode();
1135     } else {
1136         SetGNUMode();
1137     }
1138
1139     if (*appData.cmailGameName != NULLCHAR) {
1140         SetCmailMode();
1141         OpenLoopback(&cmailPR);
1142         cmailISR =
1143           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1144     }
1145     
1146     ThawUI();
1147     DisplayMessage("", "");
1148     if (StrCaseCmp(appData.initialMode, "") == 0) {
1149       initialMode = BeginningOfGame;
1150     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1151       initialMode = TwoMachinesPlay;
1152     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1153       initialMode = AnalyzeFile; 
1154     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1155       initialMode = AnalyzeMode;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1157       initialMode = MachinePlaysWhite;
1158     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1159       initialMode = MachinePlaysBlack;
1160     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1161       initialMode = EditGame;
1162     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1163       initialMode = EditPosition;
1164     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1165       initialMode = Training;
1166     } else {
1167       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1168       DisplayFatalError(buf, 0, 2);
1169       return;
1170     }
1171
1172     if (appData.matchMode) {
1173         /* Set up machine vs. machine match */
1174         if (appData.noChessProgram) {
1175             DisplayFatalError(_("Can't have a match with no chess programs"),
1176                               0, 2);
1177             return;
1178         }
1179         matchMode = TRUE;
1180         matchGame = 1;
1181         if (*appData.loadGameFile != NULLCHAR) {
1182             int index = appData.loadGameIndex; // [HGM] autoinc
1183             if(index<0) lastIndex = index = 1;
1184             if (!LoadGameFromFile(appData.loadGameFile,
1185                                   index,
1186                                   appData.loadGameFile, FALSE)) {
1187                 DisplayFatalError(_("Bad game file"), 0, 1);
1188                 return;
1189             }
1190         } else if (*appData.loadPositionFile != NULLCHAR) {
1191             int index = appData.loadPositionIndex; // [HGM] autoinc
1192             if(index<0) lastIndex = index = 1;
1193             if (!LoadPositionFromFile(appData.loadPositionFile,
1194                                       index,
1195                                       appData.loadPositionFile)) {
1196                 DisplayFatalError(_("Bad position file"), 0, 1);
1197                 return;
1198             }
1199         }
1200         TwoMachinesEvent();
1201     } else if (*appData.cmailGameName != NULLCHAR) {
1202         /* Set up cmail mode */
1203         ReloadCmailMsgEvent(TRUE);
1204     } else {
1205         /* Set up other modes */
1206         if (initialMode == AnalyzeFile) {
1207           if (*appData.loadGameFile == NULLCHAR) {
1208             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1209             return;
1210           }
1211         }
1212         if (*appData.loadGameFile != NULLCHAR) {
1213             (void) LoadGameFromFile(appData.loadGameFile,
1214                                     appData.loadGameIndex,
1215                                     appData.loadGameFile, TRUE);
1216         } else if (*appData.loadPositionFile != NULLCHAR) {
1217             (void) LoadPositionFromFile(appData.loadPositionFile,
1218                                         appData.loadPositionIndex,
1219                                         appData.loadPositionFile);
1220             /* [HGM] try to make self-starting even after FEN load */
1221             /* to allow automatic setup of fairy variants with wtm */
1222             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1223                 gameMode = BeginningOfGame;
1224                 setboardSpoiledMachineBlack = 1;
1225             }
1226             /* [HGM] loadPos: make that every new game uses the setup */
1227             /* from file as long as we do not switch variant          */
1228             if(!blackPlaysFirst) {
1229                 startedFromPositionFile = TRUE;
1230                 CopyBoard(filePosition, boards[0]);
1231             }
1232         }
1233         if (initialMode == AnalyzeMode) {
1234           if (appData.noChessProgram) {
1235             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1236             return;
1237           }
1238           if (appData.icsActive) {
1239             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1240             return;
1241           }
1242           AnalyzeModeEvent();
1243         } else if (initialMode == AnalyzeFile) {
1244           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1245           ShowThinkingEvent();
1246           AnalyzeFileEvent();
1247           AnalysisPeriodicEvent(1);
1248         } else if (initialMode == MachinePlaysWhite) {
1249           if (appData.noChessProgram) {
1250             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1251                               0, 2);
1252             return;
1253           }
1254           if (appData.icsActive) {
1255             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1256                               0, 2);
1257             return;
1258           }
1259           MachineWhiteEvent();
1260         } else if (initialMode == MachinePlaysBlack) {
1261           if (appData.noChessProgram) {
1262             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1263                               0, 2);
1264             return;
1265           }
1266           if (appData.icsActive) {
1267             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1268                               0, 2);
1269             return;
1270           }
1271           MachineBlackEvent();
1272         } else if (initialMode == TwoMachinesPlay) {
1273           if (appData.noChessProgram) {
1274             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1275                               0, 2);
1276             return;
1277           }
1278           if (appData.icsActive) {
1279             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280                               0, 2);
1281             return;
1282           }
1283           TwoMachinesEvent();
1284         } else if (initialMode == EditGame) {
1285           EditGameEvent();
1286         } else if (initialMode == EditPosition) {
1287           EditPositionEvent();
1288         } else if (initialMode == Training) {
1289           if (*appData.loadGameFile == NULLCHAR) {
1290             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1291             return;
1292           }
1293           TrainingEvent();
1294         }
1295     }
1296 }
1297
1298 /*
1299  * Establish will establish a contact to a remote host.port.
1300  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1301  *  used to talk to the host.
1302  * Returns 0 if okay, error code if not.
1303  */
1304 int
1305 establish()
1306 {
1307     char buf[MSG_SIZ];
1308
1309     if (*appData.icsCommPort != NULLCHAR) {
1310         /* Talk to the host through a serial comm port */
1311         return OpenCommPort(appData.icsCommPort, &icsPR);
1312
1313     } else if (*appData.gateway != NULLCHAR) {
1314         if (*appData.remoteShell == NULLCHAR) {
1315             /* Use the rcmd protocol to run telnet program on a gateway host */
1316             snprintf(buf, sizeof(buf), "%s %s %s",
1317                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1318             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1319
1320         } else {
1321             /* Use the rsh program to run telnet program on a gateway host */
1322             if (*appData.remoteUser == NULLCHAR) {
1323                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1324                         appData.gateway, appData.telnetProgram,
1325                         appData.icsHost, appData.icsPort);
1326             } else {
1327                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1328                         appData.remoteShell, appData.gateway, 
1329                         appData.remoteUser, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             }
1332             return StartChildProcess(buf, "", &icsPR);
1333
1334         }
1335     } else if (appData.useTelnet) {
1336         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1337
1338     } else {
1339         /* TCP socket interface differs somewhat between
1340            Unix and NT; handle details in the front end.
1341            */
1342         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343     }
1344 }
1345
1346 void
1347 show_bytes(fp, buf, count)
1348      FILE *fp;
1349      char *buf;
1350      int count;
1351 {
1352     while (count--) {
1353         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1354             fprintf(fp, "\\%03o", *buf & 0xff);
1355         } else {
1356             putc(*buf, fp);
1357         }
1358         buf++;
1359     }
1360     fflush(fp);
1361 }
1362
1363 /* Returns an errno value */
1364 int
1365 OutputMaybeTelnet(pr, message, count, outError)
1366      ProcRef pr;
1367      char *message;
1368      int count;
1369      int *outError;
1370 {
1371     char buf[8192], *p, *q, *buflim;
1372     int left, newcount, outcount;
1373
1374     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1375         *appData.gateway != NULLCHAR) {
1376         if (appData.debugMode) {
1377             fprintf(debugFP, ">ICS: ");
1378             show_bytes(debugFP, message, count);
1379             fprintf(debugFP, "\n");
1380         }
1381         return OutputToProcess(pr, message, count, outError);
1382     }
1383
1384     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1385     p = message;
1386     q = buf;
1387     left = count;
1388     newcount = 0;
1389     while (left) {
1390         if (q >= buflim) {
1391             if (appData.debugMode) {
1392                 fprintf(debugFP, ">ICS: ");
1393                 show_bytes(debugFP, buf, newcount);
1394                 fprintf(debugFP, "\n");
1395             }
1396             outcount = OutputToProcess(pr, buf, newcount, outError);
1397             if (outcount < newcount) return -1; /* to be sure */
1398             q = buf;
1399             newcount = 0;
1400         }
1401         if (*p == '\n') {
1402             *q++ = '\r';
1403             newcount++;
1404         } else if (((unsigned char) *p) == TN_IAC) {
1405             *q++ = (char) TN_IAC;
1406             newcount ++;
1407         }
1408         *q++ = *p++;
1409         newcount++;
1410         left--;
1411     }
1412     if (appData.debugMode) {
1413         fprintf(debugFP, ">ICS: ");
1414         show_bytes(debugFP, buf, newcount);
1415         fprintf(debugFP, "\n");
1416     }
1417     outcount = OutputToProcess(pr, buf, newcount, outError);
1418     if (outcount < newcount) return -1; /* to be sure */
1419     return count;
1420 }
1421
1422 void
1423 read_from_player(isr, closure, message, count, error)
1424      InputSourceRef isr;
1425      VOIDSTAR closure;
1426      char *message;
1427      int count;
1428      int error;
1429 {
1430     int outError, outCount;
1431     static int gotEof = 0;
1432
1433     /* Pass data read from player on to ICS */
1434     if (count > 0) {
1435         gotEof = 0;
1436         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1437         if (outCount < count) {
1438             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1439         }
1440     } else if (count < 0) {
1441         RemoveInputSource(isr);
1442         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1443     } else if (gotEof++ > 0) {
1444         RemoveInputSource(isr);
1445         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1446     }
1447 }
1448
1449 void
1450 KeepAlive()
1451 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1452     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1453     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1454     SendToICS("date\n");
1455     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1456 }
1457
1458 /* added routine for printf style output to ics */
1459 void ics_printf(char *format, ...)
1460 {
1461     char buffer[MSG_SIZ];
1462     va_list args;
1463
1464     va_start(args, format);
1465     vsnprintf(buffer, sizeof(buffer), format, args);
1466     buffer[sizeof(buffer)-1] = '\0';
1467     SendToICS(buffer);
1468     va_end(args);
1469 }
1470
1471 void
1472 SendToICS(s)
1473      char *s;
1474 {
1475     int count, outCount, outError;
1476
1477     if (icsPR == NULL) return;
1478
1479     count = strlen(s);
1480     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1481     if (outCount < count) {
1482         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1483     }
1484 }
1485
1486 /* This is used for sending logon scripts to the ICS. Sending
1487    without a delay causes problems when using timestamp on ICC
1488    (at least on my machine). */
1489 void
1490 SendToICSDelayed(s,msdelay)
1491      char *s;
1492      long msdelay;
1493 {
1494     int count, outCount, outError;
1495
1496     if (icsPR == NULL) return;
1497
1498     count = strlen(s);
1499     if (appData.debugMode) {
1500         fprintf(debugFP, ">ICS: ");
1501         show_bytes(debugFP, s, count);
1502         fprintf(debugFP, "\n");
1503     }
1504     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1505                                       msdelay);
1506     if (outCount < count) {
1507         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508     }
1509 }
1510
1511
1512 /* Remove all highlighting escape sequences in s
1513    Also deletes any suffix starting with '(' 
1514    */
1515 char *
1516 StripHighlightAndTitle(s)
1517      char *s;
1518 {
1519     static char retbuf[MSG_SIZ];
1520     char *p = retbuf;
1521
1522     while (*s != NULLCHAR) {
1523         while (*s == '\033') {
1524             while (*s != NULLCHAR && !isalpha(*s)) s++;
1525             if (*s != NULLCHAR) s++;
1526         }
1527         while (*s != NULLCHAR && *s != '\033') {
1528             if (*s == '(' || *s == '[') {
1529                 *p = NULLCHAR;
1530                 return retbuf;
1531             }
1532             *p++ = *s++;
1533         }
1534     }
1535     *p = NULLCHAR;
1536     return retbuf;
1537 }
1538
1539 /* Remove all highlighting escape sequences in s */
1540 char *
1541 StripHighlight(s)
1542      char *s;
1543 {
1544     static char retbuf[MSG_SIZ];
1545     char *p = retbuf;
1546
1547     while (*s != NULLCHAR) {
1548         while (*s == '\033') {
1549             while (*s != NULLCHAR && !isalpha(*s)) s++;
1550             if (*s != NULLCHAR) s++;
1551         }
1552         while (*s != NULLCHAR && *s != '\033') {
1553             *p++ = *s++;
1554         }
1555     }
1556     *p = NULLCHAR;
1557     return retbuf;
1558 }
1559
1560 char *variantNames[] = VARIANT_NAMES;
1561 char *
1562 VariantName(v)
1563      VariantClass v;
1564 {
1565     return variantNames[v];
1566 }
1567
1568
1569 /* Identify a variant from the strings the chess servers use or the
1570    PGN Variant tag names we use. */
1571 VariantClass
1572 StringToVariant(e)
1573      char *e;
1574 {
1575     char *p;
1576     int wnum = -1;
1577     VariantClass v = VariantNormal;
1578     int i, found = FALSE;
1579     char buf[MSG_SIZ];
1580
1581     if (!e) return v;
1582
1583     /* [HGM] skip over optional board-size prefixes */
1584     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1585         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1586         while( *e++ != '_');
1587     }
1588
1589     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1590         v = VariantNormal;
1591         found = TRUE;
1592     } else
1593     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1594       if (StrCaseStr(e, variantNames[i])) {
1595         v = (VariantClass) i;
1596         found = TRUE;
1597         break;
1598       }
1599     }
1600
1601     if (!found) {
1602       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1603           || StrCaseStr(e, "wild/fr") 
1604           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1605         v = VariantFischeRandom;
1606       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1607                  (i = 1, p = StrCaseStr(e, "w"))) {
1608         p += i;
1609         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1610         if (isdigit(*p)) {
1611           wnum = atoi(p);
1612         } else {
1613           wnum = -1;
1614         }
1615         switch (wnum) {
1616         case 0: /* FICS only, actually */
1617         case 1:
1618           /* Castling legal even if K starts on d-file */
1619           v = VariantWildCastle;
1620           break;
1621         case 2:
1622         case 3:
1623         case 4:
1624           /* Castling illegal even if K & R happen to start in
1625              normal positions. */
1626           v = VariantNoCastle;
1627           break;
1628         case 5:
1629         case 7:
1630         case 8:
1631         case 10:
1632         case 11:
1633         case 12:
1634         case 13:
1635         case 14:
1636         case 15:
1637         case 18:
1638         case 19:
1639           /* Castling legal iff K & R start in normal positions */
1640           v = VariantNormal;
1641           break;
1642         case 6:
1643         case 20:
1644         case 21:
1645           /* Special wilds for position setup; unclear what to do here */
1646           v = VariantLoadable;
1647           break;
1648         case 9:
1649           /* Bizarre ICC game */
1650           v = VariantTwoKings;
1651           break;
1652         case 16:
1653           v = VariantKriegspiel;
1654           break;
1655         case 17:
1656           v = VariantLosers;
1657           break;
1658         case 22:
1659           v = VariantFischeRandom;
1660           break;
1661         case 23:
1662           v = VariantCrazyhouse;
1663           break;
1664         case 24:
1665           v = VariantBughouse;
1666           break;
1667         case 25:
1668           v = Variant3Check;
1669           break;
1670         case 26:
1671           /* Not quite the same as FICS suicide! */
1672           v = VariantGiveaway;
1673           break;
1674         case 27:
1675           v = VariantAtomic;
1676           break;
1677         case 28:
1678           v = VariantShatranj;
1679           break;
1680
1681         /* Temporary names for future ICC types.  The name *will* change in 
1682            the next xboard/WinBoard release after ICC defines it. */
1683         case 29:
1684           v = Variant29;
1685           break;
1686         case 30:
1687           v = Variant30;
1688           break;
1689         case 31:
1690           v = Variant31;
1691           break;
1692         case 32:
1693           v = Variant32;
1694           break;
1695         case 33:
1696           v = Variant33;
1697           break;
1698         case 34:
1699           v = Variant34;
1700           break;
1701         case 35:
1702           v = Variant35;
1703           break;
1704         case 36:
1705           v = Variant36;
1706           break;
1707         case 37:
1708           v = VariantShogi;
1709           break;
1710         case 38:
1711           v = VariantXiangqi;
1712           break;
1713         case 39:
1714           v = VariantCourier;
1715           break;
1716         case 40:
1717           v = VariantGothic;
1718           break;
1719         case 41:
1720           v = VariantCapablanca;
1721           break;
1722         case 42:
1723           v = VariantKnightmate;
1724           break;
1725         case 43:
1726           v = VariantFairy;
1727           break;
1728         case 44:
1729           v = VariantCylinder;
1730           break;
1731         case 45:
1732           v = VariantFalcon;
1733           break;
1734         case 46:
1735           v = VariantCapaRandom;
1736           break;
1737         case 47:
1738           v = VariantBerolina;
1739           break;
1740         case 48:
1741           v = VariantJanus;
1742           break;
1743         case 49:
1744           v = VariantSuper;
1745           break;
1746         case 50:
1747           v = VariantGreat;
1748           break;
1749         case -1:
1750           /* Found "wild" or "w" in the string but no number;
1751              must assume it's normal chess. */
1752           v = VariantNormal;
1753           break;
1754         default:
1755           sprintf(buf, _("Unknown wild type %d"), wnum);
1756           DisplayError(buf, 0);
1757           v = VariantUnknown;
1758           break;
1759         }
1760       }
1761     }
1762     if (appData.debugMode) {
1763       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1764               e, wnum, VariantName(v));
1765     }
1766     return v;
1767 }
1768
1769 static int leftover_start = 0, leftover_len = 0;
1770 char star_match[STAR_MATCH_N][MSG_SIZ];
1771
1772 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1773    advance *index beyond it, and set leftover_start to the new value of
1774    *index; else return FALSE.  If pattern contains the character '*', it
1775    matches any sequence of characters not containing '\r', '\n', or the
1776    character following the '*' (if any), and the matched sequence(s) are
1777    copied into star_match.
1778    */
1779 int
1780 looking_at(buf, index, pattern)
1781      char *buf;
1782      int *index;
1783      char *pattern;
1784 {
1785     char *bufp = &buf[*index], *patternp = pattern;
1786     int star_count = 0;
1787     char *matchp = star_match[0];
1788     
1789     for (;;) {
1790         if (*patternp == NULLCHAR) {
1791             *index = leftover_start = bufp - buf;
1792             *matchp = NULLCHAR;
1793             return TRUE;
1794         }
1795         if (*bufp == NULLCHAR) return FALSE;
1796         if (*patternp == '*') {
1797             if (*bufp == *(patternp + 1)) {
1798                 *matchp = NULLCHAR;
1799                 matchp = star_match[++star_count];
1800                 patternp += 2;
1801                 bufp++;
1802                 continue;
1803             } else if (*bufp == '\n' || *bufp == '\r') {
1804                 patternp++;
1805                 if (*patternp == NULLCHAR)
1806                   continue;
1807                 else
1808                   return FALSE;
1809             } else {
1810                 *matchp++ = *bufp++;
1811                 continue;
1812             }
1813         }
1814         if (*patternp != *bufp) return FALSE;
1815         patternp++;
1816         bufp++;
1817     }
1818 }
1819
1820 void
1821 SendToPlayer(data, length)
1822      char *data;
1823      int length;
1824 {
1825     int error, outCount;
1826     outCount = OutputToProcess(NoProc, data, length, &error);
1827     if (outCount < length) {
1828         DisplayFatalError(_("Error writing to display"), error, 1);
1829     }
1830 }
1831
1832 void
1833 PackHolding(packed, holding)
1834      char packed[];
1835      char *holding;
1836 {
1837     char *p = holding;
1838     char *q = packed;
1839     int runlength = 0;
1840     int curr = 9999;
1841     do {
1842         if (*p == curr) {
1843             runlength++;
1844         } else {
1845             switch (runlength) {
1846               case 0:
1847                 break;
1848               case 1:
1849                 *q++ = curr;
1850                 break;
1851               case 2:
1852                 *q++ = curr;
1853                 *q++ = curr;
1854                 break;
1855               default:
1856                 sprintf(q, "%d", runlength);
1857                 while (*q) q++;
1858                 *q++ = curr;
1859                 break;
1860             }
1861             runlength = 1;
1862             curr = *p;
1863         }
1864     } while (*p++);
1865     *q = NULLCHAR;
1866 }
1867
1868 /* Telnet protocol requests from the front end */
1869 void
1870 TelnetRequest(ddww, option)
1871      unsigned char ddww, option;
1872 {
1873     unsigned char msg[3];
1874     int outCount, outError;
1875
1876     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1877
1878     if (appData.debugMode) {
1879         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1880         switch (ddww) {
1881           case TN_DO:
1882             ddwwStr = "DO";
1883             break;
1884           case TN_DONT:
1885             ddwwStr = "DONT";
1886             break;
1887           case TN_WILL:
1888             ddwwStr = "WILL";
1889             break;
1890           case TN_WONT:
1891             ddwwStr = "WONT";
1892             break;
1893           default:
1894             ddwwStr = buf1;
1895             sprintf(buf1, "%d", ddww);
1896             break;
1897         }
1898         switch (option) {
1899           case TN_ECHO:
1900             optionStr = "ECHO";
1901             break;
1902           default:
1903             optionStr = buf2;
1904             sprintf(buf2, "%d", option);
1905             break;
1906         }
1907         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908     }
1909     msg[0] = TN_IAC;
1910     msg[1] = ddww;
1911     msg[2] = option;
1912     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1913     if (outCount < 3) {
1914         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1915     }
1916 }
1917
1918 void
1919 DoEcho()
1920 {
1921     if (!appData.icsActive) return;
1922     TelnetRequest(TN_DO, TN_ECHO);
1923 }
1924
1925 void
1926 DontEcho()
1927 {
1928     if (!appData.icsActive) return;
1929     TelnetRequest(TN_DONT, TN_ECHO);
1930 }
1931
1932 void
1933 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1934 {
1935     /* put the holdings sent to us by the server on the board holdings area */
1936     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1937     char p;
1938     ChessSquare piece;
1939
1940     if(gameInfo.holdingsWidth < 2)  return;
1941     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1942         return; // prevent overwriting by pre-board holdings
1943
1944     if( (int)lowestPiece >= BlackPawn ) {
1945         holdingsColumn = 0;
1946         countsColumn = 1;
1947         holdingsStartRow = BOARD_HEIGHT-1;
1948         direction = -1;
1949     } else {
1950         holdingsColumn = BOARD_WIDTH-1;
1951         countsColumn = BOARD_WIDTH-2;
1952         holdingsStartRow = 0;
1953         direction = 1;
1954     }
1955
1956     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1957         board[i][holdingsColumn] = EmptySquare;
1958         board[i][countsColumn]   = (ChessSquare) 0;
1959     }
1960     while( (p=*holdings++) != NULLCHAR ) {
1961         piece = CharToPiece( ToUpper(p) );
1962         if(piece == EmptySquare) continue;
1963         /*j = (int) piece - (int) WhitePawn;*/
1964         j = PieceToNumber(piece);
1965         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1966         if(j < 0) continue;               /* should not happen */
1967         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1968         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1969         board[holdingsStartRow+j*direction][countsColumn]++;
1970     }
1971 }
1972
1973
1974 void
1975 VariantSwitch(Board board, VariantClass newVariant)
1976 {
1977    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1978    Board oldBoard;
1979
1980    startedFromPositionFile = FALSE;
1981    if(gameInfo.variant == newVariant) return;
1982
1983    /* [HGM] This routine is called each time an assignment is made to
1984     * gameInfo.variant during a game, to make sure the board sizes
1985     * are set to match the new variant. If that means adding or deleting
1986     * holdings, we shift the playing board accordingly
1987     * This kludge is needed because in ICS observe mode, we get boards
1988     * of an ongoing game without knowing the variant, and learn about the
1989     * latter only later. This can be because of the move list we requested,
1990     * in which case the game history is refilled from the beginning anyway,
1991     * but also when receiving holdings of a crazyhouse game. In the latter
1992     * case we want to add those holdings to the already received position.
1993     */
1994
1995    
1996    if (appData.debugMode) {
1997      fprintf(debugFP, "Switch board from %s to %s\n",
1998              VariantName(gameInfo.variant), VariantName(newVariant));
1999      setbuf(debugFP, NULL);
2000    }
2001    shuffleOpenings = 0;       /* [HGM] shuffle */
2002    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2003    switch(newVariant) 
2004      {
2005      case VariantShogi:
2006        newWidth = 9;  newHeight = 9;
2007        gameInfo.holdingsSize = 7;
2008      case VariantBughouse:
2009      case VariantCrazyhouse:
2010        newHoldingsWidth = 2; break;
2011      case VariantGreat:
2012        newWidth = 10;
2013      case VariantSuper:
2014        newHoldingsWidth = 2;
2015        gameInfo.holdingsSize = 8;
2016        break;
2017      case VariantGothic:
2018      case VariantCapablanca:
2019      case VariantCapaRandom:
2020        newWidth = 10;
2021      default:
2022        newHoldingsWidth = gameInfo.holdingsSize = 0;
2023      };
2024    
2025    if(newWidth  != gameInfo.boardWidth  ||
2026       newHeight != gameInfo.boardHeight ||
2027       newHoldingsWidth != gameInfo.holdingsWidth ) {
2028      
2029      /* shift position to new playing area, if needed */
2030      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2031        for(i=0; i<BOARD_HEIGHT; i++) 
2032          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2033            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2034              board[i][j];
2035        for(i=0; i<newHeight; i++) {
2036          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2037          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2038        }
2039      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2040        for(i=0; i<BOARD_HEIGHT; i++)
2041          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2042            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2043              board[i][j];
2044      }
2045      gameInfo.boardWidth  = newWidth;
2046      gameInfo.boardHeight = newHeight;
2047      gameInfo.holdingsWidth = newHoldingsWidth;
2048      gameInfo.variant = newVariant;
2049      InitDrawingSizes(-2, 0);
2050    } else gameInfo.variant = newVariant;
2051    CopyBoard(oldBoard, board);   // remember correctly formatted board
2052      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2053    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2054 }
2055
2056 static int loggedOn = FALSE;
2057
2058 /*-- Game start info cache: --*/
2059 int gs_gamenum;
2060 char gs_kind[MSG_SIZ];
2061 static char player1Name[128] = "";
2062 static char player2Name[128] = "";
2063 static char cont_seq[] = "\n\\   ";
2064 static int player1Rating = -1;
2065 static int player2Rating = -1;
2066 /*----------------------------*/
2067
2068 ColorClass curColor = ColorNormal;
2069 int suppressKibitz = 0;
2070
2071 // [HGM] seekgraph
2072 Boolean soughtPending = FALSE;
2073 Boolean seekGraphUp;
2074 #define MAX_SEEK_ADS 200
2075 #define SQUARE 0x80
2076 char *seekAdList[MAX_SEEK_ADS];
2077 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2078 float tcList[MAX_SEEK_ADS];
2079 char colorList[MAX_SEEK_ADS];
2080 int nrOfSeekAds = 0;
2081 int minRating = 1010, maxRating = 2800;
2082 int hMargin = 10, vMargin = 20, h, w;
2083 extern int squareSize, lineGap;
2084
2085 void
2086 PlotSeekAd(int i)
2087 {
2088         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2089         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2090         if(r < minRating+100 && r >=0 ) r = minRating+100;
2091         if(r > maxRating) r = maxRating;
2092         if(tc < 1.) tc = 1.;
2093         if(tc > 95.) tc = 95.;
2094         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2095         y = ((double)r - minRating)/(maxRating - minRating)
2096             * (h-vMargin-squareSize/8-1) + vMargin;
2097         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2098         if(strstr(seekAdList[i], " u ")) color = 1;
2099         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2100            !strstr(seekAdList[i], "bullet") &&
2101            !strstr(seekAdList[i], "blitz") &&
2102            !strstr(seekAdList[i], "standard") ) color = 2;
2103         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2104         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2105 }
2106
2107 void
2108 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2109 {
2110         char buf[MSG_SIZ], *ext = "";
2111         VariantClass v = StringToVariant(type);
2112         if(strstr(type, "wild")) {
2113             ext = type + 4; // append wild number
2114             if(v == VariantFischeRandom) type = "chess960"; else
2115             if(v == VariantLoadable) type = "setup"; else
2116             type = VariantName(v);
2117         }
2118         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2119         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2120             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2121             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2122             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2123             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2124             seekNrList[nrOfSeekAds] = nr;
2125             zList[nrOfSeekAds] = 0;
2126             seekAdList[nrOfSeekAds++] = StrSave(buf);
2127             if(plot) PlotSeekAd(nrOfSeekAds-1);
2128         }
2129 }
2130
2131 void
2132 EraseSeekDot(int i)
2133 {
2134     int x = xList[i], y = yList[i], d=squareSize/4, k;
2135     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2136     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2137     // now replot every dot that overlapped
2138     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2139         int xx = xList[k], yy = yList[k];
2140         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2141             DrawSeekDot(xx, yy, colorList[k]);
2142     }
2143 }
2144
2145 void
2146 RemoveSeekAd(int nr)
2147 {
2148         int i;
2149         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2150             EraseSeekDot(i);
2151             if(seekAdList[i]) free(seekAdList[i]);
2152             seekAdList[i] = seekAdList[--nrOfSeekAds];
2153             seekNrList[i] = seekNrList[nrOfSeekAds];
2154             ratingList[i] = ratingList[nrOfSeekAds];
2155             colorList[i]  = colorList[nrOfSeekAds];
2156             tcList[i] = tcList[nrOfSeekAds];
2157             xList[i]  = xList[nrOfSeekAds];
2158             yList[i]  = yList[nrOfSeekAds];
2159             zList[i]  = zList[nrOfSeekAds];
2160             seekAdList[nrOfSeekAds] = NULL;
2161             break;
2162         }
2163 }
2164
2165 Boolean
2166 MatchSoughtLine(char *line)
2167 {
2168     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2169     int nr, base, inc, u=0; char dummy;
2170
2171     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2172        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2173        (u=1) &&
2174        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2175         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2176         // match: compact and save the line
2177         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2178         return TRUE;
2179     }
2180     return FALSE;
2181 }
2182
2183 int
2184 DrawSeekGraph()
2185 {
2186     if(!seekGraphUp) return FALSE;
2187     int i;
2188     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2189     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2190
2191     DrawSeekBackground(0, 0, w, h);
2192     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2193     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2194     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2195         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2196         yy = h-1-yy;
2197         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2198         if(i%500 == 0) {
2199             char buf[MSG_SIZ];
2200             sprintf(buf, "%d", i);
2201             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2202         }
2203     }
2204     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2205     for(i=1; i<100; i+=(i<10?1:5)) {
2206         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2207         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2208         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2209             char buf[MSG_SIZ];
2210             sprintf(buf, "%d", i);
2211             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2212         }
2213     }
2214     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2215     return TRUE;
2216 }
2217
2218 int SeekGraphClick(ClickType click, int x, int y, int moving)
2219 {
2220     static int lastDown = 0, displayed = 0, lastSecond;
2221     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2222         if(click == Release || moving) return FALSE;
2223         nrOfSeekAds = 0;
2224         soughtPending = TRUE;
2225         SendToICS(ics_prefix);
2226         SendToICS("sought\n"); // should this be "sought all"?
2227     } else { // issue challenge based on clicked ad
2228         int dist = 10000; int i, closest = 0, second = 0;
2229         for(i=0; i<nrOfSeekAds; i++) {
2230             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2231             if(d < dist) { dist = d; closest = i; }
2232             second += (d - zList[i] < 120); // count in-range ads
2233             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2234         }
2235         if(dist < 120) {
2236             char buf[MSG_SIZ];
2237             second = (second > 1);
2238             if(displayed != closest || second != lastSecond) {
2239                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2240                 lastSecond = second; displayed = closest;
2241             }
2242             if(click == Press) {
2243                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2244                 lastDown = closest;
2245                 return TRUE;
2246             } // on press 'hit', only show info
2247             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2248             sprintf(buf, "play %d\n", seekNrList[closest]);
2249             SendToICS(ics_prefix);
2250             SendToICS(buf);
2251             return TRUE; // let incoming board of started game pop down the graph
2252         } else if(click == Release) { // release 'miss' is ignored
2253             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2254             if(moving == 2) { // right up-click
2255                 nrOfSeekAds = 0; // refresh graph
2256                 soughtPending = TRUE;
2257                 SendToICS(ics_prefix);
2258                 SendToICS("sought\n"); // should this be "sought all"?
2259             }
2260             return TRUE;
2261         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2262         // press miss or release hit 'pop down' seek graph
2263         seekGraphUp = FALSE;
2264         DrawPosition(TRUE, NULL);
2265     }
2266     return TRUE;
2267 }
2268
2269 void
2270 read_from_ics(isr, closure, data, count, error)
2271      InputSourceRef isr;
2272      VOIDSTAR closure;
2273      char *data;
2274      int count;
2275      int error;
2276 {
2277 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2278 #define STARTED_NONE 0
2279 #define STARTED_MOVES 1
2280 #define STARTED_BOARD 2
2281 #define STARTED_OBSERVE 3
2282 #define STARTED_HOLDINGS 4
2283 #define STARTED_CHATTER 5
2284 #define STARTED_COMMENT 6
2285 #define STARTED_MOVES_NOHIDE 7
2286     
2287     static int started = STARTED_NONE;
2288     static char parse[20000];
2289     static int parse_pos = 0;
2290     static char buf[BUF_SIZE + 1];
2291     static int firstTime = TRUE, intfSet = FALSE;
2292     static ColorClass prevColor = ColorNormal;
2293     static int savingComment = FALSE;
2294     static int cmatch = 0; // continuation sequence match
2295     char *bp;
2296     char str[500];
2297     int i, oldi;
2298     int buf_len;
2299     int next_out;
2300     int tkind;
2301     int backup;    /* [DM] For zippy color lines */
2302     char *p;
2303     char talker[MSG_SIZ]; // [HGM] chat
2304     int channel;
2305
2306     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2307
2308     if (appData.debugMode) {
2309       if (!error) {
2310         fprintf(debugFP, "<ICS: ");
2311         show_bytes(debugFP, data, count);
2312         fprintf(debugFP, "\n");
2313       }
2314     }
2315
2316     if (appData.debugMode) { int f = forwardMostMove;
2317         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2318                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2319                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2320     }
2321     if (count > 0) {
2322         /* If last read ended with a partial line that we couldn't parse,
2323            prepend it to the new read and try again. */
2324         if (leftover_len > 0) {
2325             for (i=0; i<leftover_len; i++)
2326               buf[i] = buf[leftover_start + i];
2327         }
2328
2329     /* copy new characters into the buffer */
2330     bp = buf + leftover_len;
2331     buf_len=leftover_len;
2332     for (i=0; i<count; i++)
2333     {
2334         // ignore these
2335         if (data[i] == '\r')
2336             continue;
2337
2338         // join lines split by ICS?
2339         if (!appData.noJoin)
2340         {
2341             /*
2342                 Joining just consists of finding matches against the
2343                 continuation sequence, and discarding that sequence
2344                 if found instead of copying it.  So, until a match
2345                 fails, there's nothing to do since it might be the
2346                 complete sequence, and thus, something we don't want
2347                 copied.
2348             */
2349             if (data[i] == cont_seq[cmatch])
2350             {
2351                 cmatch++;
2352                 if (cmatch == strlen(cont_seq))
2353                 {
2354                     cmatch = 0; // complete match.  just reset the counter
2355
2356                     /*
2357                         it's possible for the ICS to not include the space
2358                         at the end of the last word, making our [correct]
2359                         join operation fuse two separate words.  the server
2360                         does this when the space occurs at the width setting.
2361                     */
2362                     if (!buf_len || buf[buf_len-1] != ' ')
2363                     {
2364                         *bp++ = ' ';
2365                         buf_len++;
2366                     }
2367                 }
2368                 continue;
2369             }
2370             else if (cmatch)
2371             {
2372                 /*
2373                     match failed, so we have to copy what matched before
2374                     falling through and copying this character.  In reality,
2375                     this will only ever be just the newline character, but
2376                     it doesn't hurt to be precise.
2377                 */
2378                 strncpy(bp, cont_seq, cmatch);
2379                 bp += cmatch;
2380                 buf_len += cmatch;
2381                 cmatch = 0;
2382             }
2383         }
2384
2385         // copy this char
2386         *bp++ = data[i];
2387         buf_len++;
2388     }
2389
2390         buf[buf_len] = NULLCHAR;
2391 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2392         next_out = 0;
2393         leftover_start = 0;
2394         
2395         i = 0;
2396         while (i < buf_len) {
2397             /* Deal with part of the TELNET option negotiation
2398                protocol.  We refuse to do anything beyond the
2399                defaults, except that we allow the WILL ECHO option,
2400                which ICS uses to turn off password echoing when we are
2401                directly connected to it.  We reject this option
2402                if localLineEditing mode is on (always on in xboard)
2403                and we are talking to port 23, which might be a real
2404                telnet server that will try to keep WILL ECHO on permanently.
2405              */
2406             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2407                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2408                 unsigned char option;
2409                 oldi = i;
2410                 switch ((unsigned char) buf[++i]) {
2411                   case TN_WILL:
2412                     if (appData.debugMode)
2413                       fprintf(debugFP, "\n<WILL ");
2414                     switch (option = (unsigned char) buf[++i]) {
2415                       case TN_ECHO:
2416                         if (appData.debugMode)
2417                           fprintf(debugFP, "ECHO ");
2418                         /* Reply only if this is a change, according
2419                            to the protocol rules. */
2420                         if (remoteEchoOption) break;
2421                         if (appData.localLineEditing &&
2422                             atoi(appData.icsPort) == TN_PORT) {
2423                             TelnetRequest(TN_DONT, TN_ECHO);
2424                         } else {
2425                             EchoOff();
2426                             TelnetRequest(TN_DO, TN_ECHO);
2427                             remoteEchoOption = TRUE;
2428                         }
2429                         break;
2430                       default:
2431                         if (appData.debugMode)
2432                           fprintf(debugFP, "%d ", option);
2433                         /* Whatever this is, we don't want it. */
2434                         TelnetRequest(TN_DONT, option);
2435                         break;
2436                     }
2437                     break;
2438                   case TN_WONT:
2439                     if (appData.debugMode)
2440                       fprintf(debugFP, "\n<WONT ");
2441                     switch (option = (unsigned char) buf[++i]) {
2442                       case TN_ECHO:
2443                         if (appData.debugMode)
2444                           fprintf(debugFP, "ECHO ");
2445                         /* Reply only if this is a change, according
2446                            to the protocol rules. */
2447                         if (!remoteEchoOption) break;
2448                         EchoOn();
2449                         TelnetRequest(TN_DONT, TN_ECHO);
2450                         remoteEchoOption = FALSE;
2451                         break;
2452                       default:
2453                         if (appData.debugMode)
2454                           fprintf(debugFP, "%d ", (unsigned char) option);
2455                         /* Whatever this is, it must already be turned
2456                            off, because we never agree to turn on
2457                            anything non-default, so according to the
2458                            protocol rules, we don't reply. */
2459                         break;
2460                     }
2461                     break;
2462                   case TN_DO:
2463                     if (appData.debugMode)
2464                       fprintf(debugFP, "\n<DO ");
2465                     switch (option = (unsigned char) buf[++i]) {
2466                       default:
2467                         /* Whatever this is, we refuse to do it. */
2468                         if (appData.debugMode)
2469                           fprintf(debugFP, "%d ", option);
2470                         TelnetRequest(TN_WONT, option);
2471                         break;
2472                     }
2473                     break;
2474                   case TN_DONT:
2475                     if (appData.debugMode)
2476                       fprintf(debugFP, "\n<DONT ");
2477                     switch (option = (unsigned char) buf[++i]) {
2478                       default:
2479                         if (appData.debugMode)
2480                           fprintf(debugFP, "%d ", option);
2481                         /* Whatever this is, we are already not doing
2482                            it, because we never agree to do anything
2483                            non-default, so according to the protocol
2484                            rules, we don't reply. */
2485                         break;
2486                     }
2487                     break;
2488                   case TN_IAC:
2489                     if (appData.debugMode)
2490                       fprintf(debugFP, "\n<IAC ");
2491                     /* Doubled IAC; pass it through */
2492                     i--;
2493                     break;
2494                   default:
2495                     if (appData.debugMode)
2496                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2497                     /* Drop all other telnet commands on the floor */
2498                     break;
2499                 }
2500                 if (oldi > next_out)
2501                   SendToPlayer(&buf[next_out], oldi - next_out);
2502                 if (++i > next_out)
2503                   next_out = i;
2504                 continue;
2505             }
2506                 
2507             /* OK, this at least will *usually* work */
2508             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2509                 loggedOn = TRUE;
2510             }
2511             
2512             if (loggedOn && !intfSet) {
2513                 if (ics_type == ICS_ICC) {
2514                   sprintf(str,
2515                           "/set-quietly interface %s\n/set-quietly style 12\n",
2516                           programVersion);
2517                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2518                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2519                 } else if (ics_type == ICS_CHESSNET) {
2520                   sprintf(str, "/style 12\n");
2521                 } else {
2522                   strcpy(str, "alias $ @\n$set interface ");
2523                   strcat(str, programVersion);
2524                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2525                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2526                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2527 #ifdef WIN32
2528                   strcat(str, "$iset nohighlight 1\n");
2529 #endif
2530                   strcat(str, "$iset lock 1\n$style 12\n");
2531                 }
2532                 SendToICS(str);
2533                 NotifyFrontendLogin();
2534                 intfSet = TRUE;
2535             }
2536
2537             if (started == STARTED_COMMENT) {
2538                 /* Accumulate characters in comment */
2539                 parse[parse_pos++] = buf[i];
2540                 if (buf[i] == '\n') {
2541                     parse[parse_pos] = NULLCHAR;
2542                     if(chattingPartner>=0) {
2543                         char mess[MSG_SIZ];
2544                         sprintf(mess, "%s%s", talker, parse);
2545                         OutputChatMessage(chattingPartner, mess);
2546                         chattingPartner = -1;
2547                         next_out = i+1; // [HGM] suppress printing in ICS window
2548                     } else
2549                     if(!suppressKibitz) // [HGM] kibitz
2550                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2551                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2552                         int nrDigit = 0, nrAlph = 0, j;
2553                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2554                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2555                         parse[parse_pos] = NULLCHAR;
2556                         // try to be smart: if it does not look like search info, it should go to
2557                         // ICS interaction window after all, not to engine-output window.
2558                         for(j=0; j<parse_pos; j++) { // count letters and digits
2559                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2560                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2561                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2562                         }
2563                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2564                             int depth=0; float score;
2565                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2566                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2567                                 pvInfoList[forwardMostMove-1].depth = depth;
2568                                 pvInfoList[forwardMostMove-1].score = 100*score;
2569                             }
2570                             OutputKibitz(suppressKibitz, parse);
2571                         } else {
2572                             char tmp[MSG_SIZ];
2573                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2574                             SendToPlayer(tmp, strlen(tmp));
2575                         }
2576                         next_out = i+1; // [HGM] suppress printing in ICS window
2577                     }
2578                     started = STARTED_NONE;
2579                 } else {
2580                     /* Don't match patterns against characters in comment */
2581                     i++;
2582                     continue;
2583                 }
2584             }
2585             if (started == STARTED_CHATTER) {
2586                 if (buf[i] != '\n') {
2587                     /* Don't match patterns against characters in chatter */
2588                     i++;
2589                     continue;
2590                 }
2591                 started = STARTED_NONE;
2592                 if(suppressKibitz) next_out = i+1;
2593             }
2594
2595             /* Kludge to deal with rcmd protocol */
2596             if (firstTime && looking_at(buf, &i, "\001*")) {
2597                 DisplayFatalError(&buf[1], 0, 1);
2598                 continue;
2599             } else {
2600                 firstTime = FALSE;
2601             }
2602
2603             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2604                 ics_type = ICS_ICC;
2605                 ics_prefix = "/";
2606                 if (appData.debugMode)
2607                   fprintf(debugFP, "ics_type %d\n", ics_type);
2608                 continue;
2609             }
2610             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2611                 ics_type = ICS_FICS;
2612                 ics_prefix = "$";
2613                 if (appData.debugMode)
2614                   fprintf(debugFP, "ics_type %d\n", ics_type);
2615                 continue;
2616             }
2617             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2618                 ics_type = ICS_CHESSNET;
2619                 ics_prefix = "/";
2620                 if (appData.debugMode)
2621                   fprintf(debugFP, "ics_type %d\n", ics_type);
2622                 continue;
2623             }
2624
2625             if (!loggedOn &&
2626                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2627                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2628                  looking_at(buf, &i, "will be \"*\""))) {
2629               strcpy(ics_handle, star_match[0]);
2630               continue;
2631             }
2632
2633             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2634               char buf[MSG_SIZ];
2635               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2636               DisplayIcsInteractionTitle(buf);
2637               have_set_title = TRUE;
2638             }
2639
2640             /* skip finger notes */
2641             if (started == STARTED_NONE &&
2642                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2643                  (buf[i] == '1' && buf[i+1] == '0')) &&
2644                 buf[i+2] == ':' && buf[i+3] == ' ') {
2645               started = STARTED_CHATTER;
2646               i += 3;
2647               continue;
2648             }
2649
2650             oldi = i;
2651             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2652             if(appData.seekGraph) {
2653                 if(soughtPending && MatchSoughtLine(buf+i)) {
2654                     i = strstr(buf+i, "rated") - buf;
2655                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2656                     next_out = leftover_start = i;
2657                     started = STARTED_CHATTER;
2658                     suppressKibitz = TRUE;
2659                     continue;
2660                 }
2661                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2662                         && looking_at(buf, &i, "* ads displayed")) {
2663                     soughtPending = FALSE;
2664                     seekGraphUp = TRUE;
2665                     DrawSeekGraph();
2666                     continue;
2667                 }
2668                 if(appData.autoRefresh) {
2669                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2670                         int s = (ics_type == ICS_ICC); // ICC format differs
2671                         if(seekGraphUp)
2672                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2673                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2674                         looking_at(buf, &i, "*% "); // eat prompt
2675                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2676                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2677                         next_out = i; // suppress
2678                         continue;
2679                     }
2680                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2681                         char *p = star_match[0];
2682                         while(*p) {
2683                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2684                             while(*p && *p++ != ' '); // next
2685                         }
2686                         looking_at(buf, &i, "*% "); // eat prompt
2687                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2688                         next_out = i;
2689                         continue;
2690                     }
2691                 }
2692             }
2693
2694             /* skip formula vars */
2695             if (started == STARTED_NONE &&
2696                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2697               started = STARTED_CHATTER;
2698               i += 3;
2699               continue;
2700             }
2701
2702             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2703             if (appData.autoKibitz && started == STARTED_NONE && 
2704                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2705                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2706                 if(looking_at(buf, &i, "* kibitzes: ") &&
2707                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2708                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2709                         suppressKibitz = TRUE;
2710                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2711                         next_out = i;
2712                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2713                                 && (gameMode == IcsPlayingWhite)) ||
2714                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2715                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2716                             started = STARTED_CHATTER; // own kibitz we simply discard
2717                         else {
2718                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2719                             parse_pos = 0; parse[0] = NULLCHAR;
2720                             savingComment = TRUE;
2721                             suppressKibitz = gameMode != IcsObserving ? 2 :
2722                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2723                         } 
2724                         continue;
2725                 } else
2726                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2727                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2728                          && atoi(star_match[0])) {
2729                     // suppress the acknowledgements of our own autoKibitz
2730                     char *p;
2731                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2732                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2733                     SendToPlayer(star_match[0], strlen(star_match[0]));
2734                     if(looking_at(buf, &i, "*% ")) // eat prompt
2735                         suppressKibitz = FALSE;
2736                     next_out = i;
2737                     continue;
2738                 }
2739             } // [HGM] kibitz: end of patch
2740
2741             // [HGM] chat: intercept tells by users for which we have an open chat window
2742             channel = -1;
2743             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2744                                            looking_at(buf, &i, "* whispers:") ||
2745                                            looking_at(buf, &i, "* shouts:") ||
2746                                            looking_at(buf, &i, "* c-shouts:") ||
2747                                            looking_at(buf, &i, "--> * ") ||
2748                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2749                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2750                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2751                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2752                 int p;
2753                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2754                 chattingPartner = -1;
2755
2756                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2757                 for(p=0; p<MAX_CHAT; p++) {
2758                     if(channel == atoi(chatPartner[p])) {
2759                     talker[0] = '['; strcat(talker, "] ");
2760                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2761                     chattingPartner = p; break;
2762                     }
2763                 } else
2764                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2765                 for(p=0; p<MAX_CHAT; p++) {
2766                     if(!strcmp("whispers", chatPartner[p])) {
2767                         talker[0] = '['; strcat(talker, "] ");
2768                         chattingPartner = p; break;
2769                     }
2770                 } else
2771                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2772                 for(p=0; p<MAX_CHAT; p++) {
2773                     if(!strcmp("shouts", chatPartner[p])) {
2774                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2775                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2776                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2777                         chattingPartner = p; break;
2778                     }
2779                 }
2780                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2781                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2782                     talker[0] = 0; Colorize(ColorTell, FALSE);
2783                     chattingPartner = p; break;
2784                 }
2785                 if(chattingPartner<0) i = oldi; else {
2786                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2787                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2788                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2789                     started = STARTED_COMMENT;
2790                     parse_pos = 0; parse[0] = NULLCHAR;
2791                     savingComment = 3 + chattingPartner; // counts as TRUE
2792                     suppressKibitz = TRUE;
2793                     continue;
2794                 }
2795             } // [HGM] chat: end of patch
2796
2797             if (appData.zippyTalk || appData.zippyPlay) {
2798                 /* [DM] Backup address for color zippy lines */
2799                 backup = i;
2800 #if ZIPPY
2801        #ifdef WIN32
2802                if (loggedOn == TRUE)
2803                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2804                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2805        #else
2806                 if (ZippyControl(buf, &i) ||
2807                     ZippyConverse(buf, &i) ||
2808                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2809                       loggedOn = TRUE;
2810                       if (!appData.colorize) continue;
2811                 }
2812        #endif
2813 #endif
2814             } // [DM] 'else { ' deleted
2815                 if (
2816                     /* Regular tells and says */
2817                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2818                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2819                     looking_at(buf, &i, "* says: ") ||
2820                     /* Don't color "message" or "messages" output */
2821                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2822                     looking_at(buf, &i, "*. * at *:*: ") ||
2823                     looking_at(buf, &i, "--* (*:*): ") ||
2824                     /* Message notifications (same color as tells) */
2825                     looking_at(buf, &i, "* has left a message ") ||
2826                     looking_at(buf, &i, "* just sent you a message:\n") ||
2827                     /* Whispers and kibitzes */
2828                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2829                     looking_at(buf, &i, "* kibitzes: ") ||
2830                     /* Channel tells */
2831                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2832
2833                   if (tkind == 1 && strchr(star_match[0], ':')) {
2834                       /* Avoid "tells you:" spoofs in channels */
2835                      tkind = 3;
2836                   }
2837                   if (star_match[0][0] == NULLCHAR ||
2838                       strchr(star_match[0], ' ') ||
2839                       (tkind == 3 && strchr(star_match[1], ' '))) {
2840                     /* Reject bogus matches */
2841                     i = oldi;
2842                   } else {
2843                     if (appData.colorize) {
2844                       if (oldi > next_out) {
2845                         SendToPlayer(&buf[next_out], oldi - next_out);
2846                         next_out = oldi;
2847                       }
2848                       switch (tkind) {
2849                       case 1:
2850                         Colorize(ColorTell, FALSE);
2851                         curColor = ColorTell;
2852                         break;
2853                       case 2:
2854                         Colorize(ColorKibitz, FALSE);
2855                         curColor = ColorKibitz;
2856                         break;
2857                       case 3:
2858                         p = strrchr(star_match[1], '(');
2859                         if (p == NULL) {
2860                           p = star_match[1];
2861                         } else {
2862                           p++;
2863                         }
2864                         if (atoi(p) == 1) {
2865                           Colorize(ColorChannel1, FALSE);
2866                           curColor = ColorChannel1;
2867                         } else {
2868                           Colorize(ColorChannel, FALSE);
2869                           curColor = ColorChannel;
2870                         }
2871                         break;
2872                       case 5:
2873                         curColor = ColorNormal;
2874                         break;
2875                       }
2876                     }
2877                     if (started == STARTED_NONE && appData.autoComment &&
2878                         (gameMode == IcsObserving ||
2879                          gameMode == IcsPlayingWhite ||
2880                          gameMode == IcsPlayingBlack)) {
2881                       parse_pos = i - oldi;
2882                       memcpy(parse, &buf[oldi], parse_pos);
2883                       parse[parse_pos] = NULLCHAR;
2884                       started = STARTED_COMMENT;
2885                       savingComment = TRUE;
2886                     } else {
2887                       started = STARTED_CHATTER;
2888                       savingComment = FALSE;
2889                     }
2890                     loggedOn = TRUE;
2891                     continue;
2892                   }
2893                 }
2894
2895                 if (looking_at(buf, &i, "* s-shouts: ") ||
2896                     looking_at(buf, &i, "* c-shouts: ")) {
2897                     if (appData.colorize) {
2898                         if (oldi > next_out) {
2899                             SendToPlayer(&buf[next_out], oldi - next_out);
2900                             next_out = oldi;
2901                         }
2902                         Colorize(ColorSShout, FALSE);
2903                         curColor = ColorSShout;
2904                     }
2905                     loggedOn = TRUE;
2906                     started = STARTED_CHATTER;
2907                     continue;
2908                 }
2909
2910                 if (looking_at(buf, &i, "--->")) {
2911                     loggedOn = TRUE;
2912                     continue;
2913                 }
2914
2915                 if (looking_at(buf, &i, "* shouts: ") ||
2916                     looking_at(buf, &i, "--> ")) {
2917                     if (appData.colorize) {
2918                         if (oldi > next_out) {
2919                             SendToPlayer(&buf[next_out], oldi - next_out);
2920                             next_out = oldi;
2921                         }
2922                         Colorize(ColorShout, FALSE);
2923                         curColor = ColorShout;
2924                     }
2925                     loggedOn = TRUE;
2926                     started = STARTED_CHATTER;
2927                     continue;
2928                 }
2929
2930                 if (looking_at( buf, &i, "Challenge:")) {
2931                     if (appData.colorize) {
2932                         if (oldi > next_out) {
2933                             SendToPlayer(&buf[next_out], oldi - next_out);
2934                             next_out = oldi;
2935                         }
2936                         Colorize(ColorChallenge, FALSE);
2937                         curColor = ColorChallenge;
2938                     }
2939                     loggedOn = TRUE;
2940                     continue;
2941                 }
2942
2943                 if (looking_at(buf, &i, "* offers you") ||
2944                     looking_at(buf, &i, "* offers to be") ||
2945                     looking_at(buf, &i, "* would like to") ||
2946                     looking_at(buf, &i, "* requests to") ||
2947                     looking_at(buf, &i, "Your opponent offers") ||
2948                     looking_at(buf, &i, "Your opponent requests")) {
2949
2950                     if (appData.colorize) {
2951                         if (oldi > next_out) {
2952                             SendToPlayer(&buf[next_out], oldi - next_out);
2953                             next_out = oldi;
2954                         }
2955                         Colorize(ColorRequest, FALSE);
2956                         curColor = ColorRequest;
2957                     }
2958                     continue;
2959                 }
2960
2961                 if (looking_at(buf, &i, "* (*) seeking")) {
2962                     if (appData.colorize) {
2963                         if (oldi > next_out) {
2964                             SendToPlayer(&buf[next_out], oldi - next_out);
2965                             next_out = oldi;
2966                         }
2967                         Colorize(ColorSeek, FALSE);
2968                         curColor = ColorSeek;
2969                     }
2970                     continue;
2971             }
2972
2973             if (looking_at(buf, &i, "\\   ")) {
2974                 if (prevColor != ColorNormal) {
2975                     if (oldi > next_out) {
2976                         SendToPlayer(&buf[next_out], oldi - next_out);
2977                         next_out = oldi;
2978                     }
2979                     Colorize(prevColor, TRUE);
2980                     curColor = prevColor;
2981                 }
2982                 if (savingComment) {
2983                     parse_pos = i - oldi;
2984                     memcpy(parse, &buf[oldi], parse_pos);
2985                     parse[parse_pos] = NULLCHAR;
2986                     started = STARTED_COMMENT;
2987                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2988                         chattingPartner = savingComment - 3; // kludge to remember the box
2989                 } else {
2990                     started = STARTED_CHATTER;
2991                 }
2992                 continue;
2993             }
2994
2995             if (looking_at(buf, &i, "Black Strength :") ||
2996                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2997                 looking_at(buf, &i, "<10>") ||
2998                 looking_at(buf, &i, "#@#")) {
2999                 /* Wrong board style */
3000                 loggedOn = TRUE;
3001                 SendToICS(ics_prefix);
3002                 SendToICS("set style 12\n");
3003                 SendToICS(ics_prefix);
3004                 SendToICS("refresh\n");
3005                 continue;
3006             }
3007             
3008             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3009                 ICSInitScript();
3010                 have_sent_ICS_logon = 1;
3011                 continue;
3012             }
3013               
3014             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3015                 (looking_at(buf, &i, "\n<12> ") ||
3016                  looking_at(buf, &i, "<12> "))) {
3017                 loggedOn = TRUE;
3018                 if (oldi > next_out) {
3019                     SendToPlayer(&buf[next_out], oldi - next_out);
3020                 }
3021                 next_out = i;
3022                 started = STARTED_BOARD;
3023                 parse_pos = 0;
3024                 continue;
3025             }
3026
3027             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3028                 looking_at(buf, &i, "<b1> ")) {
3029                 if (oldi > next_out) {
3030                     SendToPlayer(&buf[next_out], oldi - next_out);
3031                 }
3032                 next_out = i;
3033                 started = STARTED_HOLDINGS;
3034                 parse_pos = 0;
3035                 continue;
3036             }
3037
3038             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3039                 loggedOn = TRUE;
3040                 /* Header for a move list -- first line */
3041
3042                 switch (ics_getting_history) {
3043                   case H_FALSE:
3044                     switch (gameMode) {
3045                       case IcsIdle:
3046                       case BeginningOfGame:
3047                         /* User typed "moves" or "oldmoves" while we
3048                            were idle.  Pretend we asked for these
3049                            moves and soak them up so user can step
3050                            through them and/or save them.
3051                            */
3052                         Reset(FALSE, TRUE);
3053                         gameMode = IcsObserving;
3054                         ModeHighlight();
3055                         ics_gamenum = -1;
3056                         ics_getting_history = H_GOT_UNREQ_HEADER;
3057                         break;
3058                       case EditGame: /*?*/
3059                       case EditPosition: /*?*/
3060                         /* Should above feature work in these modes too? */
3061                         /* For now it doesn't */
3062                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3063                         break;
3064                       default:
3065                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3066                         break;
3067                     }
3068                     break;
3069                   case H_REQUESTED:
3070                     /* Is this the right one? */
3071                     if (gameInfo.white && gameInfo.black &&
3072                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3073                         strcmp(gameInfo.black, star_match[2]) == 0) {
3074                         /* All is well */
3075                         ics_getting_history = H_GOT_REQ_HEADER;
3076                     }
3077                     break;
3078                   case H_GOT_REQ_HEADER:
3079                   case H_GOT_UNREQ_HEADER:
3080                   case H_GOT_UNWANTED_HEADER:
3081                   case H_GETTING_MOVES:
3082                     /* Should not happen */
3083                     DisplayError(_("Error gathering move list: two headers"), 0);
3084                     ics_getting_history = H_FALSE;
3085                     break;
3086                 }
3087
3088                 /* Save player ratings into gameInfo if needed */
3089                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3090                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3091                     (gameInfo.whiteRating == -1 ||
3092                      gameInfo.blackRating == -1)) {
3093
3094                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3095                     gameInfo.blackRating = string_to_rating(star_match[3]);
3096                     if (appData.debugMode)
3097                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3098                               gameInfo.whiteRating, gameInfo.blackRating);
3099                 }
3100                 continue;
3101             }
3102
3103             if (looking_at(buf, &i,
3104               "* * match, initial time: * minute*, increment: * second")) {
3105                 /* Header for a move list -- second line */
3106                 /* Initial board will follow if this is a wild game */
3107                 if (gameInfo.event != NULL) free(gameInfo.event);
3108                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3109                 gameInfo.event = StrSave(str);
3110                 /* [HGM] we switched variant. Translate boards if needed. */
3111                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3112                 continue;
3113             }
3114
3115             if (looking_at(buf, &i, "Move  ")) {
3116                 /* Beginning of a move list */
3117                 switch (ics_getting_history) {
3118                   case H_FALSE:
3119                     /* Normally should not happen */
3120                     /* Maybe user hit reset while we were parsing */
3121                     break;
3122                   case H_REQUESTED:
3123                     /* Happens if we are ignoring a move list that is not
3124                      * the one we just requested.  Common if the user
3125                      * tries to observe two games without turning off
3126                      * getMoveList */
3127                     break;
3128                   case H_GETTING_MOVES:
3129                     /* Should not happen */
3130                     DisplayError(_("Error gathering move list: nested"), 0);
3131                     ics_getting_history = H_FALSE;
3132                     break;
3133                   case H_GOT_REQ_HEADER:
3134                     ics_getting_history = H_GETTING_MOVES;
3135                     started = STARTED_MOVES;
3136                     parse_pos = 0;
3137                     if (oldi > next_out) {
3138                         SendToPlayer(&buf[next_out], oldi - next_out);
3139                     }
3140                     break;
3141                   case H_GOT_UNREQ_HEADER:
3142                     ics_getting_history = H_GETTING_MOVES;
3143                     started = STARTED_MOVES_NOHIDE;
3144                     parse_pos = 0;
3145                     break;
3146                   case H_GOT_UNWANTED_HEADER:
3147                     ics_getting_history = H_FALSE;
3148                     break;
3149                 }
3150                 continue;
3151             }                           
3152             
3153             if (looking_at(buf, &i, "% ") ||
3154                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3155                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3156                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3157                     soughtPending = FALSE;
3158                     seekGraphUp = TRUE;
3159                     DrawSeekGraph();
3160                 }
3161                 if(suppressKibitz) next_out = i;
3162                 savingComment = FALSE;
3163                 suppressKibitz = 0;
3164                 switch (started) {
3165                   case STARTED_MOVES:
3166                   case STARTED_MOVES_NOHIDE:
3167                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3168                     parse[parse_pos + i - oldi] = NULLCHAR;
3169                     ParseGameHistory(parse);
3170 #if ZIPPY
3171                     if (appData.zippyPlay && first.initDone) {
3172                         FeedMovesToProgram(&first, forwardMostMove);
3173                         if (gameMode == IcsPlayingWhite) {
3174                             if (WhiteOnMove(forwardMostMove)) {
3175                                 if (first.sendTime) {
3176                                   if (first.useColors) {
3177                                     SendToProgram("black\n", &first); 
3178                                   }
3179                                   SendTimeRemaining(&first, TRUE);
3180                                 }
3181                                 if (first.useColors) {
3182                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3183                                 }
3184                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3185                                 first.maybeThinking = TRUE;
3186                             } else {
3187                                 if (first.usePlayother) {
3188                                   if (first.sendTime) {
3189                                     SendTimeRemaining(&first, TRUE);
3190                                   }
3191                                   SendToProgram("playother\n", &first);
3192                                   firstMove = FALSE;
3193                                 } else {
3194                                   firstMove = TRUE;
3195                                 }
3196                             }
3197                         } else if (gameMode == IcsPlayingBlack) {
3198                             if (!WhiteOnMove(forwardMostMove)) {
3199                                 if (first.sendTime) {
3200                                   if (first.useColors) {
3201                                     SendToProgram("white\n", &first);
3202                                   }
3203                                   SendTimeRemaining(&first, FALSE);
3204                                 }
3205                                 if (first.useColors) {
3206                                   SendToProgram("black\n", &first);
3207                                 }
3208                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3209                                 first.maybeThinking = TRUE;
3210                             } else {
3211                                 if (first.usePlayother) {
3212                                   if (first.sendTime) {
3213                                     SendTimeRemaining(&first, FALSE);
3214                                   }
3215                                   SendToProgram("playother\n", &first);
3216                                   firstMove = FALSE;
3217                                 } else {
3218                                   firstMove = TRUE;
3219                                 }
3220                             }
3221                         }                       
3222                     }
3223 #endif
3224                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3225                         /* Moves came from oldmoves or moves command
3226                            while we weren't doing anything else.
3227                            */
3228                         currentMove = forwardMostMove;
3229                         ClearHighlights();/*!!could figure this out*/
3230                         flipView = appData.flipView;
3231                         DrawPosition(TRUE, boards[currentMove]);
3232                         DisplayBothClocks();
3233                         sprintf(str, "%s vs. %s",
3234                                 gameInfo.white, gameInfo.black);
3235                         DisplayTitle(str);
3236                         gameMode = IcsIdle;
3237                     } else {
3238                         /* Moves were history of an active game */
3239                         if (gameInfo.resultDetails != NULL) {
3240                             free(gameInfo.resultDetails);
3241                             gameInfo.resultDetails = NULL;
3242                         }
3243                     }
3244                     HistorySet(parseList, backwardMostMove,
3245                                forwardMostMove, currentMove-1);
3246                     DisplayMove(currentMove - 1);
3247                     if (started == STARTED_MOVES) next_out = i;
3248                     started = STARTED_NONE;
3249                     ics_getting_history = H_FALSE;
3250                     break;
3251
3252                   case STARTED_OBSERVE:
3253                     started = STARTED_NONE;
3254                     SendToICS(ics_prefix);
3255                     SendToICS("refresh\n");
3256                     break;
3257
3258                   default:
3259                     break;
3260                 }
3261                 if(bookHit) { // [HGM] book: simulate book reply
3262                     static char bookMove[MSG_SIZ]; // a bit generous?
3263
3264                     programStats.nodes = programStats.depth = programStats.time = 
3265                     programStats.score = programStats.got_only_move = 0;
3266                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3267
3268                     strcpy(bookMove, "move ");
3269                     strcat(bookMove, bookHit);
3270                     HandleMachineMove(bookMove, &first);
3271                 }
3272                 continue;
3273             }
3274             
3275             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3276                  started == STARTED_HOLDINGS ||
3277                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3278                 /* Accumulate characters in move list or board */
3279                 parse[parse_pos++] = buf[i];
3280             }
3281             
3282             /* Start of game messages.  Mostly we detect start of game
3283                when the first board image arrives.  On some versions
3284                of the ICS, though, we need to do a "refresh" after starting
3285                to observe in order to get the current board right away. */
3286             if (looking_at(buf, &i, "Adding game * to observation list")) {
3287                 started = STARTED_OBSERVE;
3288                 continue;
3289             }
3290
3291             /* Handle auto-observe */
3292             if (appData.autoObserve &&
3293                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3294                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3295                 char *player;
3296                 /* Choose the player that was highlighted, if any. */
3297                 if (star_match[0][0] == '\033' ||
3298                     star_match[1][0] != '\033') {
3299                     player = star_match[0];
3300                 } else {
3301                     player = star_match[2];
3302                 }
3303                 sprintf(str, "%sobserve %s\n",
3304                         ics_prefix, StripHighlightAndTitle(player));
3305                 SendToICS(str);
3306
3307                 /* Save ratings from notify string */
3308                 strcpy(player1Name, star_match[0]);
3309                 player1Rating = string_to_rating(star_match[1]);
3310                 strcpy(player2Name, star_match[2]);
3311                 player2Rating = string_to_rating(star_match[3]);
3312
3313                 if (appData.debugMode)
3314                   fprintf(debugFP, 
3315                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3316                           player1Name, player1Rating,
3317                           player2Name, player2Rating);
3318
3319                 continue;
3320             }
3321
3322             /* Deal with automatic examine mode after a game,
3323                and with IcsObserving -> IcsExamining transition */
3324             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3325                 looking_at(buf, &i, "has made you an examiner of game *")) {
3326
3327                 int gamenum = atoi(star_match[0]);
3328                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3329                     gamenum == ics_gamenum) {
3330                     /* We were already playing or observing this game;
3331                        no need to refetch history */
3332                     gameMode = IcsExamining;
3333                     if (pausing) {
3334                         pauseExamForwardMostMove = forwardMostMove;
3335                     } else if (currentMove < forwardMostMove) {
3336                         ForwardInner(forwardMostMove);
3337                     }
3338                 } else {
3339                     /* I don't think this case really can happen */
3340                     SendToICS(ics_prefix);
3341                     SendToICS("refresh\n");
3342                 }
3343                 continue;
3344             }    
3345             
3346             /* Error messages */
3347 //          if (ics_user_moved) {
3348             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3349                 if (looking_at(buf, &i, "Illegal move") ||
3350                     looking_at(buf, &i, "Not a legal move") ||
3351                     looking_at(buf, &i, "Your king is in check") ||
3352                     looking_at(buf, &i, "It isn't your turn") ||
3353                     looking_at(buf, &i, "It is not your move")) {
3354                     /* Illegal move */
3355                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3356                         currentMove = forwardMostMove-1;
3357                         DisplayMove(currentMove - 1); /* before DMError */
3358                         DrawPosition(FALSE, boards[currentMove]);
3359                         SwitchClocks(forwardMostMove-1); // [HGM] race
3360                         DisplayBothClocks();
3361                     }
3362                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3363                     ics_user_moved = 0;
3364                     continue;
3365                 }
3366             }
3367
3368             if (looking_at(buf, &i, "still have time") ||
3369                 looking_at(buf, &i, "not out of time") ||
3370                 looking_at(buf, &i, "either player is out of time") ||
3371                 looking_at(buf, &i, "has timeseal; checking")) {
3372                 /* We must have called his flag a little too soon */
3373                 whiteFlag = blackFlag = FALSE;
3374                 continue;
3375             }
3376
3377             if (looking_at(buf, &i, "added * seconds to") ||
3378                 looking_at(buf, &i, "seconds were added to")) {
3379                 /* Update the clocks */
3380                 SendToICS(ics_prefix);
3381                 SendToICS("refresh\n");
3382                 continue;
3383             }
3384
3385             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3386                 ics_clock_paused = TRUE;
3387                 StopClocks();
3388                 continue;
3389             }
3390
3391             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3392                 ics_clock_paused = FALSE;
3393                 StartClocks();
3394                 continue;
3395             }
3396
3397             /* Grab player ratings from the Creating: message.
3398                Note we have to check for the special case when
3399                the ICS inserts things like [white] or [black]. */
3400             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3401                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3402                 /* star_matches:
3403                    0    player 1 name (not necessarily white)
3404                    1    player 1 rating
3405                    2    empty, white, or black (IGNORED)
3406                    3    player 2 name (not necessarily black)
3407                    4    player 2 rating
3408                    
3409                    The names/ratings are sorted out when the game
3410                    actually starts (below).
3411                 */
3412                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3413                 player1Rating = string_to_rating(star_match[1]);
3414                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3415                 player2Rating = string_to_rating(star_match[4]);
3416
3417                 if (appData.debugMode)
3418                   fprintf(debugFP, 
3419                           "Ratings from 'Creating:' %s %d, %s %d\n",
3420                           player1Name, player1Rating,
3421                           player2Name, player2Rating);
3422
3423                 continue;
3424             }
3425             
3426             /* Improved generic start/end-of-game messages */
3427             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3428                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3429                 /* If tkind == 0: */
3430                 /* star_match[0] is the game number */
3431                 /*           [1] is the white player's name */
3432                 /*           [2] is the black player's name */
3433                 /* For end-of-game: */
3434                 /*           [3] is the reason for the game end */
3435                 /*           [4] is a PGN end game-token, preceded by " " */
3436                 /* For start-of-game: */
3437                 /*           [3] begins with "Creating" or "Continuing" */
3438                 /*           [4] is " *" or empty (don't care). */
3439                 int gamenum = atoi(star_match[0]);
3440                 char *whitename, *blackname, *why, *endtoken;
3441                 ChessMove endtype = (ChessMove) 0;
3442
3443                 if (tkind == 0) {
3444                   whitename = star_match[1];
3445                   blackname = star_match[2];
3446                   why = star_match[3];
3447                   endtoken = star_match[4];
3448                 } else {
3449                   whitename = star_match[1];
3450                   blackname = star_match[3];
3451                   why = star_match[5];
3452                   endtoken = star_match[6];
3453                 }
3454
3455                 /* Game start messages */
3456                 if (strncmp(why, "Creating ", 9) == 0 ||
3457                     strncmp(why, "Continuing ", 11) == 0) {
3458                     gs_gamenum = gamenum;
3459                     strcpy(gs_kind, strchr(why, ' ') + 1);
3460                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3461 #if ZIPPY
3462                     if (appData.zippyPlay) {
3463                         ZippyGameStart(whitename, blackname);
3464                     }
3465 #endif /*ZIPPY*/
3466                     continue;
3467                 }
3468
3469                 /* Game end messages */
3470                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3471                     ics_gamenum != gamenum) {
3472                     continue;
3473                 }
3474                 while (endtoken[0] == ' ') endtoken++;
3475                 switch (endtoken[0]) {
3476                   case '*':
3477                   default:
3478                     endtype = GameUnfinished;
3479                     break;
3480                   case '0':
3481                     endtype = BlackWins;
3482                     break;
3483                   case '1':
3484                     if (endtoken[1] == '/')
3485                       endtype = GameIsDrawn;
3486                     else
3487                       endtype = WhiteWins;
3488                     break;
3489                 }
3490                 GameEnds(endtype, why, GE_ICS);
3491 #if ZIPPY
3492                 if (appData.zippyPlay && first.initDone) {
3493                     ZippyGameEnd(endtype, why);
3494                     if (first.pr == NULL) {
3495                       /* Start the next process early so that we'll
3496                          be ready for the next challenge */
3497                       StartChessProgram(&first);
3498                     }
3499                     /* Send "new" early, in case this command takes
3500                        a long time to finish, so that we'll be ready
3501                        for the next challenge. */
3502                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3503                     Reset(TRUE, TRUE);
3504                 }
3505 #endif /*ZIPPY*/
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i, "Removing game * from observation") ||
3510                 looking_at(buf, &i, "no longer observing game *") ||
3511                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3512                 if (gameMode == IcsObserving &&
3513                     atoi(star_match[0]) == ics_gamenum)
3514                   {
3515                       /* icsEngineAnalyze */
3516                       if (appData.icsEngineAnalyze) {
3517                             ExitAnalyzeMode();
3518                             ModeHighlight();
3519                       }
3520                       StopClocks();
3521                       gameMode = IcsIdle;
3522                       ics_gamenum = -1;
3523                       ics_user_moved = FALSE;
3524                   }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i, "no longer examining game *")) {
3529                 if (gameMode == IcsExamining &&
3530                     atoi(star_match[0]) == ics_gamenum)
3531                   {
3532                       gameMode = IcsIdle;
3533                       ics_gamenum = -1;
3534                       ics_user_moved = FALSE;
3535                   }
3536                 continue;
3537             }
3538
3539             /* Advance leftover_start past any newlines we find,
3540                so only partial lines can get reparsed */
3541             if (looking_at(buf, &i, "\n")) {
3542                 prevColor = curColor;
3543                 if (curColor != ColorNormal) {
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                         next_out = oldi;
3547                     }
3548                     Colorize(ColorNormal, FALSE);
3549                     curColor = ColorNormal;
3550                 }
3551                 if (started == STARTED_BOARD) {
3552                     started = STARTED_NONE;
3553                     parse[parse_pos] = NULLCHAR;
3554                     ParseBoard12(parse);
3555                     ics_user_moved = 0;
3556
3557                     /* Send premove here */
3558                     if (appData.premove) {
3559                       char str[MSG_SIZ];
3560                       if (currentMove == 0 &&
3561                           gameMode == IcsPlayingWhite &&
3562                           appData.premoveWhite) {
3563                         sprintf(str, "%s\n", appData.premoveWhiteText);
3564                         if (appData.debugMode)
3565                           fprintf(debugFP, "Sending premove:\n");
3566                         SendToICS(str);
3567                       } else if (currentMove == 1 &&
3568                                  gameMode == IcsPlayingBlack &&
3569                                  appData.premoveBlack) {
3570                         sprintf(str, "%s\n", appData.premoveBlackText);
3571                         if (appData.debugMode)
3572                           fprintf(debugFP, "Sending premove:\n");
3573                         SendToICS(str);
3574                       } else if (gotPremove) {
3575                         gotPremove = 0;
3576                         ClearPremoveHighlights();
3577                         if (appData.debugMode)
3578                           fprintf(debugFP, "Sending premove:\n");
3579                           UserMoveEvent(premoveFromX, premoveFromY, 
3580                                         premoveToX, premoveToY, 
3581                                         premovePromoChar);
3582                       }
3583                     }
3584
3585                     /* Usually suppress following prompt */
3586                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3587                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3588                         if (looking_at(buf, &i, "*% ")) {
3589                             savingComment = FALSE;
3590                             suppressKibitz = 0;
3591                         }
3592                     }
3593                     next_out = i;
3594                 } else if (started == STARTED_HOLDINGS) {
3595                     int gamenum;
3596                     char new_piece[MSG_SIZ];
3597                     started = STARTED_NONE;
3598                     parse[parse_pos] = NULLCHAR;
3599                     if (appData.debugMode)
3600                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3601                                                         parse, currentMove);
3602                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3603                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3604                         if (gameInfo.variant == VariantNormal) {
3605                           /* [HGM] We seem to switch variant during a game!
3606                            * Presumably no holdings were displayed, so we have
3607                            * to move the position two files to the right to
3608                            * create room for them!
3609                            */
3610                           VariantClass newVariant;
3611                           switch(gameInfo.boardWidth) { // base guess on board width
3612                                 case 9:  newVariant = VariantShogi; break;
3613                                 case 10: newVariant = VariantGreat; break;
3614                                 default: newVariant = VariantCrazyhouse; break;
3615                           }
3616                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3617                           /* Get a move list just to see the header, which
3618                              will tell us whether this is really bug or zh */
3619                           if (ics_getting_history == H_FALSE) {
3620                             ics_getting_history = H_REQUESTED;
3621                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3622                             SendToICS(str);
3623                           }
3624                         }
3625                         new_piece[0] = NULLCHAR;
3626                         sscanf(parse, "game %d white [%s black [%s <- %s",
3627                                &gamenum, white_holding, black_holding,
3628                                new_piece);
3629                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3630                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3631                         /* [HGM] copy holdings to board holdings area */
3632                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3633                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3634                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3635 #if ZIPPY
3636                         if (appData.zippyPlay && first.initDone) {
3637                             ZippyHoldings(white_holding, black_holding,
3638                                           new_piece);
3639                         }
3640 #endif /*ZIPPY*/
3641                         if (tinyLayout || smallLayout) {
3642                             char wh[16], bh[16];
3643                             PackHolding(wh, white_holding);
3644                             PackHolding(bh, black_holding);
3645                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3646                                     gameInfo.white, gameInfo.black);
3647                         } else {
3648                             sprintf(str, "%s [%s] vs. %s [%s]",
3649                                     gameInfo.white, white_holding,
3650                                     gameInfo.black, black_holding);
3651                         }
3652                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3653                         DrawPosition(FALSE, boards[currentMove]);
3654                         DisplayTitle(str);
3655                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3656                         sscanf(parse, "game %d white [%s black [%s <- %s",
3657                                &gamenum, white_holding, black_holding,
3658                                new_piece);
3659                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3660                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3661                         /* [HGM] copy holdings to partner-board holdings area */
3662                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3663                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3664                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3665                       }
3666                     }
3667                     /* Suppress following prompt */
3668                     if (looking_at(buf, &i, "*% ")) {
3669                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3670                         savingComment = FALSE;
3671                         suppressKibitz = 0;
3672                     }
3673                     next_out = i;
3674                 }
3675                 continue;
3676             }
3677
3678             i++;                /* skip unparsed character and loop back */
3679         }
3680         
3681         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3682 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3683 //          SendToPlayer(&buf[next_out], i - next_out);
3684             started != STARTED_HOLDINGS && leftover_start > next_out) {
3685             SendToPlayer(&buf[next_out], leftover_start - next_out);
3686             next_out = i;
3687         }
3688         
3689         leftover_len = buf_len - leftover_start;
3690         /* if buffer ends with something we couldn't parse,
3691            reparse it after appending the next read */
3692         
3693     } else if (count == 0) {
3694         RemoveInputSource(isr);
3695         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3696     } else {
3697         DisplayFatalError(_("Error reading from ICS"), error, 1);
3698     }
3699 }
3700
3701
3702 /* Board style 12 looks like this:
3703    
3704    <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
3705    
3706  * The "<12> " is stripped before it gets to this routine.  The two
3707  * trailing 0's (flip state and clock ticking) are later addition, and
3708  * some chess servers may not have them, or may have only the first.
3709  * Additional trailing fields may be added in the future.  
3710  */
3711
3712 #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"
3713
3714 #define RELATION_OBSERVING_PLAYED    0
3715 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3716 #define RELATION_PLAYING_MYMOVE      1
3717 #define RELATION_PLAYING_NOTMYMOVE  -1
3718 #define RELATION_EXAMINING           2
3719 #define RELATION_ISOLATED_BOARD     -3
3720 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3721
3722 void
3723 ParseBoard12(string)
3724      char *string;
3725
3726     GameMode newGameMode;
3727     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3728     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3729     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3730     char to_play, board_chars[200];
3731     char move_str[500], str[500], elapsed_time[500];
3732     char black[32], white[32];
3733     Board board;
3734     int prevMove = currentMove;
3735     int ticking = 2;
3736     ChessMove moveType;
3737     int fromX, fromY, toX, toY;
3738     char promoChar;
3739     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3740     char *bookHit = NULL; // [HGM] book
3741     Boolean weird = FALSE, reqFlag = FALSE;
3742
3743     fromX = fromY = toX = toY = -1;
3744     
3745     newGame = FALSE;
3746
3747     if (appData.debugMode)
3748       fprintf(debugFP, _("Parsing board: %s\n"), string);
3749
3750     move_str[0] = NULLCHAR;
3751     elapsed_time[0] = NULLCHAR;
3752     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3753         int  i = 0, j;
3754         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3755             if(string[i] == ' ') { ranks++; files = 0; }
3756             else files++;
3757             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3758             i++;
3759         }
3760         for(j = 0; j <i; j++) board_chars[j] = string[j];
3761         board_chars[i] = '\0';
3762         string += i + 1;
3763     }
3764     n = sscanf(string, PATTERN, &to_play, &double_push,
3765                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3766                &gamenum, white, black, &relation, &basetime, &increment,
3767                &white_stren, &black_stren, &white_time, &black_time,
3768                &moveNum, str, elapsed_time, move_str, &ics_flip,
3769                &ticking);
3770
3771     if (n < 21) {
3772         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3773         DisplayError(str, 0);
3774         return;
3775     }
3776
3777     /* Convert the move number to internal form */
3778     moveNum = (moveNum - 1) * 2;
3779     if (to_play == 'B') moveNum++;
3780     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3781       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3782                         0, 1);
3783       return;
3784     }
3785     
3786     switch (relation) {
3787       case RELATION_OBSERVING_PLAYED:
3788       case RELATION_OBSERVING_STATIC:
3789         if (gamenum == -1) {
3790             /* Old ICC buglet */
3791             relation = RELATION_OBSERVING_STATIC;
3792         }
3793         newGameMode = IcsObserving;
3794         break;
3795       case RELATION_PLAYING_MYMOVE:
3796       case RELATION_PLAYING_NOTMYMOVE:
3797         newGameMode =
3798           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3799             IcsPlayingWhite : IcsPlayingBlack;
3800         break;
3801       case RELATION_EXAMINING:
3802         newGameMode = IcsExamining;
3803         break;
3804       case RELATION_ISOLATED_BOARD:
3805       default:
3806         /* Just display this board.  If user was doing something else,
3807            we will forget about it until the next board comes. */ 
3808         newGameMode = IcsIdle;
3809         break;
3810       case RELATION_STARTING_POSITION:
3811         newGameMode = gameMode;
3812         break;
3813     }
3814     
3815     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3816          && newGameMode == IcsObserving && appData.bgObserve) {
3817       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3818       for (k = 0; k < ranks; k++) {
3819         for (j = 0; j < files; j++)
3820           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3821         if(gameInfo.holdingsWidth > 1) {
3822              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3823              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3824         }
3825       }
3826       CopyBoard(partnerBoard, board);
3827       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3828       sprintf(partnerStatus, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3829                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3830       DisplayMessage(partnerStatus, "");
3831       return;
3832     }
3833
3834     /* Modify behavior for initial board display on move listing
3835        of wild games.
3836        */
3837     switch (ics_getting_history) {
3838       case H_FALSE:
3839       case H_REQUESTED:
3840         break;
3841       case H_GOT_REQ_HEADER:
3842       case H_GOT_UNREQ_HEADER:
3843         /* This is the initial position of the current game */
3844         gamenum = ics_gamenum;
3845         moveNum = 0;            /* old ICS bug workaround */
3846         if (to_play == 'B') {
3847           startedFromSetupPosition = TRUE;
3848           blackPlaysFirst = TRUE;
3849           moveNum = 1;
3850           if (forwardMostMove == 0) forwardMostMove = 1;
3851           if (backwardMostMove == 0) backwardMostMove = 1;
3852           if (currentMove == 0) currentMove = 1;
3853         }
3854         newGameMode = gameMode;
3855         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3856         break;
3857       case H_GOT_UNWANTED_HEADER:
3858         /* This is an initial board that we don't want */
3859         return;
3860       case H_GETTING_MOVES:
3861         /* Should not happen */
3862         DisplayError(_("Error gathering move list: extra board"), 0);
3863         ics_getting_history = H_FALSE;
3864         return;
3865     }
3866
3867    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3868                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3869      /* [HGM] We seem to have switched variant unexpectedly
3870       * Try to guess new variant from board size
3871       */
3872           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3873           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3874           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3875           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3876           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3877           if(!weird) newVariant = VariantNormal;
3878           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3879           /* Get a move list just to see the header, which
3880              will tell us whether this is really bug or zh */
3881           if (ics_getting_history == H_FALSE) {
3882             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3883             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3884             SendToICS(str);
3885           }
3886     }
3887     
3888     /* Take action if this is the first board of a new game, or of a
3889        different game than is currently being displayed.  */
3890     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3891         relation == RELATION_ISOLATED_BOARD) {
3892         
3893         /* Forget the old game and get the history (if any) of the new one */
3894         if (gameMode != BeginningOfGame) {
3895           Reset(TRUE, TRUE);
3896         }
3897         newGame = TRUE;
3898         if (appData.autoRaiseBoard) BoardToTop();
3899         prevMove = -3;
3900         if (gamenum == -1) {
3901             newGameMode = IcsIdle;
3902         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3903                    appData.getMoveList && !reqFlag) {
3904             /* Need to get game history */
3905             ics_getting_history = H_REQUESTED;
3906             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3907             SendToICS(str);
3908         }
3909         
3910         /* Initially flip the board to have black on the bottom if playing
3911            black or if the ICS flip flag is set, but let the user change
3912            it with the Flip View button. */
3913         flipView = appData.autoFlipView ? 
3914           (newGameMode == IcsPlayingBlack) || ics_flip :
3915           appData.flipView;
3916         
3917         /* Done with values from previous mode; copy in new ones */
3918         gameMode = newGameMode;
3919         ModeHighlight();
3920         ics_gamenum = gamenum;
3921         if (gamenum == gs_gamenum) {
3922             int klen = strlen(gs_kind);
3923             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3924             sprintf(str, "ICS %s", gs_kind);
3925             gameInfo.event = StrSave(str);
3926         } else {
3927             gameInfo.event = StrSave("ICS game");
3928         }
3929         gameInfo.site = StrSave(appData.icsHost);
3930         gameInfo.date = PGNDate();
3931         gameInfo.round = StrSave("-");
3932         gameInfo.white = StrSave(white);
3933         gameInfo.black = StrSave(black);
3934         timeControl = basetime * 60 * 1000;
3935         timeControl_2 = 0;
3936         timeIncrement = increment * 1000;
3937         movesPerSession = 0;
3938         gameInfo.timeControl = TimeControlTagValue();
3939         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3940   if (appData.debugMode) {
3941     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3942     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3943     setbuf(debugFP, NULL);
3944   }
3945
3946         gameInfo.outOfBook = NULL;
3947         
3948         /* Do we have the ratings? */
3949         if (strcmp(player1Name, white) == 0 &&
3950             strcmp(player2Name, black) == 0) {
3951             if (appData.debugMode)
3952               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3953                       player1Rating, player2Rating);
3954             gameInfo.whiteRating = player1Rating;
3955             gameInfo.blackRating = player2Rating;
3956         } else if (strcmp(player2Name, white) == 0 &&
3957                    strcmp(player1Name, black) == 0) {
3958             if (appData.debugMode)
3959               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3960                       player2Rating, player1Rating);
3961             gameInfo.whiteRating = player2Rating;
3962             gameInfo.blackRating = player1Rating;
3963         }
3964         player1Name[0] = player2Name[0] = NULLCHAR;
3965
3966         /* Silence shouts if requested */
3967         if (appData.quietPlay &&
3968             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3969             SendToICS(ics_prefix);
3970             SendToICS("set shout 0\n");
3971         }
3972     }
3973     
3974     /* Deal with midgame name changes */
3975     if (!newGame) {
3976         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3977             if (gameInfo.white) free(gameInfo.white);
3978             gameInfo.white = StrSave(white);
3979         }
3980         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3981             if (gameInfo.black) free(gameInfo.black);
3982             gameInfo.black = StrSave(black);
3983         }
3984     }
3985     
3986     /* Throw away game result if anything actually changes in examine mode */
3987     if (gameMode == IcsExamining && !newGame) {
3988         gameInfo.result = GameUnfinished;
3989         if (gameInfo.resultDetails != NULL) {
3990             free(gameInfo.resultDetails);
3991             gameInfo.resultDetails = NULL;
3992         }
3993     }
3994     
3995     /* In pausing && IcsExamining mode, we ignore boards coming
3996        in if they are in a different variation than we are. */
3997     if (pauseExamInvalid) return;
3998     if (pausing && gameMode == IcsExamining) {
3999         if (moveNum <= pauseExamForwardMostMove) {
4000             pauseExamInvalid = TRUE;
4001             forwardMostMove = pauseExamForwardMostMove;
4002             return;
4003         }
4004     }
4005     
4006   if (appData.debugMode) {
4007     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4008   }
4009     /* Parse the board */
4010     for (k = 0; k < ranks; k++) {
4011       for (j = 0; j < files; j++)
4012         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4013       if(gameInfo.holdingsWidth > 1) {
4014            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4015            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4016       }
4017     }
4018     CopyBoard(boards[moveNum], board);
4019     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4020     if (moveNum == 0) {
4021         startedFromSetupPosition =
4022           !CompareBoards(board, initialPosition);
4023         if(startedFromSetupPosition)
4024             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4025     }
4026
4027     /* [HGM] Set castling rights. Take the outermost Rooks,
4028        to make it also work for FRC opening positions. Note that board12
4029        is really defective for later FRC positions, as it has no way to
4030        indicate which Rook can castle if they are on the same side of King.
4031        For the initial position we grant rights to the outermost Rooks,
4032        and remember thos rights, and we then copy them on positions
4033        later in an FRC game. This means WB might not recognize castlings with
4034        Rooks that have moved back to their original position as illegal,
4035        but in ICS mode that is not its job anyway.
4036     */
4037     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4038     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4039
4040         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4041             if(board[0][i] == WhiteRook) j = i;
4042         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4043         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4044             if(board[0][i] == WhiteRook) j = i;
4045         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4046         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4047             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4048         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4049         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4050             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4051         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4052
4053         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4054         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4055             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4056         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4057             if(board[BOARD_HEIGHT-1][k] == bKing)
4058                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4059         if(gameInfo.variant == VariantTwoKings) {
4060             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4061             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4062             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4063         }
4064     } else { int r;
4065         r = boards[moveNum][CASTLING][0] = initialRights[0];
4066         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4067         r = boards[moveNum][CASTLING][1] = initialRights[1];
4068         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4069         r = boards[moveNum][CASTLING][3] = initialRights[3];
4070         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4071         r = boards[moveNum][CASTLING][4] = initialRights[4];
4072         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4073         /* wildcastle kludge: always assume King has rights */
4074         r = boards[moveNum][CASTLING][2] = initialRights[2];
4075         r = boards[moveNum][CASTLING][5] = initialRights[5];
4076     }
4077     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4078     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4079
4080     
4081     if (ics_getting_history == H_GOT_REQ_HEADER ||
4082         ics_getting_history == H_GOT_UNREQ_HEADER) {
4083         /* This was an initial position from a move list, not
4084            the current position */
4085         return;
4086     }
4087     
4088     /* Update currentMove and known move number limits */
4089     newMove = newGame || moveNum > forwardMostMove;
4090
4091     if (newGame) {
4092         forwardMostMove = backwardMostMove = currentMove = moveNum;
4093         if (gameMode == IcsExamining && moveNum == 0) {
4094           /* Workaround for ICS limitation: we are not told the wild
4095              type when starting to examine a game.  But if we ask for
4096              the move list, the move list header will tell us */
4097             ics_getting_history = H_REQUESTED;
4098             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4099             SendToICS(str);
4100         }
4101     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4102                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4103 #if ZIPPY
4104         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4105         /* [HGM] applied this also to an engine that is silently watching        */
4106         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4107             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4108             gameInfo.variant == currentlyInitializedVariant) {
4109           takeback = forwardMostMove - moveNum;
4110           for (i = 0; i < takeback; i++) {
4111             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4112             SendToProgram("undo\n", &first);
4113           }
4114         }
4115 #endif
4116
4117         forwardMostMove = moveNum;
4118         if (!pausing || currentMove > forwardMostMove)
4119           currentMove = forwardMostMove;
4120     } else {
4121         /* New part of history that is not contiguous with old part */ 
4122         if (pausing && gameMode == IcsExamining) {
4123             pauseExamInvalid = TRUE;
4124             forwardMostMove = pauseExamForwardMostMove;
4125             return;
4126         }
4127         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4128 #if ZIPPY
4129             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4130                 // [HGM] when we will receive the move list we now request, it will be
4131                 // fed to the engine from the first move on. So if the engine is not
4132                 // in the initial position now, bring it there.
4133                 InitChessProgram(&first, 0);
4134             }
4135 #endif
4136             ics_getting_history = H_REQUESTED;
4137             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4138             SendToICS(str);
4139         }
4140         forwardMostMove = backwardMostMove = currentMove = moveNum;
4141     }
4142     
4143     /* Update the clocks */
4144     if (strchr(elapsed_time, '.')) {
4145       /* Time is in ms */
4146       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4147       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4148     } else {
4149       /* Time is in seconds */
4150       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4151       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4152     }
4153       
4154
4155 #if ZIPPY
4156     if (appData.zippyPlay && newGame &&
4157         gameMode != IcsObserving && gameMode != IcsIdle &&
4158         gameMode != IcsExamining)
4159       ZippyFirstBoard(moveNum, basetime, increment);
4160 #endif
4161     
4162     /* Put the move on the move list, first converting
4163        to canonical algebraic form. */
4164     if (moveNum > 0) {
4165   if (appData.debugMode) {
4166     if (appData.debugMode) { int f = forwardMostMove;
4167         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4168                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4169                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4170     }
4171     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4172     fprintf(debugFP, "moveNum = %d\n", moveNum);
4173     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4174     setbuf(debugFP, NULL);
4175   }
4176         if (moveNum <= backwardMostMove) {
4177             /* We don't know what the board looked like before
4178                this move.  Punt. */
4179             strcpy(parseList[moveNum - 1], move_str);
4180             strcat(parseList[moveNum - 1], " ");
4181             strcat(parseList[moveNum - 1], elapsed_time);
4182             moveList[moveNum - 1][0] = NULLCHAR;
4183         } else if (strcmp(move_str, "none") == 0) {
4184             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4185             /* Again, we don't know what the board looked like;
4186                this is really the start of the game. */
4187             parseList[moveNum - 1][0] = NULLCHAR;
4188             moveList[moveNum - 1][0] = NULLCHAR;
4189             backwardMostMove = moveNum;
4190             startedFromSetupPosition = TRUE;
4191             fromX = fromY = toX = toY = -1;
4192         } else {
4193           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4194           //                 So we parse the long-algebraic move string in stead of the SAN move
4195           int valid; char buf[MSG_SIZ], *prom;
4196
4197           // str looks something like "Q/a1-a2"; kill the slash
4198           if(str[1] == '/') 
4199                 sprintf(buf, "%c%s", str[0], str+2);
4200           else  strcpy(buf, str); // might be castling
4201           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4202                 strcat(buf, prom); // long move lacks promo specification!
4203           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4204                 if(appData.debugMode) 
4205                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4206                 strcpy(move_str, buf);
4207           }
4208           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4209                                 &fromX, &fromY, &toX, &toY, &promoChar)
4210                || ParseOneMove(buf, moveNum - 1, &moveType,
4211                                 &fromX, &fromY, &toX, &toY, &promoChar);
4212           // end of long SAN patch
4213           if (valid) {
4214             (void) CoordsToAlgebraic(boards[moveNum - 1],
4215                                      PosFlags(moveNum - 1),
4216                                      fromY, fromX, toY, toX, promoChar,
4217                                      parseList[moveNum-1]);
4218             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4219               case MT_NONE:
4220               case MT_STALEMATE:
4221               default:
4222                 break;
4223               case MT_CHECK:
4224                 if(gameInfo.variant != VariantShogi)
4225                     strcat(parseList[moveNum - 1], "+");
4226                 break;
4227               case MT_CHECKMATE:
4228               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4229                 strcat(parseList[moveNum - 1], "#");
4230                 break;
4231             }
4232             strcat(parseList[moveNum - 1], " ");
4233             strcat(parseList[moveNum - 1], elapsed_time);
4234             /* currentMoveString is set as a side-effect of ParseOneMove */
4235             strcpy(moveList[moveNum - 1], currentMoveString);
4236             strcat(moveList[moveNum - 1], "\n");
4237           } else {
4238             /* Move from ICS was illegal!?  Punt. */
4239   if (appData.debugMode) {
4240     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4241     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4242   }
4243             strcpy(parseList[moveNum - 1], move_str);
4244             strcat(parseList[moveNum - 1], " ");
4245             strcat(parseList[moveNum - 1], elapsed_time);
4246             moveList[moveNum - 1][0] = NULLCHAR;
4247             fromX = fromY = toX = toY = -1;
4248           }
4249         }
4250   if (appData.debugMode) {
4251     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4252     setbuf(debugFP, NULL);
4253   }
4254
4255 #if ZIPPY
4256         /* Send move to chess program (BEFORE animating it). */
4257         if (appData.zippyPlay && !newGame && newMove && 
4258            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4259
4260             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4261                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4262                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4263                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4264                             move_str);
4265                     DisplayError(str, 0);
4266                 } else {
4267                     if (first.sendTime) {
4268                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4269                     }
4270                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4271                     if (firstMove && !bookHit) {
4272                         firstMove = FALSE;
4273                         if (first.useColors) {
4274                           SendToProgram(gameMode == IcsPlayingWhite ?
4275                                         "white\ngo\n" :
4276                                         "black\ngo\n", &first);
4277                         } else {
4278                           SendToProgram("go\n", &first);
4279                         }
4280                         first.maybeThinking = TRUE;
4281                     }
4282                 }
4283             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4284               if (moveList[moveNum - 1][0] == NULLCHAR) {
4285                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4286                 DisplayError(str, 0);
4287               } else {
4288                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4289                 SendMoveToProgram(moveNum - 1, &first);
4290               }
4291             }
4292         }
4293 #endif
4294     }
4295
4296     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4297         /* If move comes from a remote source, animate it.  If it
4298            isn't remote, it will have already been animated. */
4299         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4300             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4301         }
4302         if (!pausing && appData.highlightLastMove) {
4303             SetHighlights(fromX, fromY, toX, toY);
4304         }
4305     }
4306     
4307     /* Start the clocks */
4308     whiteFlag = blackFlag = FALSE;
4309     appData.clockMode = !(basetime == 0 && increment == 0);
4310     if (ticking == 0) {
4311       ics_clock_paused = TRUE;
4312       StopClocks();
4313     } else if (ticking == 1) {
4314       ics_clock_paused = FALSE;
4315     }
4316     if (gameMode == IcsIdle ||
4317         relation == RELATION_OBSERVING_STATIC ||
4318         relation == RELATION_EXAMINING ||
4319         ics_clock_paused)
4320       DisplayBothClocks();
4321     else
4322       StartClocks();
4323     
4324     /* Display opponents and material strengths */
4325     if (gameInfo.variant != VariantBughouse &&
4326         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4327         if (tinyLayout || smallLayout) {
4328             if(gameInfo.variant == VariantNormal)
4329                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4330                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4331                     basetime, increment);
4332             else
4333                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4334                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4335                     basetime, increment, (int) gameInfo.variant);
4336         } else {
4337             if(gameInfo.variant == VariantNormal)
4338                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4339                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4340                     basetime, increment);
4341             else
4342                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment, VariantName(gameInfo.variant));
4345         }
4346         DisplayTitle(str);
4347   if (appData.debugMode) {
4348     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4349   }
4350     }
4351
4352
4353     /* Display the board */
4354     if (!pausing && !appData.noGUI) {
4355       
4356       if (appData.premove)
4357           if (!gotPremove || 
4358              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4359              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4360               ClearPremoveHighlights();
4361
4362       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4363         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4364       DrawPosition(j, boards[currentMove]);
4365
4366       DisplayMove(moveNum - 1);
4367       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4368             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4369               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4370         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4371       }
4372     }
4373
4374     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4375 #if ZIPPY
4376     if(bookHit) { // [HGM] book: simulate book reply
4377         static char bookMove[MSG_SIZ]; // a bit generous?
4378
4379         programStats.nodes = programStats.depth = programStats.time = 
4380         programStats.score = programStats.got_only_move = 0;
4381         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4382
4383         strcpy(bookMove, "move ");
4384         strcat(bookMove, bookHit);
4385         HandleMachineMove(bookMove, &first);
4386     }
4387 #endif
4388 }
4389
4390 void
4391 GetMoveListEvent()
4392 {
4393     char buf[MSG_SIZ];
4394     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4395         ics_getting_history = H_REQUESTED;
4396         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4397         SendToICS(buf);
4398     }
4399 }
4400
4401 void
4402 AnalysisPeriodicEvent(force)
4403      int force;
4404 {
4405     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4406          && !force) || !appData.periodicUpdates)
4407       return;
4408
4409     /* Send . command to Crafty to collect stats */
4410     SendToProgram(".\n", &first);
4411
4412     /* Don't send another until we get a response (this makes
4413        us stop sending to old Crafty's which don't understand
4414        the "." command (sending illegal cmds resets node count & time,
4415        which looks bad)) */
4416     programStats.ok_to_send = 0;
4417 }
4418
4419 void ics_update_width(new_width)
4420         int new_width;
4421 {
4422         ics_printf("set width %d\n", new_width);
4423 }
4424
4425 void
4426 SendMoveToProgram(moveNum, cps)
4427      int moveNum;
4428      ChessProgramState *cps;
4429 {
4430     char buf[MSG_SIZ];
4431
4432     if (cps->useUsermove) {
4433       SendToProgram("usermove ", cps);
4434     }
4435     if (cps->useSAN) {
4436       char *space;
4437       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4438         int len = space - parseList[moveNum];
4439         memcpy(buf, parseList[moveNum], len);
4440         buf[len++] = '\n';
4441         buf[len] = NULLCHAR;
4442       } else {
4443         sprintf(buf, "%s\n", parseList[moveNum]);
4444       }
4445       SendToProgram(buf, cps);
4446     } else {
4447       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4448         AlphaRank(moveList[moveNum], 4);
4449         SendToProgram(moveList[moveNum], cps);
4450         AlphaRank(moveList[moveNum], 4); // and back
4451       } else
4452       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4453        * the engine. It would be nice to have a better way to identify castle 
4454        * moves here. */
4455       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4456                                                                          && cps->useOOCastle) {
4457         int fromX = moveList[moveNum][0] - AAA; 
4458         int fromY = moveList[moveNum][1] - ONE;
4459         int toX = moveList[moveNum][2] - AAA; 
4460         int toY = moveList[moveNum][3] - ONE;
4461         if((boards[moveNum][fromY][fromX] == WhiteKing 
4462             && boards[moveNum][toY][toX] == WhiteRook)
4463            || (boards[moveNum][fromY][fromX] == BlackKing 
4464                && boards[moveNum][toY][toX] == BlackRook)) {
4465           if(toX > fromX) SendToProgram("O-O\n", cps);
4466           else SendToProgram("O-O-O\n", cps);
4467         }
4468         else SendToProgram(moveList[moveNum], cps);
4469       }
4470       else SendToProgram(moveList[moveNum], cps);
4471       /* End of additions by Tord */
4472     }
4473
4474     /* [HGM] setting up the opening has brought engine in force mode! */
4475     /*       Send 'go' if we are in a mode where machine should play. */
4476     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4477         (gameMode == TwoMachinesPlay   ||
4478 #ifdef ZIPPY
4479          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4480 #endif
4481          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4482         SendToProgram("go\n", cps);
4483   if (appData.debugMode) {
4484     fprintf(debugFP, "(extra)\n");
4485   }
4486     }
4487     setboardSpoiledMachineBlack = 0;
4488 }
4489
4490 void
4491 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4492      ChessMove moveType;
4493      int fromX, fromY, toX, toY;
4494 {
4495     char user_move[MSG_SIZ];
4496
4497     switch (moveType) {
4498       default:
4499         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4500                 (int)moveType, fromX, fromY, toX, toY);
4501         DisplayError(user_move + strlen("say "), 0);
4502         break;
4503       case WhiteKingSideCastle:
4504       case BlackKingSideCastle:
4505       case WhiteQueenSideCastleWild:
4506       case BlackQueenSideCastleWild:
4507       /* PUSH Fabien */
4508       case WhiteHSideCastleFR:
4509       case BlackHSideCastleFR:
4510       /* POP Fabien */
4511         sprintf(user_move, "o-o\n");
4512         break;
4513       case WhiteQueenSideCastle:
4514       case BlackQueenSideCastle:
4515       case WhiteKingSideCastleWild:
4516       case BlackKingSideCastleWild:
4517       /* PUSH Fabien */
4518       case WhiteASideCastleFR:
4519       case BlackASideCastleFR:
4520       /* POP Fabien */
4521         sprintf(user_move, "o-o-o\n");
4522         break;
4523       case WhitePromotionQueen:
4524       case BlackPromotionQueen:
4525       case WhitePromotionRook:
4526       case BlackPromotionRook:
4527       case WhitePromotionBishop:
4528       case BlackPromotionBishop:
4529       case WhitePromotionKnight:
4530       case BlackPromotionKnight:
4531       case WhitePromotionKing:
4532       case BlackPromotionKing:
4533       case WhitePromotionChancellor:
4534       case BlackPromotionChancellor:
4535       case WhitePromotionArchbishop:
4536       case BlackPromotionArchbishop:
4537         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4538             sprintf(user_move, "%c%c%c%c=%c\n",
4539                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4540                 PieceToChar(WhiteFerz));
4541         else if(gameInfo.variant == VariantGreat)
4542             sprintf(user_move, "%c%c%c%c=%c\n",
4543                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4544                 PieceToChar(WhiteMan));
4545         else
4546             sprintf(user_move, "%c%c%c%c=%c\n",
4547                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4548                 PieceToChar(PromoPiece(moveType)));
4549         break;
4550       case WhiteDrop:
4551       case BlackDrop:
4552         sprintf(user_move, "%c@%c%c\n",
4553                 ToUpper(PieceToChar((ChessSquare) fromX)),
4554                 AAA + toX, ONE + toY);
4555         break;
4556       case NormalMove:
4557       case WhiteCapturesEnPassant:
4558       case BlackCapturesEnPassant:
4559       case IllegalMove:  /* could be a variant we don't quite understand */
4560         sprintf(user_move, "%c%c%c%c\n",
4561                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4562         break;
4563     }
4564     SendToICS(user_move);
4565     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4566         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4567 }
4568
4569 void
4570 UploadGameEvent()
4571 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4572     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4573     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4574     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4575         DisplayError("You cannot do this while you are playing or observing", 0);
4576         return;
4577     }
4578     if(gameMode != IcsExamining) { // is this ever not the case?
4579         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4580
4581         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4582             sprintf(command, "match %s", ics_handle);
4583         } else { // on FICS we must first go to general examine mode
4584             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4585         }
4586         if(gameInfo.variant != VariantNormal) {
4587             // try figure out wild number, as xboard names are not always valid on ICS
4588             for(i=1; i<=36; i++) {
4589                 sprintf(buf, "wild/%d", i);
4590                 if(StringToVariant(buf) == gameInfo.variant) break;
4591             }
4592             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4593             else if(i == 22) sprintf(buf, "%s fr\n", command);
4594             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4595         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4596         SendToICS(ics_prefix);
4597         SendToICS(buf);
4598         if(startedFromSetupPosition || backwardMostMove != 0) {
4599           fen = PositionToFEN(backwardMostMove, NULL);
4600           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4601             sprintf(buf, "loadfen %s\n", fen);
4602             SendToICS(buf);
4603           } else { // FICS: everything has to set by separate bsetup commands
4604             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4605             sprintf(buf, "bsetup fen %s\n", fen);
4606             SendToICS(buf);
4607             if(!WhiteOnMove(backwardMostMove)) {
4608                 SendToICS("bsetup tomove black\n");
4609             }
4610             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4611             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4612             SendToICS(buf);
4613             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4614             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4615             SendToICS(buf);
4616             i = boards[backwardMostMove][EP_STATUS];
4617             if(i >= 0) { // set e.p.
4618                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4619                 SendToICS(buf);
4620             }
4621             bsetup++;
4622           }
4623         }
4624       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4625             SendToICS("bsetup done\n"); // switch to normal examining.
4626     }
4627     for(i = backwardMostMove; i<last; i++) {
4628         char buf[20];
4629         sprintf(buf, "%s\n", parseList[i]);
4630         SendToICS(buf);
4631     }
4632     SendToICS(ics_prefix);
4633     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4634 }
4635
4636 void
4637 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4638      int rf, ff, rt, ft;
4639      char promoChar;
4640      char move[7];
4641 {
4642     if (rf == DROP_RANK) {
4643         sprintf(move, "%c@%c%c\n",
4644                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4645     } else {
4646         if (promoChar == 'x' || promoChar == NULLCHAR) {
4647             sprintf(move, "%c%c%c%c\n",
4648                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4649         } else {
4650             sprintf(move, "%c%c%c%c%c\n",
4651                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4652         }
4653     }
4654 }
4655
4656 void
4657 ProcessICSInitScript(f)
4658      FILE *f;
4659 {
4660     char buf[MSG_SIZ];
4661
4662     while (fgets(buf, MSG_SIZ, f)) {
4663         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4664     }
4665
4666     fclose(f);
4667 }
4668
4669
4670 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4671 void
4672 AlphaRank(char *move, int n)
4673 {
4674 //    char *p = move, c; int x, y;
4675
4676     if (appData.debugMode) {
4677         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4678     }
4679
4680     if(move[1]=='*' && 
4681        move[2]>='0' && move[2]<='9' &&
4682        move[3]>='a' && move[3]<='x'    ) {
4683         move[1] = '@';
4684         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4685         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4686     } else
4687     if(move[0]>='0' && move[0]<='9' &&
4688        move[1]>='a' && move[1]<='x' &&
4689        move[2]>='0' && move[2]<='9' &&
4690        move[3]>='a' && move[3]<='x'    ) {
4691         /* input move, Shogi -> normal */
4692         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4693         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4694         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4695         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4696     } else
4697     if(move[1]=='@' &&
4698        move[3]>='0' && move[3]<='9' &&
4699        move[2]>='a' && move[2]<='x'    ) {
4700         move[1] = '*';
4701         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4702         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4703     } else
4704     if(
4705        move[0]>='a' && move[0]<='x' &&
4706        move[3]>='0' && move[3]<='9' &&
4707        move[2]>='a' && move[2]<='x'    ) {
4708          /* output move, normal -> Shogi */
4709         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4710         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4711         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4712         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4713         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4714     }
4715     if (appData.debugMode) {
4716         fprintf(debugFP, "   out = '%s'\n", move);
4717     }
4718 }
4719
4720 char yy_textstr[8000];
4721
4722 /* Parser for moves from gnuchess, ICS, or user typein box */
4723 Boolean
4724 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4725      char *move;
4726      int moveNum;
4727      ChessMove *moveType;
4728      int *fromX, *fromY, *toX, *toY;
4729      char *promoChar;
4730 {       
4731     if (appData.debugMode) {
4732         fprintf(debugFP, "move to parse: %s\n", move);
4733     }
4734     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4735
4736     switch (*moveType) {
4737       case WhitePromotionChancellor:
4738       case BlackPromotionChancellor:
4739       case WhitePromotionArchbishop:
4740       case BlackPromotionArchbishop:
4741       case WhitePromotionQueen:
4742       case BlackPromotionQueen:
4743       case WhitePromotionRook:
4744       case BlackPromotionRook:
4745       case WhitePromotionBishop:
4746       case BlackPromotionBishop:
4747       case WhitePromotionKnight:
4748       case BlackPromotionKnight:
4749       case WhitePromotionKing:
4750       case BlackPromotionKing:
4751       case NormalMove:
4752       case WhiteCapturesEnPassant:
4753       case BlackCapturesEnPassant:
4754       case WhiteKingSideCastle:
4755       case WhiteQueenSideCastle:
4756       case BlackKingSideCastle:
4757       case BlackQueenSideCastle:
4758       case WhiteKingSideCastleWild:
4759       case WhiteQueenSideCastleWild:
4760       case BlackKingSideCastleWild:
4761       case BlackQueenSideCastleWild:
4762       /* Code added by Tord: */
4763       case WhiteHSideCastleFR:
4764       case WhiteASideCastleFR:
4765       case BlackHSideCastleFR:
4766       case BlackASideCastleFR:
4767       /* End of code added by Tord */
4768       case IllegalMove:         /* bug or odd chess variant */
4769         *fromX = currentMoveString[0] - AAA;
4770         *fromY = currentMoveString[1] - ONE;
4771         *toX = currentMoveString[2] - AAA;
4772         *toY = currentMoveString[3] - ONE;
4773         *promoChar = currentMoveString[4];
4774         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4775             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4776     if (appData.debugMode) {
4777         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4778     }
4779             *fromX = *fromY = *toX = *toY = 0;
4780             return FALSE;
4781         }
4782         if (appData.testLegality) {
4783           return (*moveType != IllegalMove);
4784         } else {
4785           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4786                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4787         }
4788
4789       case WhiteDrop:
4790       case BlackDrop:
4791         *fromX = *moveType == WhiteDrop ?
4792           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4793           (int) CharToPiece(ToLower(currentMoveString[0]));
4794         *fromY = DROP_RANK;
4795         *toX = currentMoveString[2] - AAA;
4796         *toY = currentMoveString[3] - ONE;
4797         *promoChar = NULLCHAR;
4798         return TRUE;
4799
4800       case AmbiguousMove:
4801       case ImpossibleMove:
4802       case (ChessMove) 0:       /* end of file */
4803       case ElapsedTime:
4804       case Comment:
4805       case PGNTag:
4806       case NAG:
4807       case WhiteWins:
4808       case BlackWins:
4809       case GameIsDrawn:
4810       default:
4811     if (appData.debugMode) {
4812         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4813     }
4814         /* bug? */
4815         *fromX = *fromY = *toX = *toY = 0;
4816         *promoChar = NULLCHAR;
4817         return FALSE;
4818     }
4819 }
4820
4821
4822 void
4823 ParsePV(char *pv, Boolean storeComments)
4824 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4825   int fromX, fromY, toX, toY; char promoChar;
4826   ChessMove moveType;
4827   Boolean valid;
4828   int nr = 0;
4829
4830   endPV = forwardMostMove;
4831   do {
4832     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4833     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4834     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4835 if(appData.debugMode){
4836 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);
4837 }
4838     if(!valid && nr == 0 &&
4839        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4840         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4841         // Hande case where played move is different from leading PV move
4842         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4843         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4844         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4845         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4846           endPV += 2; // if position different, keep this
4847           moveList[endPV-1][0] = fromX + AAA;
4848           moveList[endPV-1][1] = fromY + ONE;
4849           moveList[endPV-1][2] = toX + AAA;
4850           moveList[endPV-1][3] = toY + ONE;
4851           parseList[endPV-1][0] = NULLCHAR;
4852           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4853         }
4854       }
4855     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4856     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4857     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4858     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4859         valid++; // allow comments in PV
4860         continue;
4861     }
4862     nr++;
4863     if(endPV+1 > framePtr) break; // no space, truncate
4864     if(!valid) break;
4865     endPV++;
4866     CopyBoard(boards[endPV], boards[endPV-1]);
4867     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4868     moveList[endPV-1][0] = fromX + AAA;
4869     moveList[endPV-1][1] = fromY + ONE;
4870     moveList[endPV-1][2] = toX + AAA;
4871     moveList[endPV-1][3] = toY + ONE;
4872     if(storeComments)
4873         CoordsToAlgebraic(boards[endPV - 1],
4874                              PosFlags(endPV - 1),
4875                              fromY, fromX, toY, toX, promoChar,
4876                              parseList[endPV - 1]);
4877     else
4878         parseList[endPV-1][0] = NULLCHAR;
4879   } while(valid);
4880   currentMove = endPV;
4881   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4882   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4883                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4884   DrawPosition(TRUE, boards[currentMove]);
4885 }
4886
4887 static int lastX, lastY;
4888
4889 Boolean
4890 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4891 {
4892         int startPV;
4893         char *p;
4894
4895         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4896         lastX = x; lastY = y;
4897         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4898         startPV = index;
4899         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4900         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4901         index = startPV;
4902         do{ while(buf[index] && buf[index] != '\n') index++;
4903         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4904         buf[index] = 0;
4905         ParsePV(buf+startPV, FALSE);
4906         *start = startPV; *end = index-1;
4907         return TRUE;
4908 }
4909
4910 Boolean
4911 LoadPV(int x, int y)
4912 { // called on right mouse click to load PV
4913   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4914   lastX = x; lastY = y;
4915   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4916   return TRUE;
4917 }
4918
4919 void
4920 UnLoadPV()
4921 {
4922   if(endPV < 0) return;
4923   endPV = -1;
4924   currentMove = forwardMostMove;
4925   ClearPremoveHighlights();
4926   DrawPosition(TRUE, boards[currentMove]);
4927 }
4928
4929 void
4930 MovePV(int x, int y, int h)
4931 { // step through PV based on mouse coordinates (called on mouse move)
4932   int margin = h>>3, step = 0;
4933
4934   if(endPV < 0) return;
4935   // we must somehow check if right button is still down (might be released off board!)
4936   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4937   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4938   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4939   if(!step) return;
4940   lastX = x; lastY = y;
4941   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4942   currentMove += step;
4943   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4944   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4945                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4946   DrawPosition(FALSE, boards[currentMove]);
4947 }
4948
4949
4950 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4951 // All positions will have equal probability, but the current method will not provide a unique
4952 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4953 #define DARK 1
4954 #define LITE 2
4955 #define ANY 3
4956
4957 int squaresLeft[4];
4958 int piecesLeft[(int)BlackPawn];
4959 int seed, nrOfShuffles;
4960
4961 void GetPositionNumber()
4962 {       // sets global variable seed
4963         int i;
4964
4965         seed = appData.defaultFrcPosition;
4966         if(seed < 0) { // randomize based on time for negative FRC position numbers
4967                 for(i=0; i<50; i++) seed += random();
4968                 seed = random() ^ random() >> 8 ^ random() << 8;
4969                 if(seed<0) seed = -seed;
4970         }
4971 }
4972
4973 int put(Board board, int pieceType, int rank, int n, int shade)
4974 // put the piece on the (n-1)-th empty squares of the given shade
4975 {
4976         int i;
4977
4978         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4979                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4980                         board[rank][i] = (ChessSquare) pieceType;
4981                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4982                         squaresLeft[ANY]--;
4983                         piecesLeft[pieceType]--; 
4984                         return i;
4985                 }
4986         }
4987         return -1;
4988 }
4989
4990
4991 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4992 // calculate where the next piece goes, (any empty square), and put it there
4993 {
4994         int i;
4995
4996         i = seed % squaresLeft[shade];
4997         nrOfShuffles *= squaresLeft[shade];
4998         seed /= squaresLeft[shade];
4999         put(board, pieceType, rank, i, shade);
5000 }
5001
5002 void AddTwoPieces(Board board, int pieceType, int rank)
5003 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5004 {
5005         int i, n=squaresLeft[ANY], j=n-1, k;
5006
5007         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5008         i = seed % k;  // pick one
5009         nrOfShuffles *= k;
5010         seed /= k;
5011         while(i >= j) i -= j--;
5012         j = n - 1 - j; i += j;
5013         put(board, pieceType, rank, j, ANY);
5014         put(board, pieceType, rank, i, ANY);
5015 }
5016
5017 void SetUpShuffle(Board board, int number)
5018 {
5019         int i, p, first=1;
5020
5021         GetPositionNumber(); nrOfShuffles = 1;
5022
5023         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5024         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5025         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5026
5027         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5028
5029         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5030             p = (int) board[0][i];
5031             if(p < (int) BlackPawn) piecesLeft[p] ++;
5032             board[0][i] = EmptySquare;
5033         }
5034
5035         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5036             // shuffles restricted to allow normal castling put KRR first
5037             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5038                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5039             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5040                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5041             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5042                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5043             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5044                 put(board, WhiteRook, 0, 0, ANY);
5045             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5046         }
5047
5048         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5049             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5050             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5051                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5052                 while(piecesLeft[p] >= 2) {
5053                     AddOnePiece(board, p, 0, LITE);
5054                     AddOnePiece(board, p, 0, DARK);
5055                 }
5056                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5057             }
5058
5059         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5060             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5061             // but we leave King and Rooks for last, to possibly obey FRC restriction
5062             if(p == (int)WhiteRook) continue;
5063             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5064             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5065         }
5066
5067         // now everything is placed, except perhaps King (Unicorn) and Rooks
5068
5069         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5070             // Last King gets castling rights
5071             while(piecesLeft[(int)WhiteUnicorn]) {
5072                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5073                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5074             }
5075
5076             while(piecesLeft[(int)WhiteKing]) {
5077                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5078                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5079             }
5080
5081
5082         } else {
5083             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5084             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5085         }
5086
5087         // Only Rooks can be left; simply place them all
5088         while(piecesLeft[(int)WhiteRook]) {
5089                 i = put(board, WhiteRook, 0, 0, ANY);
5090                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5091                         if(first) {
5092                                 first=0;
5093                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5094                         }
5095                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5096                 }
5097         }
5098         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5099             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5100         }
5101
5102         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5103 }
5104
5105 int SetCharTable( char *table, const char * map )
5106 /* [HGM] moved here from winboard.c because of its general usefulness */
5107 /*       Basically a safe strcpy that uses the last character as King */
5108 {
5109     int result = FALSE; int NrPieces;
5110
5111     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5112                     && NrPieces >= 12 && !(NrPieces&1)) {
5113         int i; /* [HGM] Accept even length from 12 to 34 */
5114
5115         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5116         for( i=0; i<NrPieces/2-1; i++ ) {
5117             table[i] = map[i];
5118             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5119         }
5120         table[(int) WhiteKing]  = map[NrPieces/2-1];
5121         table[(int) BlackKing]  = map[NrPieces-1];
5122
5123         result = TRUE;
5124     }
5125
5126     return result;
5127 }
5128
5129 void Prelude(Board board)
5130 {       // [HGM] superchess: random selection of exo-pieces
5131         int i, j, k; ChessSquare p; 
5132         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5133
5134         GetPositionNumber(); // use FRC position number
5135
5136         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5137             SetCharTable(pieceToChar, appData.pieceToCharTable);
5138             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5139                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5140         }
5141
5142         j = seed%4;                 seed /= 4; 
5143         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5144         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5145         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5146         j = seed%3 + (seed%3 >= j); seed /= 3; 
5147         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5148         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5149         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5150         j = seed%3;                 seed /= 3; 
5151         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5152         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5153         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5154         j = seed%2 + (seed%2 >= j); seed /= 2; 
5155         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5156         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5157         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5158         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5159         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5160         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5161         put(board, exoPieces[0],    0, 0, ANY);
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5163 }
5164
5165 void
5166 InitPosition(redraw)
5167      int redraw;
5168 {
5169     ChessSquare (* pieces)[BOARD_FILES];
5170     int i, j, pawnRow, overrule,
5171     oldx = gameInfo.boardWidth,
5172     oldy = gameInfo.boardHeight,
5173     oldh = gameInfo.holdingsWidth,
5174     oldv = gameInfo.variant;
5175
5176     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5177
5178     /* [AS] Initialize pv info list [HGM] and game status */
5179     {
5180         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5181             pvInfoList[i].depth = 0;
5182             boards[i][EP_STATUS] = EP_NONE;
5183             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5184         }
5185
5186         initialRulePlies = 0; /* 50-move counter start */
5187
5188         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5189         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5190     }
5191
5192     
5193     /* [HGM] logic here is completely changed. In stead of full positions */
5194     /* the initialized data only consist of the two backranks. The switch */
5195     /* selects which one we will use, which is than copied to the Board   */
5196     /* initialPosition, which for the rest is initialized by Pawns and    */
5197     /* empty squares. This initial position is then copied to boards[0],  */
5198     /* possibly after shuffling, so that it remains available.            */
5199
5200     gameInfo.holdingsWidth = 0; /* default board sizes */
5201     gameInfo.boardWidth    = 8;
5202     gameInfo.boardHeight   = 8;
5203     gameInfo.holdingsSize  = 0;
5204     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5205     for(i=0; i<BOARD_FILES-2; i++)
5206       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5207     initialPosition[EP_STATUS] = EP_NONE;
5208     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5209
5210     switch (gameInfo.variant) {
5211     case VariantFischeRandom:
5212       shuffleOpenings = TRUE;
5213     default:
5214       pieces = FIDEArray;
5215       break;
5216     case VariantShatranj:
5217       pieces = ShatranjArray;
5218       nrCastlingRights = 0;
5219       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5220       break;
5221     case VariantMakruk:
5222       pieces = makrukArray;
5223       nrCastlingRights = 0;
5224       startedFromSetupPosition = TRUE;
5225       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5226       break;
5227     case VariantTwoKings:
5228       pieces = twoKingsArray;
5229       break;
5230     case VariantCapaRandom:
5231       shuffleOpenings = TRUE;
5232     case VariantCapablanca:
5233       pieces = CapablancaArray;
5234       gameInfo.boardWidth = 10;
5235       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5236       break;
5237     case VariantGothic:
5238       pieces = GothicArray;
5239       gameInfo.boardWidth = 10;
5240       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5241       break;
5242     case VariantJanus:
5243       pieces = JanusArray;
5244       gameInfo.boardWidth = 10;
5245       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5246       nrCastlingRights = 6;
5247         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5248         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5249         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5250         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5251         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5252         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5253       break;
5254     case VariantFalcon:
5255       pieces = FalconArray;
5256       gameInfo.boardWidth = 10;
5257       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5258       break;
5259     case VariantXiangqi:
5260       pieces = XiangqiArray;
5261       gameInfo.boardWidth  = 9;
5262       gameInfo.boardHeight = 10;
5263       nrCastlingRights = 0;
5264       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5265       break;
5266     case VariantShogi:
5267       pieces = ShogiArray;
5268       gameInfo.boardWidth  = 9;
5269       gameInfo.boardHeight = 9;
5270       gameInfo.holdingsSize = 7;
5271       nrCastlingRights = 0;
5272       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5273       break;
5274     case VariantCourier:
5275       pieces = CourierArray;
5276       gameInfo.boardWidth  = 12;
5277       nrCastlingRights = 0;
5278       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5279       break;
5280     case VariantKnightmate:
5281       pieces = KnightmateArray;
5282       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5283       break;
5284     case VariantFairy:
5285       pieces = fairyArray;
5286       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5287       break;
5288     case VariantGreat:
5289       pieces = GreatArray;
5290       gameInfo.boardWidth = 10;
5291       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5292       gameInfo.holdingsSize = 8;
5293       break;
5294     case VariantSuper:
5295       pieces = FIDEArray;
5296       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5297       gameInfo.holdingsSize = 8;
5298       startedFromSetupPosition = TRUE;
5299       break;
5300     case VariantCrazyhouse:
5301     case VariantBughouse:
5302       pieces = FIDEArray;
5303       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5304       gameInfo.holdingsSize = 5;
5305       break;
5306     case VariantWildCastle:
5307       pieces = FIDEArray;
5308       /* !!?shuffle with kings guaranteed to be on d or e file */
5309       shuffleOpenings = 1;
5310       break;
5311     case VariantNoCastle:
5312       pieces = FIDEArray;
5313       nrCastlingRights = 0;
5314       /* !!?unconstrained back-rank shuffle */
5315       shuffleOpenings = 1;
5316       break;
5317     }
5318
5319     overrule = 0;
5320     if(appData.NrFiles >= 0) {
5321         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5322         gameInfo.boardWidth = appData.NrFiles;
5323     }
5324     if(appData.NrRanks >= 0) {
5325         gameInfo.boardHeight = appData.NrRanks;
5326     }
5327     if(appData.holdingsSize >= 0) {
5328         i = appData.holdingsSize;
5329         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5330         gameInfo.holdingsSize = i;
5331     }
5332     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5333     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5334         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5335
5336     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5337     if(pawnRow < 1) pawnRow = 1;
5338     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5339
5340     /* User pieceToChar list overrules defaults */
5341     if(appData.pieceToCharTable != NULL)
5342         SetCharTable(pieceToChar, appData.pieceToCharTable);
5343
5344     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5345
5346         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5347             s = (ChessSquare) 0; /* account holding counts in guard band */
5348         for( i=0; i<BOARD_HEIGHT; i++ )
5349             initialPosition[i][j] = s;
5350
5351         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5352         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5353         initialPosition[pawnRow][j] = WhitePawn;
5354         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5355         if(gameInfo.variant == VariantXiangqi) {
5356             if(j&1) {
5357                 initialPosition[pawnRow][j] = 
5358                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5359                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5360                    initialPosition[2][j] = WhiteCannon;
5361                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5362                 }
5363             }
5364         }
5365         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5366     }
5367     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5368
5369             j=BOARD_LEFT+1;
5370             initialPosition[1][j] = WhiteBishop;
5371             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5372             j=BOARD_RGHT-2;
5373             initialPosition[1][j] = WhiteRook;
5374             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5375     }
5376
5377     if( nrCastlingRights == -1) {
5378         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5379         /*       This sets default castling rights from none to normal corners   */
5380         /* Variants with other castling rights must set them themselves above    */
5381         nrCastlingRights = 6;
5382        
5383         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5384         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5385         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5386         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5387         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5388         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5389      }
5390
5391      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5392      if(gameInfo.variant == VariantGreat) { // promotion commoners
5393         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5394         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5395         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5396         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5397      }
5398   if (appData.debugMode) {
5399     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5400   }
5401     if(shuffleOpenings) {
5402         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5403         startedFromSetupPosition = TRUE;
5404     }
5405     if(startedFromPositionFile) {
5406       /* [HGM] loadPos: use PositionFile for every new game */
5407       CopyBoard(initialPosition, filePosition);
5408       for(i=0; i<nrCastlingRights; i++)
5409           initialRights[i] = filePosition[CASTLING][i];
5410       startedFromSetupPosition = TRUE;
5411     }
5412
5413     CopyBoard(boards[0], initialPosition);
5414
5415     if(oldx != gameInfo.boardWidth ||
5416        oldy != gameInfo.boardHeight ||
5417        oldh != gameInfo.holdingsWidth
5418 #ifdef GOTHIC
5419        || oldv == VariantGothic ||        // For licensing popups
5420        gameInfo.variant == VariantGothic
5421 #endif
5422 #ifdef FALCON
5423        || oldv == VariantFalcon ||
5424        gameInfo.variant == VariantFalcon
5425 #endif
5426                                          )
5427             InitDrawingSizes(-2 ,0);
5428
5429     if (redraw)
5430       DrawPosition(TRUE, boards[currentMove]);
5431 }
5432
5433 void
5434 SendBoard(cps, moveNum)
5435      ChessProgramState *cps;
5436      int moveNum;
5437 {
5438     char message[MSG_SIZ];
5439     
5440     if (cps->useSetboard) {
5441       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5442       sprintf(message, "setboard %s\n", fen);
5443       SendToProgram(message, cps);
5444       free(fen);
5445
5446     } else {
5447       ChessSquare *bp;
5448       int i, j;
5449       /* Kludge to set black to move, avoiding the troublesome and now
5450        * deprecated "black" command.
5451        */
5452       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5453
5454       SendToProgram("edit\n", cps);
5455       SendToProgram("#\n", cps);
5456       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5457         bp = &boards[moveNum][i][BOARD_LEFT];
5458         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5459           if ((int) *bp < (int) BlackPawn) {
5460             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5461                     AAA + j, ONE + i);
5462             if(message[0] == '+' || message[0] == '~') {
5463                 sprintf(message, "%c%c%c+\n",
5464                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5465                         AAA + j, ONE + i);
5466             }
5467             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5468                 message[1] = BOARD_RGHT   - 1 - j + '1';
5469                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5470             }
5471             SendToProgram(message, cps);
5472           }
5473         }
5474       }
5475     
5476       SendToProgram("c\n", cps);
5477       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5478         bp = &boards[moveNum][i][BOARD_LEFT];
5479         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5480           if (((int) *bp != (int) EmptySquare)
5481               && ((int) *bp >= (int) BlackPawn)) {
5482             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5483                     AAA + j, ONE + i);
5484             if(message[0] == '+' || message[0] == '~') {
5485                 sprintf(message, "%c%c%c+\n",
5486                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5487                         AAA + j, ONE + i);
5488             }
5489             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5490                 message[1] = BOARD_RGHT   - 1 - j + '1';
5491                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5492             }
5493             SendToProgram(message, cps);
5494           }
5495         }
5496       }
5497     
5498       SendToProgram(".\n", cps);
5499     }
5500     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5501 }
5502
5503 static int autoQueen; // [HGM] oneclick
5504
5505 int
5506 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5507 {
5508     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5509     /* [HGM] add Shogi promotions */
5510     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5511     ChessSquare piece;
5512     ChessMove moveType;
5513     Boolean premove;
5514
5515     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5516     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5517
5518     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5519       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5520         return FALSE;
5521
5522     piece = boards[currentMove][fromY][fromX];
5523     if(gameInfo.variant == VariantShogi) {
5524         promotionZoneSize = 3;
5525         highestPromotingPiece = (int)WhiteFerz;
5526     } else if(gameInfo.variant == VariantMakruk) {
5527         promotionZoneSize = 3;
5528     }
5529
5530     // next weed out all moves that do not touch the promotion zone at all
5531     if((int)piece >= BlackPawn) {
5532         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5533              return FALSE;
5534         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5535     } else {
5536         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5537            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5538     }
5539
5540     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5541
5542     // weed out mandatory Shogi promotions
5543     if(gameInfo.variant == VariantShogi) {
5544         if(piece >= BlackPawn) {
5545             if(toY == 0 && piece == BlackPawn ||
5546                toY == 0 && piece == BlackQueen ||
5547                toY <= 1 && piece == BlackKnight) {
5548                 *promoChoice = '+';
5549                 return FALSE;
5550             }
5551         } else {
5552             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5553                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5554                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5555                 *promoChoice = '+';
5556                 return FALSE;
5557             }
5558         }
5559     }
5560
5561     // weed out obviously illegal Pawn moves
5562     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5563         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5564         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5565         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5566         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5567         // note we are not allowed to test for valid (non-)capture, due to premove
5568     }
5569
5570     // we either have a choice what to promote to, or (in Shogi) whether to promote
5571     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5572         *promoChoice = PieceToChar(BlackFerz);  // no choice
5573         return FALSE;
5574     }
5575     if(autoQueen) { // predetermined
5576         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5577              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5578         else *promoChoice = PieceToChar(BlackQueen);
5579         return FALSE;
5580     }
5581
5582     // suppress promotion popup on illegal moves that are not premoves
5583     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5584               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5585     if(appData.testLegality && !premove) {
5586         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5587                         fromY, fromX, toY, toX, NULLCHAR);
5588         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5589            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5590             return FALSE;
5591     }
5592
5593     return TRUE;
5594 }
5595
5596 int
5597 InPalace(row, column)
5598      int row, column;
5599 {   /* [HGM] for Xiangqi */
5600     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5601          column < (BOARD_WIDTH + 4)/2 &&
5602          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5603     return FALSE;
5604 }
5605
5606 int
5607 PieceForSquare (x, y)
5608      int x;
5609      int y;
5610 {
5611   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5612      return -1;
5613   else
5614      return boards[currentMove][y][x];
5615 }
5616
5617 int
5618 OKToStartUserMove(x, y)
5619      int x, y;
5620 {
5621     ChessSquare from_piece;
5622     int white_piece;
5623
5624     if (matchMode) return FALSE;
5625     if (gameMode == EditPosition) return TRUE;
5626
5627     if (x >= 0 && y >= 0)
5628       from_piece = boards[currentMove][y][x];
5629     else
5630       from_piece = EmptySquare;
5631
5632     if (from_piece == EmptySquare) return FALSE;
5633
5634     white_piece = (int)from_piece >= (int)WhitePawn &&
5635       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5636
5637     switch (gameMode) {
5638       case PlayFromGameFile:
5639       case AnalyzeFile:
5640       case TwoMachinesPlay:
5641       case EndOfGame:
5642         return FALSE;
5643
5644       case IcsObserving:
5645       case IcsIdle:
5646         return FALSE;
5647
5648       case MachinePlaysWhite:
5649       case IcsPlayingBlack:
5650         if (appData.zippyPlay) return FALSE;
5651         if (white_piece) {
5652             DisplayMoveError(_("You are playing Black"));
5653             return FALSE;
5654         }
5655         break;
5656
5657       case MachinePlaysBlack:
5658       case IcsPlayingWhite:
5659         if (appData.zippyPlay) return FALSE;
5660         if (!white_piece) {
5661             DisplayMoveError(_("You are playing White"));
5662             return FALSE;
5663         }
5664         break;
5665
5666       case EditGame:
5667         if (!white_piece && WhiteOnMove(currentMove)) {
5668             DisplayMoveError(_("It is White's turn"));
5669             return FALSE;
5670         }           
5671         if (white_piece && !WhiteOnMove(currentMove)) {
5672             DisplayMoveError(_("It is Black's turn"));
5673             return FALSE;
5674         }           
5675         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5676             /* Editing correspondence game history */
5677             /* Could disallow this or prompt for confirmation */
5678             cmailOldMove = -1;
5679         }
5680         break;
5681
5682       case BeginningOfGame:
5683         if (appData.icsActive) return FALSE;
5684         if (!appData.noChessProgram) {
5685             if (!white_piece) {
5686                 DisplayMoveError(_("You are playing White"));
5687                 return FALSE;
5688             }
5689         }
5690         break;
5691         
5692       case Training:
5693         if (!white_piece && WhiteOnMove(currentMove)) {
5694             DisplayMoveError(_("It is White's turn"));
5695             return FALSE;
5696         }           
5697         if (white_piece && !WhiteOnMove(currentMove)) {
5698             DisplayMoveError(_("It is Black's turn"));
5699             return FALSE;
5700         }           
5701         break;
5702
5703       default:
5704       case IcsExamining:
5705         break;
5706     }
5707     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5708         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5709         && gameMode != AnalyzeFile && gameMode != Training) {
5710         DisplayMoveError(_("Displayed position is not current"));
5711         return FALSE;
5712     }
5713     return TRUE;
5714 }
5715
5716 Boolean
5717 OnlyMove(int *x, int *y, Boolean captures) {
5718     DisambiguateClosure cl;
5719     if (appData.zippyPlay) return FALSE;
5720     switch(gameMode) {
5721       case MachinePlaysBlack:
5722       case IcsPlayingWhite:
5723       case BeginningOfGame:
5724         if(!WhiteOnMove(currentMove)) return FALSE;
5725         break;
5726       case MachinePlaysWhite:
5727       case IcsPlayingBlack:
5728         if(WhiteOnMove(currentMove)) return FALSE;
5729         break;
5730       default:
5731         return FALSE;
5732     }
5733     cl.pieceIn = EmptySquare; 
5734     cl.rfIn = *y;
5735     cl.ffIn = *x;
5736     cl.rtIn = -1;
5737     cl.ftIn = -1;
5738     cl.promoCharIn = NULLCHAR;
5739     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5740     if( cl.kind == NormalMove ||
5741         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5742         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5743         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5744         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5745       fromX = cl.ff;
5746       fromY = cl.rf;
5747       *x = cl.ft;
5748       *y = cl.rt;
5749       return TRUE;
5750     }
5751     if(cl.kind != ImpossibleMove) return FALSE;
5752     cl.pieceIn = EmptySquare;
5753     cl.rfIn = -1;
5754     cl.ffIn = -1;
5755     cl.rtIn = *y;
5756     cl.ftIn = *x;
5757     cl.promoCharIn = NULLCHAR;
5758     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5759     if( cl.kind == NormalMove ||
5760         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5761         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5762         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5763         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5764       fromX = cl.ff;
5765       fromY = cl.rf;
5766       *x = cl.ft;
5767       *y = cl.rt;
5768       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5769       return TRUE;
5770     }
5771     return FALSE;
5772 }
5773
5774 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5775 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5776 int lastLoadGameUseList = FALSE;
5777 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5778 ChessMove lastLoadGameStart = (ChessMove) 0;
5779
5780 ChessMove
5781 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5782      int fromX, fromY, toX, toY;
5783      int promoChar;
5784      Boolean captureOwn;
5785 {
5786     ChessMove moveType;
5787     ChessSquare pdown, pup;
5788
5789     /* Check if the user is playing in turn.  This is complicated because we
5790        let the user "pick up" a piece before it is his turn.  So the piece he
5791        tried to pick up may have been captured by the time he puts it down!
5792        Therefore we use the color the user is supposed to be playing in this
5793        test, not the color of the piece that is currently on the starting
5794        square---except in EditGame mode, where the user is playing both
5795        sides; fortunately there the capture race can't happen.  (It can
5796        now happen in IcsExamining mode, but that's just too bad.  The user
5797        will get a somewhat confusing message in that case.)
5798        */
5799
5800     switch (gameMode) {
5801       case PlayFromGameFile:
5802       case AnalyzeFile:
5803       case TwoMachinesPlay:
5804       case EndOfGame:
5805       case IcsObserving:
5806       case IcsIdle:
5807         /* We switched into a game mode where moves are not accepted,
5808            perhaps while the mouse button was down. */
5809         return ImpossibleMove;
5810
5811       case MachinePlaysWhite:
5812         /* User is moving for Black */
5813         if (WhiteOnMove(currentMove)) {
5814             DisplayMoveError(_("It is White's turn"));
5815             return ImpossibleMove;
5816         }
5817         break;
5818
5819       case MachinePlaysBlack:
5820         /* User is moving for White */
5821         if (!WhiteOnMove(currentMove)) {
5822             DisplayMoveError(_("It is Black's turn"));
5823             return ImpossibleMove;
5824         }
5825         break;
5826
5827       case EditGame:
5828       case IcsExamining:
5829       case BeginningOfGame:
5830       case AnalyzeMode:
5831       case Training:
5832         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5833             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5834             /* User is moving for Black */
5835             if (WhiteOnMove(currentMove)) {
5836                 DisplayMoveError(_("It is White's turn"));
5837                 return ImpossibleMove;
5838             }
5839         } else {
5840             /* User is moving for White */
5841             if (!WhiteOnMove(currentMove)) {
5842                 DisplayMoveError(_("It is Black's turn"));
5843                 return ImpossibleMove;
5844             }
5845         }
5846         break;
5847
5848       case IcsPlayingBlack:
5849         /* User is moving for Black */
5850         if (WhiteOnMove(currentMove)) {
5851             if (!appData.premove) {
5852                 DisplayMoveError(_("It is White's turn"));
5853             } else if (toX >= 0 && toY >= 0) {
5854                 premoveToX = toX;
5855                 premoveToY = toY;
5856                 premoveFromX = fromX;
5857                 premoveFromY = fromY;
5858                 premovePromoChar = promoChar;
5859                 gotPremove = 1;
5860                 if (appData.debugMode) 
5861                     fprintf(debugFP, "Got premove: fromX %d,"
5862                             "fromY %d, toX %d, toY %d\n",
5863                             fromX, fromY, toX, toY);
5864             }
5865             return ImpossibleMove;
5866         }
5867         break;
5868
5869       case IcsPlayingWhite:
5870         /* User is moving for White */
5871         if (!WhiteOnMove(currentMove)) {
5872             if (!appData.premove) {
5873                 DisplayMoveError(_("It is Black's turn"));
5874             } else if (toX >= 0 && toY >= 0) {
5875                 premoveToX = toX;
5876                 premoveToY = toY;
5877                 premoveFromX = fromX;
5878                 premoveFromY = fromY;
5879                 premovePromoChar = promoChar;
5880                 gotPremove = 1;
5881                 if (appData.debugMode) 
5882                     fprintf(debugFP, "Got premove: fromX %d,"
5883                             "fromY %d, toX %d, toY %d\n",
5884                             fromX, fromY, toX, toY);
5885             }
5886             return ImpossibleMove;
5887         }
5888         break;
5889
5890       default:
5891         break;
5892
5893       case EditPosition:
5894         /* EditPosition, empty square, or different color piece;
5895            click-click move is possible */
5896         if (toX == -2 || toY == -2) {
5897             boards[0][fromY][fromX] = EmptySquare;
5898             return AmbiguousMove;
5899         } else if (toX >= 0 && toY >= 0) {
5900             boards[0][toY][toX] = boards[0][fromY][fromX];
5901             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5902                 if(boards[0][fromY][0] != EmptySquare) {
5903                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5904                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5905                 }
5906             } else
5907             if(fromX == BOARD_RGHT+1) {
5908                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5909                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5910                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5911                 }
5912             } else
5913             boards[0][fromY][fromX] = EmptySquare;
5914             return AmbiguousMove;
5915         }
5916         return ImpossibleMove;
5917     }
5918
5919     if(toX < 0 || toY < 0) return ImpossibleMove;
5920     pdown = boards[currentMove][fromY][fromX];
5921     pup = boards[currentMove][toY][toX];
5922
5923     /* [HGM] If move started in holdings, it means a drop */
5924     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5925          if( pup != EmptySquare ) return ImpossibleMove;
5926          if(appData.testLegality) {
5927              /* it would be more logical if LegalityTest() also figured out
5928               * which drops are legal. For now we forbid pawns on back rank.
5929               * Shogi is on its own here...
5930               */
5931              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5932                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5933                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5934          }
5935          return WhiteDrop; /* Not needed to specify white or black yet */
5936     }
5937
5938     /* [HGM] always test for legality, to get promotion info */
5939     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5940                                          fromY, fromX, toY, toX, promoChar);
5941     /* [HGM] but possibly ignore an IllegalMove result */
5942     if (appData.testLegality) {
5943         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5944             DisplayMoveError(_("Illegal move"));
5945             return ImpossibleMove;
5946         }
5947     }
5948
5949     return moveType;
5950     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5951        function is made into one that returns an OK move type if FinishMove
5952        should be called. This to give the calling driver routine the
5953        opportunity to finish the userMove input with a promotion popup,
5954        without bothering the user with this for invalid or illegal moves */
5955
5956 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5957 }
5958
5959 /* Common tail of UserMoveEvent and DropMenuEvent */
5960 int
5961 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5962      ChessMove moveType;
5963      int fromX, fromY, toX, toY;
5964      /*char*/int promoChar;
5965 {
5966     char *bookHit = 0;
5967
5968     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5969         // [HGM] superchess: suppress promotions to non-available piece
5970         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5971         if(WhiteOnMove(currentMove)) {
5972             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5973         } else {
5974             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5975         }
5976     }
5977
5978     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5979        move type in caller when we know the move is a legal promotion */
5980     if(moveType == NormalMove && promoChar)
5981         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5982
5983     /* [HGM] convert drag-and-drop piece drops to standard form */
5984     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5985          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5986            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5987                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5988            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5989            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5990            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5991            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5992          fromY = DROP_RANK;
5993     }
5994
5995     /* [HGM] <popupFix> The following if has been moved here from
5996        UserMoveEvent(). Because it seemed to belong here (why not allow
5997        piece drops in training games?), and because it can only be
5998        performed after it is known to what we promote. */
5999     if (gameMode == Training) {
6000       /* compare the move played on the board to the next move in the
6001        * game. If they match, display the move and the opponent's response. 
6002        * If they don't match, display an error message.
6003        */
6004       int saveAnimate;
6005       Board testBoard;
6006       CopyBoard(testBoard, boards[currentMove]);
6007       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6008
6009       if (CompareBoards(testBoard, boards[currentMove+1])) {
6010         ForwardInner(currentMove+1);
6011
6012         /* Autoplay the opponent's response.
6013          * if appData.animate was TRUE when Training mode was entered,
6014          * the response will be animated.
6015          */
6016         saveAnimate = appData.animate;
6017         appData.animate = animateTraining;
6018         ForwardInner(currentMove+1);
6019         appData.animate = saveAnimate;
6020
6021         /* check for the end of the game */
6022         if (currentMove >= forwardMostMove) {
6023           gameMode = PlayFromGameFile;
6024           ModeHighlight();
6025           SetTrainingModeOff();
6026           DisplayInformation(_("End of game"));
6027         }
6028       } else {
6029         DisplayError(_("Incorrect move"), 0);
6030       }
6031       return 1;
6032     }
6033
6034   /* Ok, now we know that the move is good, so we can kill
6035      the previous line in Analysis Mode */
6036   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6037                                 && currentMove < forwardMostMove) {
6038     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6039   }
6040
6041   /* If we need the chess program but it's dead, restart it */
6042   ResurrectChessProgram();
6043
6044   /* A user move restarts a paused game*/
6045   if (pausing)
6046     PauseEvent();
6047
6048   thinkOutput[0] = NULLCHAR;
6049
6050   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6051
6052   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6053
6054   if (gameMode == BeginningOfGame) {
6055     if (appData.noChessProgram) {
6056       gameMode = EditGame;
6057       SetGameInfo();
6058     } else {
6059       char buf[MSG_SIZ];
6060       gameMode = MachinePlaysBlack;
6061       StartClocks();
6062       SetGameInfo();
6063       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6064       DisplayTitle(buf);
6065       if (first.sendName) {
6066         sprintf(buf, "name %s\n", gameInfo.white);
6067         SendToProgram(buf, &first);
6068       }
6069       StartClocks();
6070     }
6071     ModeHighlight();
6072   }
6073
6074   /* Relay move to ICS or chess engine */
6075   if (appData.icsActive) {
6076     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6077         gameMode == IcsExamining) {
6078       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6079         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6080         SendToICS("draw ");
6081         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6082       }
6083       // also send plain move, in case ICS does not understand atomic claims
6084       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6085       ics_user_moved = 1;
6086     }
6087   } else {
6088     if (first.sendTime && (gameMode == BeginningOfGame ||
6089                            gameMode == MachinePlaysWhite ||
6090                            gameMode == MachinePlaysBlack)) {
6091       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6092     }
6093     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6094          // [HGM] book: if program might be playing, let it use book
6095         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6096         first.maybeThinking = TRUE;
6097     } else SendMoveToProgram(forwardMostMove-1, &first);
6098     if (currentMove == cmailOldMove + 1) {
6099       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6100     }
6101   }
6102
6103   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104
6105   switch (gameMode) {
6106   case EditGame:
6107     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6108     case MT_NONE:
6109     case MT_CHECK:
6110       break;
6111     case MT_CHECKMATE:
6112     case MT_STAINMATE:
6113       if (WhiteOnMove(currentMove)) {
6114         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6115       } else {
6116         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6117       }
6118       break;
6119     case MT_STALEMATE:
6120       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6121       break;
6122     }
6123     break;
6124     
6125   case MachinePlaysBlack:
6126   case MachinePlaysWhite:
6127     /* disable certain menu options while machine is thinking */
6128     SetMachineThinkingEnables();
6129     break;
6130
6131   default:
6132     break;
6133   }
6134
6135   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6136         
6137   if(bookHit) { // [HGM] book: simulate book reply
6138         static char bookMove[MSG_SIZ]; // a bit generous?
6139
6140         programStats.nodes = programStats.depth = programStats.time = 
6141         programStats.score = programStats.got_only_move = 0;
6142         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6143
6144         strcpy(bookMove, "move ");
6145         strcat(bookMove, bookHit);
6146         HandleMachineMove(bookMove, &first);
6147   }
6148   return 1;
6149 }
6150
6151 void
6152 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6153      int fromX, fromY, toX, toY;
6154      int promoChar;
6155 {
6156     /* [HGM] This routine was added to allow calling of its two logical
6157        parts from other modules in the old way. Before, UserMoveEvent()
6158        automatically called FinishMove() if the move was OK, and returned
6159        otherwise. I separated the two, in order to make it possible to
6160        slip a promotion popup in between. But that it always needs two
6161        calls, to the first part, (now called UserMoveTest() ), and to
6162        FinishMove if the first part succeeded. Calls that do not need
6163        to do anything in between, can call this routine the old way. 
6164     */
6165     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6166 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6167     if(moveType == AmbiguousMove)
6168         DrawPosition(FALSE, boards[currentMove]);
6169     else if(moveType != ImpossibleMove && moveType != Comment)
6170         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6171 }
6172
6173 void
6174 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6175      Board board;
6176      int flags;
6177      ChessMove kind;
6178      int rf, ff, rt, ft;
6179      VOIDSTAR closure;
6180 {
6181     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6182     Markers *m = (Markers *) closure;
6183     if(rf == fromY && ff == fromX)
6184         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6185                          || kind == WhiteCapturesEnPassant
6186                          || kind == BlackCapturesEnPassant);
6187     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6188 }
6189
6190 void
6191 MarkTargetSquares(int clear)
6192 {
6193   int x, y;
6194   if(!appData.markers || !appData.highlightDragging || 
6195      !appData.testLegality || gameMode == EditPosition) return;
6196   if(clear) {
6197     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6198   } else {
6199     int capt = 0;
6200     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6201     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6202       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6203       if(capt)
6204       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6205     }
6206   }
6207   DrawPosition(TRUE, NULL);
6208 }
6209
6210 void LeftClick(ClickType clickType, int xPix, int yPix)
6211 {
6212     int x, y;
6213     Boolean saveAnimate;
6214     static int second = 0, promotionChoice = 0;
6215     char promoChoice = NULLCHAR;
6216
6217     if(appData.seekGraph && appData.icsActive && loggedOn &&
6218         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6219         SeekGraphClick(clickType, xPix, yPix, 0);
6220         return;
6221     }
6222
6223     if (clickType == Press) ErrorPopDown();
6224     MarkTargetSquares(1);
6225
6226     x = EventToSquare(xPix, BOARD_WIDTH);
6227     y = EventToSquare(yPix, BOARD_HEIGHT);
6228     if (!flipView && y >= 0) {
6229         y = BOARD_HEIGHT - 1 - y;
6230     }
6231     if (flipView && x >= 0) {
6232         x = BOARD_WIDTH - 1 - x;
6233     }
6234
6235     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6236         if(clickType == Release) return; // ignore upclick of click-click destination
6237         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6238         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6239         if(gameInfo.holdingsWidth && 
6240                 (WhiteOnMove(currentMove) 
6241                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6242                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6243             // click in right holdings, for determining promotion piece
6244             ChessSquare p = boards[currentMove][y][x];
6245             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6246             if(p != EmptySquare) {
6247                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6248                 fromX = fromY = -1;
6249                 return;
6250             }
6251         }
6252         DrawPosition(FALSE, boards[currentMove]);
6253         return;
6254     }
6255
6256     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6257     if(clickType == Press
6258             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6259               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6260               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6261         return;
6262
6263     autoQueen = appData.alwaysPromoteToQueen;
6264
6265     if (fromX == -1) {
6266       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6267         if (clickType == Press) {
6268             /* First square */
6269             if (OKToStartUserMove(x, y)) {
6270                 fromX = x;
6271                 fromY = y;
6272                 second = 0;
6273                 MarkTargetSquares(0);
6274                 DragPieceBegin(xPix, yPix);
6275                 if (appData.highlightDragging) {
6276                     SetHighlights(x, y, -1, -1);
6277                 }
6278             }
6279         }
6280         return;
6281       }
6282     }
6283
6284     /* fromX != -1 */
6285     if (clickType == Press && gameMode != EditPosition) {
6286         ChessSquare fromP;
6287         ChessSquare toP;
6288         int frc;
6289
6290         // ignore off-board to clicks
6291         if(y < 0 || x < 0) return;
6292
6293         /* Check if clicking again on the same color piece */
6294         fromP = boards[currentMove][fromY][fromX];
6295         toP = boards[currentMove][y][x];
6296         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6297         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6298              WhitePawn <= toP && toP <= WhiteKing &&
6299              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6300              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6301             (BlackPawn <= fromP && fromP <= BlackKing && 
6302              BlackPawn <= toP && toP <= BlackKing &&
6303              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6304              !(fromP == BlackKing && toP == BlackRook && frc))) {
6305             /* Clicked again on same color piece -- changed his mind */
6306             second = (x == fromX && y == fromY);
6307            if(!second || !OnlyMove(&x, &y, TRUE)) {
6308             if (appData.highlightDragging) {
6309                 SetHighlights(x, y, -1, -1);
6310             } else {
6311                 ClearHighlights();
6312             }
6313             if (OKToStartUserMove(x, y)) {
6314                 fromX = x;
6315                 fromY = y;
6316                 MarkTargetSquares(0);
6317                 DragPieceBegin(xPix, yPix);
6318             }
6319             return;
6320            }
6321         }
6322         // ignore clicks on holdings
6323         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6324     }
6325
6326     if (clickType == Release && x == fromX && y == fromY) {
6327         DragPieceEnd(xPix, yPix);
6328         if (appData.animateDragging) {
6329             /* Undo animation damage if any */
6330             DrawPosition(FALSE, NULL);
6331         }
6332         if (second) {
6333             /* Second up/down in same square; just abort move */
6334             second = 0;
6335             fromX = fromY = -1;
6336             ClearHighlights();
6337             gotPremove = 0;
6338             ClearPremoveHighlights();
6339         } else {
6340             /* First upclick in same square; start click-click mode */
6341             SetHighlights(x, y, -1, -1);
6342         }
6343         return;
6344     }
6345
6346     /* we now have a different from- and (possibly off-board) to-square */
6347     /* Completed move */
6348     toX = x;
6349     toY = y;
6350     saveAnimate = appData.animate;
6351     if (clickType == Press) {
6352         /* Finish clickclick move */
6353         if (appData.animate || appData.highlightLastMove) {
6354             SetHighlights(fromX, fromY, toX, toY);
6355         } else {
6356             ClearHighlights();
6357         }
6358     } else {
6359         /* Finish drag move */
6360         if (appData.highlightLastMove) {
6361             SetHighlights(fromX, fromY, toX, toY);
6362         } else {
6363             ClearHighlights();
6364         }
6365         DragPieceEnd(xPix, yPix);
6366         /* Don't animate move and drag both */
6367         appData.animate = FALSE;
6368     }
6369
6370     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6371     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6372         ChessSquare piece = boards[currentMove][fromY][fromX];
6373         if(gameMode == EditPosition && piece != EmptySquare &&
6374            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6375             int n;
6376              
6377             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6378                 n = PieceToNumber(piece - (int)BlackPawn);
6379                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6380                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6381                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6382             } else
6383             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6384                 n = PieceToNumber(piece);
6385                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6386                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6387                 boards[currentMove][n][BOARD_WIDTH-2]++;
6388             }
6389             boards[currentMove][fromY][fromX] = EmptySquare;
6390         }
6391         ClearHighlights();
6392         fromX = fromY = -1;
6393         DrawPosition(TRUE, boards[currentMove]);
6394         return;
6395     }
6396
6397     // off-board moves should not be highlighted
6398     if(x < 0 || x < 0) ClearHighlights();
6399
6400     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6401         SetHighlights(fromX, fromY, toX, toY);
6402         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6403             // [HGM] super: promotion to captured piece selected from holdings
6404             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6405             promotionChoice = TRUE;
6406             // kludge follows to temporarily execute move on display, without promoting yet
6407             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6408             boards[currentMove][toY][toX] = p;
6409             DrawPosition(FALSE, boards[currentMove]);
6410             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6411             boards[currentMove][toY][toX] = q;
6412             DisplayMessage("Click in holdings to choose piece", "");
6413             return;
6414         }
6415         PromotionPopUp();
6416     } else {
6417         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6418         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6419         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6420         fromX = fromY = -1;
6421     }
6422     appData.animate = saveAnimate;
6423     if (appData.animate || appData.animateDragging) {
6424         /* Undo animation damage if needed */
6425         DrawPosition(FALSE, NULL);
6426     }
6427 }
6428
6429 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6430 {   // front-end-free part taken out of PieceMenuPopup
6431     int whichMenu; int xSqr, ySqr;
6432
6433     if(seekGraphUp) { // [HGM] seekgraph
6434         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6435         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6436         return -2;
6437     }
6438
6439     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6440          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6441         if(action == Press)   {
6442             originalFlip = flipView;
6443             flipView = !flipView; // temporarily flip board to see game from partners perspective
6444             DrawPosition(TRUE, partnerBoard);
6445             DisplayMessage(partnerStatus, "");
6446             partnerUp = TRUE;
6447         } else if(action == Release) {
6448             flipView = originalFlip;
6449             DrawPosition(TRUE, boards[currentMove]);
6450             partnerUp = FALSE;
6451         }
6452         return -2;
6453     }
6454
6455     xSqr = EventToSquare(x, BOARD_WIDTH);
6456     ySqr = EventToSquare(y, BOARD_HEIGHT);
6457     if (action == Release) UnLoadPV(); // [HGM] pv
6458     if (action != Press) return -2; // return code to be ignored
6459     switch (gameMode) {
6460       case IcsExamining:
6461         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6462       case EditPosition:
6463         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6464         if (xSqr < 0 || ySqr < 0) return -1;\r
6465         whichMenu = 0; // edit-position menu
6466         break;
6467       case IcsObserving:
6468         if(!appData.icsEngineAnalyze) return -1;
6469       case IcsPlayingWhite:
6470       case IcsPlayingBlack:
6471         if(!appData.zippyPlay) goto noZip;
6472       case AnalyzeMode:
6473       case AnalyzeFile:
6474       case MachinePlaysWhite:
6475       case MachinePlaysBlack:
6476       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6477         if (!appData.dropMenu) {
6478           LoadPV(x, y);
6479           return 2; // flag front-end to grab mouse events
6480         }
6481         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6482            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6483       case EditGame:
6484       noZip:
6485         if (xSqr < 0 || ySqr < 0) return -1;
6486         if (!appData.dropMenu || appData.testLegality &&
6487             gameInfo.variant != VariantBughouse &&
6488             gameInfo.variant != VariantCrazyhouse) return -1;
6489         whichMenu = 1; // drop menu
6490         break;
6491       default:
6492         return -1;
6493     }
6494
6495     if (((*fromX = xSqr) < 0) ||
6496         ((*fromY = ySqr) < 0)) {
6497         *fromX = *fromY = -1;
6498         return -1;
6499     }
6500     if (flipView)
6501       *fromX = BOARD_WIDTH - 1 - *fromX;
6502     else
6503       *fromY = BOARD_HEIGHT - 1 - *fromY;
6504
6505     return whichMenu;
6506 }
6507
6508 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6509 {
6510 //    char * hint = lastHint;
6511     FrontEndProgramStats stats;
6512
6513     stats.which = cps == &first ? 0 : 1;
6514     stats.depth = cpstats->depth;
6515     stats.nodes = cpstats->nodes;
6516     stats.score = cpstats->score;
6517     stats.time = cpstats->time;
6518     stats.pv = cpstats->movelist;
6519     stats.hint = lastHint;
6520     stats.an_move_index = 0;
6521     stats.an_move_count = 0;
6522
6523     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6524         stats.hint = cpstats->move_name;
6525         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6526         stats.an_move_count = cpstats->nr_moves;
6527     }
6528
6529     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6530
6531     SetProgramStats( &stats );
6532 }
6533
6534 int
6535 Adjudicate(ChessProgramState *cps)
6536 {       // [HGM] some adjudications useful with buggy engines
6537         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6538         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6539         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6540         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6541         int k, count = 0; static int bare = 1;
6542         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6543         Boolean canAdjudicate = !appData.icsActive;
6544
6545         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6546         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6547             if( appData.testLegality )
6548             {   /* [HGM] Some more adjudications for obstinate engines */
6549                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6550                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6551                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6552                 static int moveCount = 6;
6553                 ChessMove result;
6554                 char *reason = NULL;
6555
6556                 /* Count what is on board. */
6557                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6558                 {   ChessSquare p = boards[forwardMostMove][i][j];
6559                     int m=i;
6560
6561                     switch((int) p)
6562                     {   /* count B,N,R and other of each side */
6563                         case WhiteKing:
6564                         case BlackKing:
6565                              NrK++; break; // [HGM] atomic: count Kings
6566                         case WhiteKnight:
6567                              NrWN++; break;
6568                         case WhiteBishop:
6569                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6570                              bishopsColor |= 1 << ((i^j)&1);
6571                              NrWB++; break;
6572                         case BlackKnight:
6573                              NrBN++; break;
6574                         case BlackBishop:
6575                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6576                              bishopsColor |= 1 << ((i^j)&1);
6577                              NrBB++; break;
6578                         case WhiteRook:
6579                              NrWR++; break;
6580                         case BlackRook:
6581                              NrBR++; break;
6582                         case WhiteQueen:
6583                              NrWQ++; break;
6584                         case BlackQueen:
6585                              NrBQ++; break;
6586                         case EmptySquare: 
6587                              break;
6588                         case BlackPawn:
6589                              m = 7-i;
6590                         case WhitePawn:
6591                              PawnAdvance += m; NrPawns++;
6592                     }
6593                     NrPieces += (p != EmptySquare);
6594                     NrW += ((int)p < (int)BlackPawn);
6595                     if(gameInfo.variant == VariantXiangqi && 
6596                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6597                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6598                         NrW -= ((int)p < (int)BlackPawn);
6599                     }
6600                 }
6601
6602                 /* Some material-based adjudications that have to be made before stalemate test */
6603                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6604                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6605                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6606                      if(canAdjudicate && appData.checkMates) {
6607                          if(engineOpponent)
6608                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6609                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6610                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6611                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6612                          return 1;
6613                      }
6614                 }
6615
6616                 /* Bare King in Shatranj (loses) or Losers (wins) */
6617                 if( NrW == 1 || NrPieces - NrW == 1) {
6618                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6619                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6620                      if(canAdjudicate && appData.checkMates) {
6621                          if(engineOpponent)
6622                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6623                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6625                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6626                          return 1;
6627                      }
6628                   } else
6629                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6630                   {    /* bare King */
6631                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6632                         if(canAdjudicate && appData.checkMates) {
6633                             /* but only adjudicate if adjudication enabled */
6634                             if(engineOpponent)
6635                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6636                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6637                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6638                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6639                             return 1;
6640                         }
6641                   }
6642                 } else bare = 1;
6643
6644
6645             // don't wait for engine to announce game end if we can judge ourselves
6646             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6647               case MT_CHECK:
6648                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6649                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6650                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6651                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6652                             checkCnt++;
6653                         if(checkCnt >= 2) {
6654                             reason = "Xboard adjudication: 3rd check";
6655                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6656                             break;
6657                         }
6658                     }
6659                 }
6660               case MT_NONE:
6661               default:
6662                 break;
6663               case MT_STALEMATE:
6664               case MT_STAINMATE:
6665                 reason = "Xboard adjudication: Stalemate";
6666                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6667                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6668                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6669                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6670                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6671                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6672                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6673                                                                         EP_CHECKMATE : EP_WINS);
6674                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6675                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6676                 }
6677                 break;
6678               case MT_CHECKMATE:
6679                 reason = "Xboard adjudication: Checkmate";
6680                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6681                 break;
6682             }
6683
6684                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6685                     case EP_STALEMATE:
6686                         result = GameIsDrawn; break;
6687                     case EP_CHECKMATE:
6688                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6689                     case EP_WINS:
6690                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6691                     default:
6692                         result = (ChessMove) 0;
6693                 }
6694                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6695                     if(engineOpponent)
6696                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6697                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6698                     GameEnds( result, reason, GE_XBOARD );
6699                     return 1;
6700                 }
6701
6702                 /* Next absolutely insufficient mating material. */
6703                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6704                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6705                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6706                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6707                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6708
6709                      /* always flag draws, for judging claims */
6710                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6711
6712                      if(canAdjudicate && appData.materialDraws) {
6713                          /* but only adjudicate them if adjudication enabled */
6714                          if(engineOpponent) {
6715                            SendToProgram("force\n", engineOpponent); // suppress reply
6716                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6717                          }
6718                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6719                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6720                          return 1;
6721                      }
6722                 }
6723
6724                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6725                 if(NrPieces == 4 && 
6726                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6727                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6728                    || NrWN==2 || NrBN==2     /* KNNK */
6729                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6730                   ) ) {
6731                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6732                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6733                           if(engineOpponent) {
6734                             SendToProgram("force\n", engineOpponent); // suppress reply
6735                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6736                           }
6737                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6738                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6739                           return 1;
6740                      }
6741                 } else moveCount = 6;
6742             }
6743         }
6744           
6745         if (appData.debugMode) { int i;
6746             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6747                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6748                     appData.drawRepeats);
6749             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6750               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6751             
6752         }
6753
6754         // Repetition draws and 50-move rule can be applied independently of legality testing
6755
6756                 /* Check for rep-draws */
6757                 count = 0;
6758                 for(k = forwardMostMove-2;
6759                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6760                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6761                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6762                     k-=2)
6763                 {   int rights=0;
6764                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6765                         /* compare castling rights */
6766                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6767                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6768                                 rights++; /* King lost rights, while rook still had them */
6769                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6770                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6771                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6772                                    rights++; /* but at least one rook lost them */
6773                         }
6774                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6775                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6776                                 rights++; 
6777                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6778                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6779                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6780                                    rights++;
6781                         }
6782                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6783                             && appData.drawRepeats > 1) {
6784                              /* adjudicate after user-specified nr of repeats */
6785                              if(engineOpponent) {
6786                                SendToProgram("force\n", engineOpponent); // suppress reply
6787                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6788                              }
6789                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6790                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6791                                 // [HGM] xiangqi: check for forbidden perpetuals
6792                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6793                                 for(m=forwardMostMove; m>k; m-=2) {
6794                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6795                                         ourPerpetual = 0; // the current mover did not always check
6796                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6797                                         hisPerpetual = 0; // the opponent did not always check
6798                                 }
6799                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6800                                                                         ourPerpetual, hisPerpetual);
6801                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6802                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6803                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6804                                     return 1;
6805                                 }
6806                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6807                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6808                                 // Now check for perpetual chases
6809                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6810                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6811                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6812                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6813                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6814                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6815                                         return 1;
6816                                     }
6817                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6818                                         break; // Abort repetition-checking loop.
6819                                 }
6820                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6821                              }
6822                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6823                              return 1;
6824                         }
6825                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6826                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6827                     }
6828                 }
6829
6830                 /* Now we test for 50-move draws. Determine ply count */
6831                 count = forwardMostMove;
6832                 /* look for last irreversble move */
6833                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6834                     count--;
6835                 /* if we hit starting position, add initial plies */
6836                 if( count == backwardMostMove )
6837                     count -= initialRulePlies;
6838                 count = forwardMostMove - count; 
6839                 if( count >= 100)
6840                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6841                          /* this is used to judge if draw claims are legal */
6842                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6843                          if(engineOpponent) {
6844                            SendToProgram("force\n", engineOpponent); // suppress reply
6845                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6846                          }
6847                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6848                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6849                          return 1;
6850                 }
6851
6852                 /* if draw offer is pending, treat it as a draw claim
6853                  * when draw condition present, to allow engines a way to
6854                  * claim draws before making their move to avoid a race
6855                  * condition occurring after their move
6856                  */
6857                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6858                          char *p = NULL;
6859                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6860                              p = "Draw claim: 50-move rule";
6861                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6862                              p = "Draw claim: 3-fold repetition";
6863                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6864                              p = "Draw claim: insufficient mating material";
6865                          if( p != NULL && canAdjudicate) {
6866                              if(engineOpponent) {
6867                                SendToProgram("force\n", engineOpponent); // suppress reply
6868                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6869                              }
6870                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6871                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6872                              return 1;
6873                          }
6874                 }
6875
6876                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6877                     if(engineOpponent) {
6878                       SendToProgram("force\n", engineOpponent); // suppress reply
6879                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6880                     }
6881                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6883                     return 1;
6884                 }
6885         return 0;
6886 }
6887
6888 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6889 {   // [HGM] book: this routine intercepts moves to simulate book replies
6890     char *bookHit = NULL;
6891
6892     //first determine if the incoming move brings opponent into his book
6893     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6894         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6895     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6896     if(bookHit != NULL && !cps->bookSuspend) {
6897         // make sure opponent is not going to reply after receiving move to book position
6898         SendToProgram("force\n", cps);
6899         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6900     }
6901     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6902     // now arrange restart after book miss
6903     if(bookHit) {
6904         // after a book hit we never send 'go', and the code after the call to this routine
6905         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6906         char buf[MSG_SIZ];
6907         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6908         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6909         SendToProgram(buf, cps);
6910         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6911     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6912         SendToProgram("go\n", cps);
6913         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6914     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6915         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6916             SendToProgram("go\n", cps); 
6917         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6918     }
6919     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6920 }
6921
6922 char *savedMessage;
6923 ChessProgramState *savedState;
6924 void DeferredBookMove(void)
6925 {
6926         if(savedState->lastPing != savedState->lastPong)
6927                     ScheduleDelayedEvent(DeferredBookMove, 10);
6928         else
6929         HandleMachineMove(savedMessage, savedState);
6930 }
6931
6932 void
6933 HandleMachineMove(message, cps)
6934      char *message;
6935      ChessProgramState *cps;
6936 {
6937     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6938     char realname[MSG_SIZ];
6939     int fromX, fromY, toX, toY;
6940     ChessMove moveType;
6941     char promoChar;
6942     char *p;
6943     int machineWhite;
6944     char *bookHit;
6945
6946     cps->userError = 0;
6947
6948 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6949     /*
6950      * Kludge to ignore BEL characters
6951      */
6952     while (*message == '\007') message++;
6953
6954     /*
6955      * [HGM] engine debug message: ignore lines starting with '#' character
6956      */
6957     if(cps->debug && *message == '#') return;
6958
6959     /*
6960      * Look for book output
6961      */
6962     if (cps == &first && bookRequested) {
6963         if (message[0] == '\t' || message[0] == ' ') {
6964             /* Part of the book output is here; append it */
6965             strcat(bookOutput, message);
6966             strcat(bookOutput, "  \n");
6967             return;
6968         } else if (bookOutput[0] != NULLCHAR) {
6969             /* All of book output has arrived; display it */
6970             char *p = bookOutput;
6971             while (*p != NULLCHAR) {
6972                 if (*p == '\t') *p = ' ';
6973                 p++;
6974             }
6975             DisplayInformation(bookOutput);
6976             bookRequested = FALSE;
6977             /* Fall through to parse the current output */
6978         }
6979     }
6980
6981     /*
6982      * Look for machine move.
6983      */
6984     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6985         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6986     {
6987         /* This method is only useful on engines that support ping */
6988         if (cps->lastPing != cps->lastPong) {
6989           if (gameMode == BeginningOfGame) {
6990             /* Extra move from before last new; ignore */
6991             if (appData.debugMode) {
6992                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6993             }
6994           } else {
6995             if (appData.debugMode) {
6996                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6997                         cps->which, gameMode);
6998             }
6999
7000             SendToProgram("undo\n", cps);
7001           }
7002           return;
7003         }
7004
7005         switch (gameMode) {
7006           case BeginningOfGame:
7007             /* Extra move from before last reset; ignore */
7008             if (appData.debugMode) {
7009                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7010             }
7011             return;
7012
7013           case EndOfGame:
7014           case IcsIdle:
7015           default:
7016             /* Extra move after we tried to stop.  The mode test is
7017                not a reliable way of detecting this problem, but it's
7018                the best we can do on engines that don't support ping.
7019             */
7020             if (appData.debugMode) {
7021                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7022                         cps->which, gameMode);
7023             }
7024             SendToProgram("undo\n", cps);
7025             return;
7026
7027           case MachinePlaysWhite:
7028           case IcsPlayingWhite:
7029             machineWhite = TRUE;
7030             break;
7031
7032           case MachinePlaysBlack:
7033           case IcsPlayingBlack:
7034             machineWhite = FALSE;
7035             break;
7036
7037           case TwoMachinesPlay:
7038             machineWhite = (cps->twoMachinesColor[0] == 'w');
7039             break;
7040         }
7041         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7042             if (appData.debugMode) {
7043                 fprintf(debugFP,
7044                         "Ignoring move out of turn by %s, gameMode %d"
7045                         ", forwardMost %d\n",
7046                         cps->which, gameMode, forwardMostMove);
7047             }
7048             return;
7049         }
7050
7051     if (appData.debugMode) { int f = forwardMostMove;
7052         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7053                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7054                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7055     }
7056         if(cps->alphaRank) AlphaRank(machineMove, 4);
7057         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7058                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7059             /* Machine move could not be parsed; ignore it. */
7060             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7061                     machineMove, cps->which);
7062             DisplayError(buf1, 0);
7063             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7064                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7065             if (gameMode == TwoMachinesPlay) {
7066               GameEnds(machineWhite ? BlackWins : WhiteWins,
7067                        buf1, GE_XBOARD);
7068             }
7069             return;
7070         }
7071
7072         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7073         /* So we have to redo legality test with true e.p. status here,  */
7074         /* to make sure an illegal e.p. capture does not slip through,   */
7075         /* to cause a forfeit on a justified illegal-move complaint      */
7076         /* of the opponent.                                              */
7077         if( gameMode==TwoMachinesPlay && appData.testLegality
7078             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7079                                                               ) {
7080            ChessMove moveType;
7081            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7082                              fromY, fromX, toY, toX, promoChar);
7083             if (appData.debugMode) {
7084                 int i;
7085                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7086                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7087                 fprintf(debugFP, "castling rights\n");
7088             }
7089             if(moveType == IllegalMove) {
7090                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7091                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7092                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7093                            buf1, GE_XBOARD);
7094                 return;
7095            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7096            /* [HGM] Kludge to handle engines that send FRC-style castling
7097               when they shouldn't (like TSCP-Gothic) */
7098            switch(moveType) {
7099              case WhiteASideCastleFR:
7100              case BlackASideCastleFR:
7101                toX+=2;
7102                currentMoveString[2]++;
7103                break;
7104              case WhiteHSideCastleFR:
7105              case BlackHSideCastleFR:
7106                toX--;
7107                currentMoveString[2]--;
7108                break;
7109              default: ; // nothing to do, but suppresses warning of pedantic compilers
7110            }
7111         }
7112         hintRequested = FALSE;
7113         lastHint[0] = NULLCHAR;
7114         bookRequested = FALSE;
7115         /* Program may be pondering now */
7116         cps->maybeThinking = TRUE;
7117         if (cps->sendTime == 2) cps->sendTime = 1;
7118         if (cps->offeredDraw) cps->offeredDraw--;
7119
7120         /* currentMoveString is set as a side-effect of ParseOneMove */
7121         strcpy(machineMove, currentMoveString);
7122         strcat(machineMove, "\n");
7123         strcpy(moveList[forwardMostMove], machineMove);
7124
7125         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7126
7127         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7128         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7129             int count = 0;
7130
7131             while( count < adjudicateLossPlies ) {
7132                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7133
7134                 if( count & 1 ) {
7135                     score = -score; /* Flip score for winning side */
7136                 }
7137
7138                 if( score > adjudicateLossThreshold ) {
7139                     break;
7140                 }
7141
7142                 count++;
7143             }
7144
7145             if( count >= adjudicateLossPlies ) {
7146                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7147
7148                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7149                     "Xboard adjudication", 
7150                     GE_XBOARD );
7151
7152                 return;
7153             }
7154         }
7155
7156         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7157
7158 #if ZIPPY
7159         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7160             first.initDone) {
7161           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7162                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7163                 SendToICS("draw ");
7164                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7165           }
7166           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7167           ics_user_moved = 1;
7168           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7169                 char buf[3*MSG_SIZ];
7170
7171                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7172                         programStats.score / 100.,
7173                         programStats.depth,
7174                         programStats.time / 100.,
7175                         (unsigned int)programStats.nodes,
7176                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7177                         programStats.movelist);
7178                 SendToICS(buf);
7179 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7180           }
7181         }
7182 #endif
7183
7184         /* [AS] Save move info and clear stats for next move */
7185         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7186         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7187         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7188         ClearProgramStats();
7189         thinkOutput[0] = NULLCHAR;
7190         hiddenThinkOutputState = 0;
7191
7192         bookHit = NULL;
7193         if (gameMode == TwoMachinesPlay) {
7194             /* [HGM] relaying draw offers moved to after reception of move */
7195             /* and interpreting offer as claim if it brings draw condition */
7196             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7197                 SendToProgram("draw\n", cps->other);
7198             }
7199             if (cps->other->sendTime) {
7200                 SendTimeRemaining(cps->other,
7201                                   cps->other->twoMachinesColor[0] == 'w');
7202             }
7203             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7204             if (firstMove && !bookHit) {
7205                 firstMove = FALSE;
7206                 if (cps->other->useColors) {
7207                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7208                 }
7209                 SendToProgram("go\n", cps->other);
7210             }
7211             cps->other->maybeThinking = TRUE;
7212         }
7213
7214         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7215         
7216         if (!pausing && appData.ringBellAfterMoves) {
7217             RingBell();
7218         }
7219
7220         /* 
7221          * Reenable menu items that were disabled while
7222          * machine was thinking
7223          */
7224         if (gameMode != TwoMachinesPlay)
7225             SetUserThinkingEnables();
7226
7227         // [HGM] book: after book hit opponent has received move and is now in force mode
7228         // force the book reply into it, and then fake that it outputted this move by jumping
7229         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7230         if(bookHit) {
7231                 static char bookMove[MSG_SIZ]; // a bit generous?
7232
7233                 strcpy(bookMove, "move ");
7234                 strcat(bookMove, bookHit);
7235                 message = bookMove;
7236                 cps = cps->other;
7237                 programStats.nodes = programStats.depth = programStats.time = 
7238                 programStats.score = programStats.got_only_move = 0;
7239                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7240
7241                 if(cps->lastPing != cps->lastPong) {
7242                     savedMessage = message; // args for deferred call
7243                     savedState = cps;
7244                     ScheduleDelayedEvent(DeferredBookMove, 10);
7245                     return;
7246                 }
7247                 goto FakeBookMove;
7248         }
7249
7250         return;
7251     }
7252
7253     /* Set special modes for chess engines.  Later something general
7254      *  could be added here; for now there is just one kludge feature,
7255      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7256      *  when "xboard" is given as an interactive command.
7257      */
7258     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7259         cps->useSigint = FALSE;
7260         cps->useSigterm = FALSE;
7261     }
7262     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7263       ParseFeatures(message+8, cps);
7264       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7265     }
7266
7267     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7268      * want this, I was asked to put it in, and obliged.
7269      */
7270     if (!strncmp(message, "setboard ", 9)) {
7271         Board initial_position;
7272
7273         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7274
7275         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7276             DisplayError(_("Bad FEN received from engine"), 0);
7277             return ;
7278         } else {
7279            Reset(TRUE, FALSE);
7280            CopyBoard(boards[0], initial_position);
7281            initialRulePlies = FENrulePlies;
7282            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7283            else gameMode = MachinePlaysBlack;                 
7284            DrawPosition(FALSE, boards[currentMove]);
7285         }
7286         return;
7287     }
7288
7289     /*
7290      * Look for communication commands
7291      */
7292     if (!strncmp(message, "telluser ", 9)) {
7293         DisplayNote(message + 9);
7294         return;
7295     }
7296     if (!strncmp(message, "tellusererror ", 14)) {
7297         cps->userError = 1;
7298         DisplayError(message + 14, 0);
7299         return;
7300     }
7301     if (!strncmp(message, "tellopponent ", 13)) {
7302       if (appData.icsActive) {
7303         if (loggedOn) {
7304           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7305           SendToICS(buf1);
7306         }
7307       } else {
7308         DisplayNote(message + 13);
7309       }
7310       return;
7311     }
7312     if (!strncmp(message, "tellothers ", 11)) {
7313       if (appData.icsActive) {
7314         if (loggedOn) {
7315           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7316           SendToICS(buf1);
7317         }
7318       }
7319       return;
7320     }
7321     if (!strncmp(message, "tellall ", 8)) {
7322       if (appData.icsActive) {
7323         if (loggedOn) {
7324           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7325           SendToICS(buf1);
7326         }
7327       } else {
7328         DisplayNote(message + 8);
7329       }
7330       return;
7331     }
7332     if (strncmp(message, "warning", 7) == 0) {
7333         /* Undocumented feature, use tellusererror in new code */
7334         DisplayError(message, 0);
7335         return;
7336     }
7337     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7338         strcpy(realname, cps->tidy);
7339         strcat(realname, " query");
7340         AskQuestion(realname, buf2, buf1, cps->pr);
7341         return;
7342     }
7343     /* Commands from the engine directly to ICS.  We don't allow these to be 
7344      *  sent until we are logged on. Crafty kibitzes have been known to 
7345      *  interfere with the login process.
7346      */
7347     if (loggedOn) {
7348         if (!strncmp(message, "tellics ", 8)) {
7349             SendToICS(message + 8);
7350             SendToICS("\n");
7351             return;
7352         }
7353         if (!strncmp(message, "tellicsnoalias ", 15)) {
7354             SendToICS(ics_prefix);
7355             SendToICS(message + 15);
7356             SendToICS("\n");
7357             return;
7358         }
7359         /* The following are for backward compatibility only */
7360         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7361             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7362             SendToICS(ics_prefix);
7363             SendToICS(message);
7364             SendToICS("\n");
7365             return;
7366         }
7367     }
7368     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7369         return;
7370     }
7371     /*
7372      * If the move is illegal, cancel it and redraw the board.
7373      * Also deal with other error cases.  Matching is rather loose
7374      * here to accommodate engines written before the spec.
7375      */
7376     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7377         strncmp(message, "Error", 5) == 0) {
7378         if (StrStr(message, "name") || 
7379             StrStr(message, "rating") || StrStr(message, "?") ||
7380             StrStr(message, "result") || StrStr(message, "board") ||
7381             StrStr(message, "bk") || StrStr(message, "computer") ||
7382             StrStr(message, "variant") || StrStr(message, "hint") ||
7383             StrStr(message, "random") || StrStr(message, "depth") ||
7384             StrStr(message, "accepted")) {
7385             return;
7386         }
7387         if (StrStr(message, "protover")) {
7388           /* Program is responding to input, so it's apparently done
7389              initializing, and this error message indicates it is
7390              protocol version 1.  So we don't need to wait any longer
7391              for it to initialize and send feature commands. */
7392           FeatureDone(cps, 1);
7393           cps->protocolVersion = 1;
7394           return;
7395         }
7396         cps->maybeThinking = FALSE;
7397
7398         if (StrStr(message, "draw")) {
7399             /* Program doesn't have "draw" command */
7400             cps->sendDrawOffers = 0;
7401             return;
7402         }
7403         if (cps->sendTime != 1 &&
7404             (StrStr(message, "time") || StrStr(message, "otim"))) {
7405           /* Program apparently doesn't have "time" or "otim" command */
7406           cps->sendTime = 0;
7407           return;
7408         }
7409         if (StrStr(message, "analyze")) {
7410             cps->analysisSupport = FALSE;
7411             cps->analyzing = FALSE;
7412             Reset(FALSE, TRUE);
7413             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7414             DisplayError(buf2, 0);
7415             return;
7416         }
7417         if (StrStr(message, "(no matching move)st")) {
7418           /* Special kludge for GNU Chess 4 only */
7419           cps->stKludge = TRUE;
7420           SendTimeControl(cps, movesPerSession, timeControl,
7421                           timeIncrement, appData.searchDepth,
7422                           searchTime);
7423           return;
7424         }
7425         if (StrStr(message, "(no matching move)sd")) {
7426           /* Special kludge for GNU Chess 4 only */
7427           cps->sdKludge = TRUE;
7428           SendTimeControl(cps, movesPerSession, timeControl,
7429                           timeIncrement, appData.searchDepth,
7430                           searchTime);
7431           return;
7432         }
7433         if (!StrStr(message, "llegal")) {
7434             return;
7435         }
7436         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7437             gameMode == IcsIdle) return;
7438         if (forwardMostMove <= backwardMostMove) return;
7439         if (pausing) PauseEvent();
7440       if(appData.forceIllegal) {
7441             // [HGM] illegal: machine refused move; force position after move into it
7442           SendToProgram("force\n", cps);
7443           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7444                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7445                 // when black is to move, while there might be nothing on a2 or black
7446                 // might already have the move. So send the board as if white has the move.
7447                 // But first we must change the stm of the engine, as it refused the last move
7448                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7449                 if(WhiteOnMove(forwardMostMove)) {
7450                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7451                     SendBoard(cps, forwardMostMove); // kludgeless board
7452                 } else {
7453                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7454                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7455                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7456                 }
7457           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7458             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7459                  gameMode == TwoMachinesPlay)
7460               SendToProgram("go\n", cps);
7461             return;
7462       } else
7463         if (gameMode == PlayFromGameFile) {
7464             /* Stop reading this game file */
7465             gameMode = EditGame;
7466             ModeHighlight();
7467         }
7468         currentMove = forwardMostMove-1;
7469         DisplayMove(currentMove-1); /* before DisplayMoveError */
7470         SwitchClocks(forwardMostMove-1); // [HGM] race
7471         DisplayBothClocks();
7472         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7473                 parseList[currentMove], cps->which);
7474         DisplayMoveError(buf1);
7475         DrawPosition(FALSE, boards[currentMove]);
7476
7477         /* [HGM] illegal-move claim should forfeit game when Xboard */
7478         /* only passes fully legal moves                            */
7479         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7480             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7481                                 "False illegal-move claim", GE_XBOARD );
7482         }
7483         return;
7484     }
7485     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7486         /* Program has a broken "time" command that
7487            outputs a string not ending in newline.
7488            Don't use it. */
7489         cps->sendTime = 0;
7490     }
7491     
7492     /*
7493      * If chess program startup fails, exit with an error message.
7494      * Attempts to recover here are futile.
7495      */
7496     if ((StrStr(message, "unknown host") != NULL)
7497         || (StrStr(message, "No remote directory") != NULL)
7498         || (StrStr(message, "not found") != NULL)
7499         || (StrStr(message, "No such file") != NULL)
7500         || (StrStr(message, "can't alloc") != NULL)
7501         || (StrStr(message, "Permission denied") != NULL)) {
7502
7503         cps->maybeThinking = FALSE;
7504         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7505                 cps->which, cps->program, cps->host, message);
7506         RemoveInputSource(cps->isr);
7507         DisplayFatalError(buf1, 0, 1);
7508         return;
7509     }
7510     
7511     /* 
7512      * Look for hint output
7513      */
7514     if (sscanf(message, "Hint: %s", buf1) == 1) {
7515         if (cps == &first && hintRequested) {
7516             hintRequested = FALSE;
7517             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7518                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7519                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7520                                     PosFlags(forwardMostMove),
7521                                     fromY, fromX, toY, toX, promoChar, buf1);
7522                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7523                 DisplayInformation(buf2);
7524             } else {
7525                 /* Hint move could not be parsed!? */
7526               snprintf(buf2, sizeof(buf2),
7527                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7528                         buf1, cps->which);
7529                 DisplayError(buf2, 0);
7530             }
7531         } else {
7532             strcpy(lastHint, buf1);
7533         }
7534         return;
7535     }
7536
7537     /*
7538      * Ignore other messages if game is not in progress
7539      */
7540     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7541         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7542
7543     /*
7544      * look for win, lose, draw, or draw offer
7545      */
7546     if (strncmp(message, "1-0", 3) == 0) {
7547         char *p, *q, *r = "";
7548         p = strchr(message, '{');
7549         if (p) {
7550             q = strchr(p, '}');
7551             if (q) {
7552                 *q = NULLCHAR;
7553                 r = p + 1;
7554             }
7555         }
7556         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7557         return;
7558     } else if (strncmp(message, "0-1", 3) == 0) {
7559         char *p, *q, *r = "";
7560         p = strchr(message, '{');
7561         if (p) {
7562             q = strchr(p, '}');
7563             if (q) {
7564                 *q = NULLCHAR;
7565                 r = p + 1;
7566             }
7567         }
7568         /* Kludge for Arasan 4.1 bug */
7569         if (strcmp(r, "Black resigns") == 0) {
7570             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7571             return;
7572         }
7573         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7574         return;
7575     } else if (strncmp(message, "1/2", 3) == 0) {
7576         char *p, *q, *r = "";
7577         p = strchr(message, '{');
7578         if (p) {
7579             q = strchr(p, '}');
7580             if (q) {
7581                 *q = NULLCHAR;
7582                 r = p + 1;
7583             }
7584         }
7585             
7586         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7587         return;
7588
7589     } else if (strncmp(message, "White resign", 12) == 0) {
7590         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7591         return;
7592     } else if (strncmp(message, "Black resign", 12) == 0) {
7593         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7594         return;
7595     } else if (strncmp(message, "White matches", 13) == 0 ||
7596                strncmp(message, "Black matches", 13) == 0   ) {
7597         /* [HGM] ignore GNUShogi noises */
7598         return;
7599     } else if (strncmp(message, "White", 5) == 0 &&
7600                message[5] != '(' &&
7601                StrStr(message, "Black") == NULL) {
7602         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7603         return;
7604     } else if (strncmp(message, "Black", 5) == 0 &&
7605                message[5] != '(') {
7606         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7607         return;
7608     } else if (strcmp(message, "resign") == 0 ||
7609                strcmp(message, "computer resigns") == 0) {
7610         switch (gameMode) {
7611           case MachinePlaysBlack:
7612           case IcsPlayingBlack:
7613             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7614             break;
7615           case MachinePlaysWhite:
7616           case IcsPlayingWhite:
7617             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7618             break;
7619           case TwoMachinesPlay:
7620             if (cps->twoMachinesColor[0] == 'w')
7621               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7622             else
7623               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7624             break;
7625           default:
7626             /* can't happen */
7627             break;
7628         }
7629         return;
7630     } else if (strncmp(message, "opponent mates", 14) == 0) {
7631         switch (gameMode) {
7632           case MachinePlaysBlack:
7633           case IcsPlayingBlack:
7634             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7635             break;
7636           case MachinePlaysWhite:
7637           case IcsPlayingWhite:
7638             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7639             break;
7640           case TwoMachinesPlay:
7641             if (cps->twoMachinesColor[0] == 'w')
7642               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7643             else
7644               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7645             break;
7646           default:
7647             /* can't happen */
7648             break;
7649         }
7650         return;
7651     } else if (strncmp(message, "computer mates", 14) == 0) {
7652         switch (gameMode) {
7653           case MachinePlaysBlack:
7654           case IcsPlayingBlack:
7655             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7656             break;
7657           case MachinePlaysWhite:
7658           case IcsPlayingWhite:
7659             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7660             break;
7661           case TwoMachinesPlay:
7662             if (cps->twoMachinesColor[0] == 'w')
7663               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7664             else
7665               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7666             break;
7667           default:
7668             /* can't happen */
7669             break;
7670         }
7671         return;
7672     } else if (strncmp(message, "checkmate", 9) == 0) {
7673         if (WhiteOnMove(forwardMostMove)) {
7674             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7675         } else {
7676             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7677         }
7678         return;
7679     } else if (strstr(message, "Draw") != NULL ||
7680                strstr(message, "game is a draw") != NULL) {
7681         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7682         return;
7683     } else if (strstr(message, "offer") != NULL &&
7684                strstr(message, "draw") != NULL) {
7685 #if ZIPPY
7686         if (appData.zippyPlay && first.initDone) {
7687             /* Relay offer to ICS */
7688             SendToICS(ics_prefix);
7689             SendToICS("draw\n");
7690         }
7691 #endif
7692         cps->offeredDraw = 2; /* valid until this engine moves twice */
7693         if (gameMode == TwoMachinesPlay) {
7694             if (cps->other->offeredDraw) {
7695                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7696             /* [HGM] in two-machine mode we delay relaying draw offer      */
7697             /* until after we also have move, to see if it is really claim */
7698             }
7699         } else if (gameMode == MachinePlaysWhite ||
7700                    gameMode == MachinePlaysBlack) {
7701           if (userOfferedDraw) {
7702             DisplayInformation(_("Machine accepts your draw offer"));
7703             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7704           } else {
7705             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7706           }
7707         }
7708     }
7709
7710     
7711     /*
7712      * Look for thinking output
7713      */
7714     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7715           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7716                                 ) {
7717         int plylev, mvleft, mvtot, curscore, time;
7718         char mvname[MOVE_LEN];
7719         u64 nodes; // [DM]
7720         char plyext;
7721         int ignore = FALSE;
7722         int prefixHint = FALSE;
7723         mvname[0] = NULLCHAR;
7724
7725         switch (gameMode) {
7726           case MachinePlaysBlack:
7727           case IcsPlayingBlack:
7728             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7729             break;
7730           case MachinePlaysWhite:
7731           case IcsPlayingWhite:
7732             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7733             break;
7734           case AnalyzeMode:
7735           case AnalyzeFile:
7736             break;
7737           case IcsObserving: /* [DM] icsEngineAnalyze */
7738             if (!appData.icsEngineAnalyze) ignore = TRUE;
7739             break;
7740           case TwoMachinesPlay:
7741             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7742                 ignore = TRUE;
7743             }
7744             break;
7745           default:
7746             ignore = TRUE;
7747             break;
7748         }
7749
7750         if (!ignore) {
7751             buf1[0] = NULLCHAR;
7752             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7753                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7754
7755                 if (plyext != ' ' && plyext != '\t') {
7756                     time *= 100;
7757                 }
7758
7759                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7760                 if( cps->scoreIsAbsolute && 
7761                     ( gameMode == MachinePlaysBlack ||
7762                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7763                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7764                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7765                      !WhiteOnMove(currentMove)
7766                     ) )
7767                 {
7768                     curscore = -curscore;
7769                 }
7770
7771
7772                 programStats.depth = plylev;
7773                 programStats.nodes = nodes;
7774                 programStats.time = time;
7775                 programStats.score = curscore;
7776                 programStats.got_only_move = 0;
7777
7778                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7779                         int ticklen;
7780
7781                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7782                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7783                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7784                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7785                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7786                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7787                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7788                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7789                 }
7790
7791                 /* Buffer overflow protection */
7792                 if (buf1[0] != NULLCHAR) {
7793                     if (strlen(buf1) >= sizeof(programStats.movelist)
7794                         && appData.debugMode) {
7795                         fprintf(debugFP,
7796                                 "PV is too long; using the first %u bytes.\n",
7797                                 (unsigned) sizeof(programStats.movelist) - 1);
7798                     }
7799
7800                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7801                 } else {
7802                     sprintf(programStats.movelist, " no PV\n");
7803                 }
7804
7805                 if (programStats.seen_stat) {
7806                     programStats.ok_to_send = 1;
7807                 }
7808
7809                 if (strchr(programStats.movelist, '(') != NULL) {
7810                     programStats.line_is_book = 1;
7811                     programStats.nr_moves = 0;
7812                     programStats.moves_left = 0;
7813                 } else {
7814                     programStats.line_is_book = 0;
7815                 }
7816
7817                 SendProgramStatsToFrontend( cps, &programStats );
7818
7819                 /* 
7820                     [AS] Protect the thinkOutput buffer from overflow... this
7821                     is only useful if buf1 hasn't overflowed first!
7822                 */
7823                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7824                         plylev, 
7825                         (gameMode == TwoMachinesPlay ?
7826                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7827                         ((double) curscore) / 100.0,
7828                         prefixHint ? lastHint : "",
7829                         prefixHint ? " " : "" );
7830
7831                 if( buf1[0] != NULLCHAR ) {
7832                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7833
7834                     if( strlen(buf1) > max_len ) {
7835                         if( appData.debugMode) {
7836                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7837                         }
7838                         buf1[max_len+1] = '\0';
7839                     }
7840
7841                     strcat( thinkOutput, buf1 );
7842                 }
7843
7844                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7845                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7846                     DisplayMove(currentMove - 1);
7847                 }
7848                 return;
7849
7850             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7851                 /* crafty (9.25+) says "(only move) <move>"
7852                  * if there is only 1 legal move
7853                  */
7854                 sscanf(p, "(only move) %s", buf1);
7855                 sprintf(thinkOutput, "%s (only move)", buf1);
7856                 sprintf(programStats.movelist, "%s (only move)", buf1);
7857                 programStats.depth = 1;
7858                 programStats.nr_moves = 1;
7859                 programStats.moves_left = 1;
7860                 programStats.nodes = 1;
7861                 programStats.time = 1;
7862                 programStats.got_only_move = 1;
7863
7864                 /* Not really, but we also use this member to
7865                    mean "line isn't going to change" (Crafty
7866                    isn't searching, so stats won't change) */
7867                 programStats.line_is_book = 1;
7868
7869                 SendProgramStatsToFrontend( cps, &programStats );
7870                 
7871                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7872                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7873                     DisplayMove(currentMove - 1);
7874                 }
7875                 return;
7876             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7877                               &time, &nodes, &plylev, &mvleft,
7878                               &mvtot, mvname) >= 5) {
7879                 /* The stat01: line is from Crafty (9.29+) in response
7880                    to the "." command */
7881                 programStats.seen_stat = 1;
7882                 cps->maybeThinking = TRUE;
7883
7884                 if (programStats.got_only_move || !appData.periodicUpdates)
7885                   return;
7886
7887                 programStats.depth = plylev;
7888                 programStats.time = time;
7889                 programStats.nodes = nodes;
7890                 programStats.moves_left = mvleft;
7891                 programStats.nr_moves = mvtot;
7892                 strcpy(programStats.move_name, mvname);
7893                 programStats.ok_to_send = 1;
7894                 programStats.movelist[0] = '\0';
7895
7896                 SendProgramStatsToFrontend( cps, &programStats );
7897
7898                 return;
7899
7900             } else if (strncmp(message,"++",2) == 0) {
7901                 /* Crafty 9.29+ outputs this */
7902                 programStats.got_fail = 2;
7903                 return;
7904
7905             } else if (strncmp(message,"--",2) == 0) {
7906                 /* Crafty 9.29+ outputs this */
7907                 programStats.got_fail = 1;
7908                 return;
7909
7910             } else if (thinkOutput[0] != NULLCHAR &&
7911                        strncmp(message, "    ", 4) == 0) {
7912                 unsigned message_len;
7913
7914                 p = message;
7915                 while (*p && *p == ' ') p++;
7916
7917                 message_len = strlen( p );
7918
7919                 /* [AS] Avoid buffer overflow */
7920                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7921                     strcat(thinkOutput, " ");
7922                     strcat(thinkOutput, p);
7923                 }
7924
7925                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7926                     strcat(programStats.movelist, " ");
7927                     strcat(programStats.movelist, p);
7928                 }
7929
7930                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7931                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7932                     DisplayMove(currentMove - 1);
7933                 }
7934                 return;
7935             }
7936         }
7937         else {
7938             buf1[0] = NULLCHAR;
7939
7940             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7941                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7942             {
7943                 ChessProgramStats cpstats;
7944
7945                 if (plyext != ' ' && plyext != '\t') {
7946                     time *= 100;
7947                 }
7948
7949                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7950                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7951                     curscore = -curscore;
7952                 }
7953
7954                 cpstats.depth = plylev;
7955                 cpstats.nodes = nodes;
7956                 cpstats.time = time;
7957                 cpstats.score = curscore;
7958                 cpstats.got_only_move = 0;
7959                 cpstats.movelist[0] = '\0';
7960
7961                 if (buf1[0] != NULLCHAR) {
7962                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7963                 }
7964
7965                 cpstats.ok_to_send = 0;
7966                 cpstats.line_is_book = 0;
7967                 cpstats.nr_moves = 0;
7968                 cpstats.moves_left = 0;
7969
7970                 SendProgramStatsToFrontend( cps, &cpstats );
7971             }
7972         }
7973     }
7974 }
7975
7976
7977 /* Parse a game score from the character string "game", and
7978    record it as the history of the current game.  The game
7979    score is NOT assumed to start from the standard position. 
7980    The display is not updated in any way.
7981    */
7982 void
7983 ParseGameHistory(game)
7984      char *game;
7985 {
7986     ChessMove moveType;
7987     int fromX, fromY, toX, toY, boardIndex;
7988     char promoChar;
7989     char *p, *q;
7990     char buf[MSG_SIZ];
7991
7992     if (appData.debugMode)
7993       fprintf(debugFP, "Parsing game history: %s\n", game);
7994
7995     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7996     gameInfo.site = StrSave(appData.icsHost);
7997     gameInfo.date = PGNDate();
7998     gameInfo.round = StrSave("-");
7999
8000     /* Parse out names of players */
8001     while (*game == ' ') game++;
8002     p = buf;
8003     while (*game != ' ') *p++ = *game++;
8004     *p = NULLCHAR;
8005     gameInfo.white = StrSave(buf);
8006     while (*game == ' ') game++;
8007     p = buf;
8008     while (*game != ' ' && *game != '\n') *p++ = *game++;
8009     *p = NULLCHAR;
8010     gameInfo.black = StrSave(buf);
8011
8012     /* Parse moves */
8013     boardIndex = blackPlaysFirst ? 1 : 0;
8014     yynewstr(game);
8015     for (;;) {
8016         yyboardindex = boardIndex;
8017         moveType = (ChessMove) yylex();
8018         switch (moveType) {
8019           case IllegalMove:             /* maybe suicide chess, etc. */
8020   if (appData.debugMode) {
8021     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8022     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8023     setbuf(debugFP, NULL);
8024   }
8025           case WhitePromotionChancellor:
8026           case BlackPromotionChancellor:
8027           case WhitePromotionArchbishop:
8028           case BlackPromotionArchbishop:
8029           case WhitePromotionQueen:
8030           case BlackPromotionQueen:
8031           case WhitePromotionRook:
8032           case BlackPromotionRook:
8033           case WhitePromotionBishop:
8034           case BlackPromotionBishop:
8035           case WhitePromotionKnight:
8036           case BlackPromotionKnight:
8037           case WhitePromotionKing:
8038           case BlackPromotionKing:
8039           case NormalMove:
8040           case WhiteCapturesEnPassant:
8041           case BlackCapturesEnPassant:
8042           case WhiteKingSideCastle:
8043           case WhiteQueenSideCastle:
8044           case BlackKingSideCastle:
8045           case BlackQueenSideCastle:
8046           case WhiteKingSideCastleWild:
8047           case WhiteQueenSideCastleWild:
8048           case BlackKingSideCastleWild:
8049           case BlackQueenSideCastleWild:
8050           /* PUSH Fabien */
8051           case WhiteHSideCastleFR:
8052           case WhiteASideCastleFR:
8053           case BlackHSideCastleFR:
8054           case BlackASideCastleFR:
8055           /* POP Fabien */
8056             fromX = currentMoveString[0] - AAA;
8057             fromY = currentMoveString[1] - ONE;
8058             toX = currentMoveString[2] - AAA;
8059             toY = currentMoveString[3] - ONE;
8060             promoChar = currentMoveString[4];
8061             break;
8062           case WhiteDrop:
8063           case BlackDrop:
8064             fromX = moveType == WhiteDrop ?
8065               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8066             (int) CharToPiece(ToLower(currentMoveString[0]));
8067             fromY = DROP_RANK;
8068             toX = currentMoveString[2] - AAA;
8069             toY = currentMoveString[3] - ONE;
8070             promoChar = NULLCHAR;
8071             break;
8072           case AmbiguousMove:
8073             /* bug? */
8074             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8075   if (appData.debugMode) {
8076     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8077     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8078     setbuf(debugFP, NULL);
8079   }
8080             DisplayError(buf, 0);
8081             return;
8082           case ImpossibleMove:
8083             /* bug? */
8084             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8085   if (appData.debugMode) {
8086     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8087     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8088     setbuf(debugFP, NULL);
8089   }
8090             DisplayError(buf, 0);
8091             return;
8092           case (ChessMove) 0:   /* end of file */
8093             if (boardIndex < backwardMostMove) {
8094                 /* Oops, gap.  How did that happen? */
8095                 DisplayError(_("Gap in move list"), 0);
8096                 return;
8097             }
8098             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8099             if (boardIndex > forwardMostMove) {
8100                 forwardMostMove = boardIndex;
8101             }
8102             return;
8103           case ElapsedTime:
8104             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8105                 strcat(parseList[boardIndex-1], " ");
8106                 strcat(parseList[boardIndex-1], yy_text);
8107             }
8108             continue;
8109           case Comment:
8110           case PGNTag:
8111           case NAG:
8112           default:
8113             /* ignore */
8114             continue;
8115           case WhiteWins:
8116           case BlackWins:
8117           case GameIsDrawn:
8118           case GameUnfinished:
8119             if (gameMode == IcsExamining) {
8120                 if (boardIndex < backwardMostMove) {
8121                     /* Oops, gap.  How did that happen? */
8122                     return;
8123                 }
8124                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8125                 return;
8126             }
8127             gameInfo.result = moveType;
8128             p = strchr(yy_text, '{');
8129             if (p == NULL) p = strchr(yy_text, '(');
8130             if (p == NULL) {
8131                 p = yy_text;
8132                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8133             } else {
8134                 q = strchr(p, *p == '{' ? '}' : ')');
8135                 if (q != NULL) *q = NULLCHAR;
8136                 p++;
8137             }
8138             gameInfo.resultDetails = StrSave(p);
8139             continue;
8140         }
8141         if (boardIndex >= forwardMostMove &&
8142             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8143             backwardMostMove = blackPlaysFirst ? 1 : 0;
8144             return;
8145         }
8146         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8147                                  fromY, fromX, toY, toX, promoChar,
8148                                  parseList[boardIndex]);
8149         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8150         /* currentMoveString is set as a side-effect of yylex */
8151         strcpy(moveList[boardIndex], currentMoveString);
8152         strcat(moveList[boardIndex], "\n");
8153         boardIndex++;
8154         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8155         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8156           case MT_NONE:
8157           case MT_STALEMATE:
8158           default:
8159             break;
8160           case MT_CHECK:
8161             if(gameInfo.variant != VariantShogi)
8162                 strcat(parseList[boardIndex - 1], "+");
8163             break;
8164           case MT_CHECKMATE:
8165           case MT_STAINMATE:
8166             strcat(parseList[boardIndex - 1], "#");
8167             break;
8168         }
8169     }
8170 }
8171
8172
8173 /* Apply a move to the given board  */
8174 void
8175 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8176      int fromX, fromY, toX, toY;
8177      int promoChar;
8178      Board board;
8179 {
8180   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8181   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8182
8183     /* [HGM] compute & store e.p. status and castling rights for new position */
8184     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8185     { int i;
8186
8187       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8188       oldEP = (signed char)board[EP_STATUS];
8189       board[EP_STATUS] = EP_NONE;
8190
8191       if( board[toY][toX] != EmptySquare ) 
8192            board[EP_STATUS] = EP_CAPTURE;  
8193
8194       if( board[fromY][fromX] == WhitePawn ) {
8195            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8196                board[EP_STATUS] = EP_PAWN_MOVE;
8197            if( toY-fromY==2) {
8198                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8199                         gameInfo.variant != VariantBerolina || toX < fromX)
8200                       board[EP_STATUS] = toX | berolina;
8201                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8202                         gameInfo.variant != VariantBerolina || toX > fromX) 
8203                       board[EP_STATUS] = toX;
8204            }
8205       } else 
8206       if( board[fromY][fromX] == BlackPawn ) {
8207            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8208                board[EP_STATUS] = EP_PAWN_MOVE; 
8209            if( toY-fromY== -2) {
8210                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8211                         gameInfo.variant != VariantBerolina || toX < fromX)
8212                       board[EP_STATUS] = toX | berolina;
8213                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8214                         gameInfo.variant != VariantBerolina || toX > fromX) 
8215                       board[EP_STATUS] = toX;
8216            }
8217        }
8218
8219        for(i=0; i<nrCastlingRights; i++) {
8220            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8221               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8222              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8223        }
8224
8225     }
8226
8227   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8228   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8229        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8230          
8231   if (fromX == toX && fromY == toY) return;
8232
8233   if (fromY == DROP_RANK) {
8234         /* must be first */
8235         piece = board[toY][toX] = (ChessSquare) fromX;
8236   } else {
8237      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8238      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8239      if(gameInfo.variant == VariantKnightmate)
8240          king += (int) WhiteUnicorn - (int) WhiteKing;
8241
8242     /* Code added by Tord: */
8243     /* FRC castling assumed when king captures friendly rook. */
8244     if (board[fromY][fromX] == WhiteKing &&
8245              board[toY][toX] == WhiteRook) {
8246       board[fromY][fromX] = EmptySquare;
8247       board[toY][toX] = EmptySquare;
8248       if(toX > fromX) {
8249         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8250       } else {
8251         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8252       }
8253     } else if (board[fromY][fromX] == BlackKing &&
8254                board[toY][toX] == BlackRook) {
8255       board[fromY][fromX] = EmptySquare;
8256       board[toY][toX] = EmptySquare;
8257       if(toX > fromX) {
8258         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8259       } else {
8260         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8261       }
8262     /* End of code added by Tord */
8263
8264     } else if (board[fromY][fromX] == king
8265         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8266         && toY == fromY && toX > fromX+1) {
8267         board[fromY][fromX] = EmptySquare;
8268         board[toY][toX] = king;
8269         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8270         board[fromY][BOARD_RGHT-1] = EmptySquare;
8271     } else if (board[fromY][fromX] == king
8272         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8273                && toY == fromY && toX < fromX-1) {
8274         board[fromY][fromX] = EmptySquare;
8275         board[toY][toX] = king;
8276         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8277         board[fromY][BOARD_LEFT] = EmptySquare;
8278     } else if (board[fromY][fromX] == WhitePawn
8279                && toY >= BOARD_HEIGHT-promoRank
8280                && gameInfo.variant != VariantXiangqi
8281                ) {
8282         /* white pawn promotion */
8283         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8284         if (board[toY][toX] == EmptySquare) {
8285             board[toY][toX] = WhiteQueen;
8286         }
8287         if(gameInfo.variant==VariantBughouse ||
8288            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8289             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8290         board[fromY][fromX] = EmptySquare;
8291     } else if ((fromY == BOARD_HEIGHT-4)
8292                && (toX != fromX)
8293                && gameInfo.variant != VariantXiangqi
8294                && gameInfo.variant != VariantBerolina
8295                && (board[fromY][fromX] == WhitePawn)
8296                && (board[toY][toX] == EmptySquare)) {
8297         board[fromY][fromX] = EmptySquare;
8298         board[toY][toX] = WhitePawn;
8299         captured = board[toY - 1][toX];
8300         board[toY - 1][toX] = EmptySquare;
8301     } else if ((fromY == BOARD_HEIGHT-4)
8302                && (toX == fromX)
8303                && gameInfo.variant == VariantBerolina
8304                && (board[fromY][fromX] == WhitePawn)
8305                && (board[toY][toX] == EmptySquare)) {
8306         board[fromY][fromX] = EmptySquare;
8307         board[toY][toX] = WhitePawn;
8308         if(oldEP & EP_BEROLIN_A) {
8309                 captured = board[fromY][fromX-1];
8310                 board[fromY][fromX-1] = EmptySquare;
8311         }else{  captured = board[fromY][fromX+1];
8312                 board[fromY][fromX+1] = EmptySquare;
8313         }
8314     } else if (board[fromY][fromX] == king
8315         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8316                && toY == fromY && toX > fromX+1) {
8317         board[fromY][fromX] = EmptySquare;
8318         board[toY][toX] = king;
8319         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8320         board[fromY][BOARD_RGHT-1] = EmptySquare;
8321     } else if (board[fromY][fromX] == king
8322         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8323                && toY == fromY && toX < fromX-1) {
8324         board[fromY][fromX] = EmptySquare;
8325         board[toY][toX] = king;
8326         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8327         board[fromY][BOARD_LEFT] = EmptySquare;
8328     } else if (fromY == 7 && fromX == 3
8329                && board[fromY][fromX] == BlackKing
8330                && toY == 7 && toX == 5) {
8331         board[fromY][fromX] = EmptySquare;
8332         board[toY][toX] = BlackKing;
8333         board[fromY][7] = EmptySquare;
8334         board[toY][4] = BlackRook;
8335     } else if (fromY == 7 && fromX == 3
8336                && board[fromY][fromX] == BlackKing
8337                && toY == 7 && toX == 1) {
8338         board[fromY][fromX] = EmptySquare;
8339         board[toY][toX] = BlackKing;
8340         board[fromY][0] = EmptySquare;
8341         board[toY][2] = BlackRook;
8342     } else if (board[fromY][fromX] == BlackPawn
8343                && toY < promoRank
8344                && gameInfo.variant != VariantXiangqi
8345                ) {
8346         /* black pawn promotion */
8347         board[toY][toX] = CharToPiece(ToLower(promoChar));
8348         if (board[toY][toX] == EmptySquare) {
8349             board[toY][toX] = BlackQueen;
8350         }
8351         if(gameInfo.variant==VariantBughouse ||
8352            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8353             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8354         board[fromY][fromX] = EmptySquare;
8355     } else if ((fromY == 3)
8356                && (toX != fromX)
8357                && gameInfo.variant != VariantXiangqi
8358                && gameInfo.variant != VariantBerolina
8359                && (board[fromY][fromX] == BlackPawn)
8360                && (board[toY][toX] == EmptySquare)) {
8361         board[fromY][fromX] = EmptySquare;
8362         board[toY][toX] = BlackPawn;
8363         captured = board[toY + 1][toX];
8364         board[toY + 1][toX] = EmptySquare;
8365     } else if ((fromY == 3)
8366                && (toX == fromX)
8367                && gameInfo.variant == VariantBerolina
8368                && (board[fromY][fromX] == BlackPawn)
8369                && (board[toY][toX] == EmptySquare)) {
8370         board[fromY][fromX] = EmptySquare;
8371         board[toY][toX] = BlackPawn;
8372         if(oldEP & EP_BEROLIN_A) {
8373                 captured = board[fromY][fromX-1];
8374                 board[fromY][fromX-1] = EmptySquare;
8375         }else{  captured = board[fromY][fromX+1];
8376                 board[fromY][fromX+1] = EmptySquare;
8377         }
8378     } else {
8379         board[toY][toX] = board[fromY][fromX];
8380         board[fromY][fromX] = EmptySquare;
8381     }
8382
8383     /* [HGM] now we promote for Shogi, if needed */
8384     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8385         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8386   }
8387
8388     if (gameInfo.holdingsWidth != 0) {
8389
8390       /* !!A lot more code needs to be written to support holdings  */
8391       /* [HGM] OK, so I have written it. Holdings are stored in the */
8392       /* penultimate board files, so they are automaticlly stored   */
8393       /* in the game history.                                       */
8394       if (fromY == DROP_RANK) {
8395         /* Delete from holdings, by decreasing count */
8396         /* and erasing image if necessary            */
8397         p = (int) fromX;
8398         if(p < (int) BlackPawn) { /* white drop */
8399              p -= (int)WhitePawn;
8400                  p = PieceToNumber((ChessSquare)p);
8401              if(p >= gameInfo.holdingsSize) p = 0;
8402              if(--board[p][BOARD_WIDTH-2] <= 0)
8403                   board[p][BOARD_WIDTH-1] = EmptySquare;
8404              if((int)board[p][BOARD_WIDTH-2] < 0)
8405                         board[p][BOARD_WIDTH-2] = 0;
8406         } else {                  /* black drop */
8407              p -= (int)BlackPawn;
8408                  p = PieceToNumber((ChessSquare)p);
8409              if(p >= gameInfo.holdingsSize) p = 0;
8410              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8411                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8412              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8413                         board[BOARD_HEIGHT-1-p][1] = 0;
8414         }
8415       }
8416       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8417           && gameInfo.variant != VariantBughouse        ) {
8418         /* [HGM] holdings: Add to holdings, if holdings exist */
8419         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8420                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8421                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8422         }
8423         p = (int) captured;
8424         if (p >= (int) BlackPawn) {
8425           p -= (int)BlackPawn;
8426           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8427                   /* in Shogi restore piece to its original  first */
8428                   captured = (ChessSquare) (DEMOTED captured);
8429                   p = DEMOTED p;
8430           }
8431           p = PieceToNumber((ChessSquare)p);
8432           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8433           board[p][BOARD_WIDTH-2]++;
8434           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8435         } else {
8436           p -= (int)WhitePawn;
8437           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8438                   captured = (ChessSquare) (DEMOTED captured);
8439                   p = DEMOTED p;
8440           }
8441           p = PieceToNumber((ChessSquare)p);
8442           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8443           board[BOARD_HEIGHT-1-p][1]++;
8444           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8445         }
8446       }
8447     } else if (gameInfo.variant == VariantAtomic) {
8448       if (captured != EmptySquare) {
8449         int y, x;
8450         for (y = toY-1; y <= toY+1; y++) {
8451           for (x = toX-1; x <= toX+1; x++) {
8452             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8453                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8454               board[y][x] = EmptySquare;
8455             }
8456           }
8457         }
8458         board[toY][toX] = EmptySquare;
8459       }
8460     }
8461     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8462         /* [HGM] Shogi promotions */
8463         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8464     }
8465
8466     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8467                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8468         // [HGM] superchess: take promotion piece out of holdings
8469         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8470         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8471             if(!--board[k][BOARD_WIDTH-2])
8472                 board[k][BOARD_WIDTH-1] = EmptySquare;
8473         } else {
8474             if(!--board[BOARD_HEIGHT-1-k][1])
8475                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8476         }
8477     }
8478
8479 }
8480
8481 /* Updates forwardMostMove */
8482 void
8483 MakeMove(fromX, fromY, toX, toY, promoChar)
8484      int fromX, fromY, toX, toY;
8485      int promoChar;
8486 {
8487 //    forwardMostMove++; // [HGM] bare: moved downstream
8488
8489     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8490         int timeLeft; static int lastLoadFlag=0; int king, piece;
8491         piece = boards[forwardMostMove][fromY][fromX];
8492         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8493         if(gameInfo.variant == VariantKnightmate)
8494             king += (int) WhiteUnicorn - (int) WhiteKing;
8495         if(forwardMostMove == 0) {
8496             if(blackPlaysFirst) 
8497                 fprintf(serverMoves, "%s;", second.tidy);
8498             fprintf(serverMoves, "%s;", first.tidy);
8499             if(!blackPlaysFirst) 
8500                 fprintf(serverMoves, "%s;", second.tidy);
8501         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8502         lastLoadFlag = loadFlag;
8503         // print base move
8504         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8505         // print castling suffix
8506         if( toY == fromY && piece == king ) {
8507             if(toX-fromX > 1)
8508                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8509             if(fromX-toX >1)
8510                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8511         }
8512         // e.p. suffix
8513         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8514              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8515              boards[forwardMostMove][toY][toX] == EmptySquare
8516              && fromX != toX )
8517                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8518         // promotion suffix
8519         if(promoChar != NULLCHAR)
8520                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8521         if(!loadFlag) {
8522             fprintf(serverMoves, "/%d/%d",
8523                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8524             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8525             else                      timeLeft = blackTimeRemaining/1000;
8526             fprintf(serverMoves, "/%d", timeLeft);
8527         }
8528         fflush(serverMoves);
8529     }
8530
8531     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8532       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8533                         0, 1);
8534       return;
8535     }
8536     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8537     if (commentList[forwardMostMove+1] != NULL) {
8538         free(commentList[forwardMostMove+1]);
8539         commentList[forwardMostMove+1] = NULL;
8540     }
8541     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8542     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8543     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8544     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8545     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8546     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8547     gameInfo.result = GameUnfinished;
8548     if (gameInfo.resultDetails != NULL) {
8549         free(gameInfo.resultDetails);
8550         gameInfo.resultDetails = NULL;
8551     }
8552     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8553                               moveList[forwardMostMove - 1]);
8554     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8555                              PosFlags(forwardMostMove - 1),
8556                              fromY, fromX, toY, toX, promoChar,
8557                              parseList[forwardMostMove - 1]);
8558     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8559       case MT_NONE:
8560       case MT_STALEMATE:
8561       default:
8562         break;
8563       case MT_CHECK:
8564         if(gameInfo.variant != VariantShogi)
8565             strcat(parseList[forwardMostMove - 1], "+");
8566         break;
8567       case MT_CHECKMATE:
8568       case MT_STAINMATE:
8569         strcat(parseList[forwardMostMove - 1], "#");
8570         break;
8571     }
8572     if (appData.debugMode) {
8573         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8574     }
8575
8576 }
8577
8578 /* Updates currentMove if not pausing */
8579 void
8580 ShowMove(fromX, fromY, toX, toY)
8581 {
8582     int instant = (gameMode == PlayFromGameFile) ?
8583         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8584     if(appData.noGUI) return;
8585     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8586         if (!instant) {
8587             if (forwardMostMove == currentMove + 1) {
8588                 AnimateMove(boards[forwardMostMove - 1],
8589                             fromX, fromY, toX, toY);
8590             }
8591             if (appData.highlightLastMove) {
8592                 SetHighlights(fromX, fromY, toX, toY);
8593             }
8594         }
8595         currentMove = forwardMostMove;
8596     }
8597
8598     if (instant) return;
8599
8600     DisplayMove(currentMove - 1);
8601     DrawPosition(FALSE, boards[currentMove]);
8602     DisplayBothClocks();
8603     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8604 }
8605
8606 void SendEgtPath(ChessProgramState *cps)
8607 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8608         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8609
8610         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8611
8612         while(*p) {
8613             char c, *q = name+1, *r, *s;
8614
8615             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8616             while(*p && *p != ',') *q++ = *p++;
8617             *q++ = ':'; *q = 0;
8618             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8619                 strcmp(name, ",nalimov:") == 0 ) {
8620                 // take nalimov path from the menu-changeable option first, if it is defined
8621                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8622                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8623             } else
8624             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8625                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8626                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8627                 s = r = StrStr(s, ":") + 1; // beginning of path info
8628                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8629                 c = *r; *r = 0;             // temporarily null-terminate path info
8630                     *--q = 0;               // strip of trailig ':' from name
8631                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8632                 *r = c;
8633                 SendToProgram(buf,cps);     // send egtbpath command for this format
8634             }
8635             if(*p == ',') p++; // read away comma to position for next format name
8636         }
8637 }
8638
8639 void
8640 InitChessProgram(cps, setup)
8641      ChessProgramState *cps;
8642      int setup; /* [HGM] needed to setup FRC opening position */
8643 {
8644     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8645     if (appData.noChessProgram) return;
8646     hintRequested = FALSE;
8647     bookRequested = FALSE;
8648
8649     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8650     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8651     if(cps->memSize) { /* [HGM] memory */
8652         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8653         SendToProgram(buf, cps);
8654     }
8655     SendEgtPath(cps); /* [HGM] EGT */
8656     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8657         sprintf(buf, "cores %d\n", appData.smpCores);
8658         SendToProgram(buf, cps);
8659     }
8660
8661     SendToProgram(cps->initString, cps);
8662     if (gameInfo.variant != VariantNormal &&
8663         gameInfo.variant != VariantLoadable
8664         /* [HGM] also send variant if board size non-standard */
8665         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8666                                             ) {
8667       char *v = VariantName(gameInfo.variant);
8668       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8669         /* [HGM] in protocol 1 we have to assume all variants valid */
8670         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8671         DisplayFatalError(buf, 0, 1);
8672         return;
8673       }
8674
8675       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8676       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8677       if( gameInfo.variant == VariantXiangqi )
8678            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8679       if( gameInfo.variant == VariantShogi )
8680            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8681       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8682            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8683       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8684                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8685            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8686       if( gameInfo.variant == VariantCourier )
8687            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8688       if( gameInfo.variant == VariantSuper )
8689            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8690       if( gameInfo.variant == VariantGreat )
8691            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8692
8693       if(overruled) {
8694            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8695                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8696            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8697            if(StrStr(cps->variants, b) == NULL) { 
8698                // specific sized variant not known, check if general sizing allowed
8699                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8700                    if(StrStr(cps->variants, "boardsize") == NULL) {
8701                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8702                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8703                        DisplayFatalError(buf, 0, 1);
8704                        return;
8705                    }
8706                    /* [HGM] here we really should compare with the maximum supported board size */
8707                }
8708            }
8709       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8710       sprintf(buf, "variant %s\n", b);
8711       SendToProgram(buf, cps);
8712     }
8713     currentlyInitializedVariant = gameInfo.variant;
8714
8715     /* [HGM] send opening position in FRC to first engine */
8716     if(setup) {
8717           SendToProgram("force\n", cps);
8718           SendBoard(cps, 0);
8719           /* engine is now in force mode! Set flag to wake it up after first move. */
8720           setboardSpoiledMachineBlack = 1;
8721     }
8722
8723     if (cps->sendICS) {
8724       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8725       SendToProgram(buf, cps);
8726     }
8727     cps->maybeThinking = FALSE;
8728     cps->offeredDraw = 0;
8729     if (!appData.icsActive) {
8730         SendTimeControl(cps, movesPerSession, timeControl,
8731                         timeIncrement, appData.searchDepth,
8732                         searchTime);
8733     }
8734     if (appData.showThinking 
8735         // [HGM] thinking: four options require thinking output to be sent
8736         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8737                                 ) {
8738         SendToProgram("post\n", cps);
8739     }
8740     SendToProgram("hard\n", cps);
8741     if (!appData.ponderNextMove) {
8742         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8743            it without being sure what state we are in first.  "hard"
8744            is not a toggle, so that one is OK.
8745          */
8746         SendToProgram("easy\n", cps);
8747     }
8748     if (cps->usePing) {
8749       sprintf(buf, "ping %d\n", ++cps->lastPing);
8750       SendToProgram(buf, cps);
8751     }
8752     cps->initDone = TRUE;
8753 }   
8754
8755
8756 void
8757 StartChessProgram(cps)
8758      ChessProgramState *cps;
8759 {
8760     char buf[MSG_SIZ];
8761     int err;
8762
8763     if (appData.noChessProgram) return;
8764     cps->initDone = FALSE;
8765
8766     if (strcmp(cps->host, "localhost") == 0) {
8767         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8768     } else if (*appData.remoteShell == NULLCHAR) {
8769         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8770     } else {
8771         if (*appData.remoteUser == NULLCHAR) {
8772           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8773                     cps->program);
8774         } else {
8775           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8776                     cps->host, appData.remoteUser, cps->program);
8777         }
8778         err = StartChildProcess(buf, "", &cps->pr);
8779     }
8780     
8781     if (err != 0) {
8782         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8783         DisplayFatalError(buf, err, 1);
8784         cps->pr = NoProc;
8785         cps->isr = NULL;
8786         return;
8787     }
8788     
8789     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8790     if (cps->protocolVersion > 1) {
8791       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8792       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8793       cps->comboCnt = 0;  //                and values of combo boxes
8794       SendToProgram(buf, cps);
8795     } else {
8796       SendToProgram("xboard\n", cps);
8797     }
8798 }
8799
8800
8801 void
8802 TwoMachinesEventIfReady P((void))
8803 {
8804   if (first.lastPing != first.lastPong) {
8805     DisplayMessage("", _("Waiting for first chess program"));
8806     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8807     return;
8808   }
8809   if (second.lastPing != second.lastPong) {
8810     DisplayMessage("", _("Waiting for second chess program"));
8811     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8812     return;
8813   }
8814   ThawUI();
8815   TwoMachinesEvent();
8816 }
8817
8818 void
8819 NextMatchGame P((void))
8820 {
8821     int index; /* [HGM] autoinc: step load index during match */
8822     Reset(FALSE, TRUE);
8823     if (*appData.loadGameFile != NULLCHAR) {
8824         index = appData.loadGameIndex;
8825         if(index < 0) { // [HGM] autoinc
8826             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8827             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8828         } 
8829         LoadGameFromFile(appData.loadGameFile,
8830                          index,
8831                          appData.loadGameFile, FALSE);
8832     } else if (*appData.loadPositionFile != NULLCHAR) {
8833         index = appData.loadPositionIndex;
8834         if(index < 0) { // [HGM] autoinc
8835             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8836             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8837         } 
8838         LoadPositionFromFile(appData.loadPositionFile,
8839                              index,
8840                              appData.loadPositionFile);
8841     }
8842     TwoMachinesEventIfReady();
8843 }
8844
8845 void UserAdjudicationEvent( int result )
8846 {
8847     ChessMove gameResult = GameIsDrawn;
8848
8849     if( result > 0 ) {
8850         gameResult = WhiteWins;
8851     }
8852     else if( result < 0 ) {
8853         gameResult = BlackWins;
8854     }
8855
8856     if( gameMode == TwoMachinesPlay ) {
8857         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8858     }
8859 }
8860
8861
8862 // [HGM] save: calculate checksum of game to make games easily identifiable
8863 int StringCheckSum(char *s)
8864 {
8865         int i = 0;
8866         if(s==NULL) return 0;
8867         while(*s) i = i*259 + *s++;
8868         return i;
8869 }
8870
8871 int GameCheckSum()
8872 {
8873         int i, sum=0;
8874         for(i=backwardMostMove; i<forwardMostMove; i++) {
8875                 sum += pvInfoList[i].depth;
8876                 sum += StringCheckSum(parseList[i]);
8877                 sum += StringCheckSum(commentList[i]);
8878                 sum *= 261;
8879         }
8880         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8881         return sum + StringCheckSum(commentList[i]);
8882 } // end of save patch
8883
8884 void
8885 GameEnds(result, resultDetails, whosays)
8886      ChessMove result;
8887      char *resultDetails;
8888      int whosays;
8889 {
8890     GameMode nextGameMode;
8891     int isIcsGame;
8892     char buf[MSG_SIZ];
8893
8894     if(endingGame) return; /* [HGM] crash: forbid recursion */
8895     endingGame = 1;
8896
8897     if (appData.debugMode) {
8898       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8899               result, resultDetails ? resultDetails : "(null)", whosays);
8900     }
8901
8902     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8903
8904     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8905         /* If we are playing on ICS, the server decides when the
8906            game is over, but the engine can offer to draw, claim 
8907            a draw, or resign. 
8908          */
8909 #if ZIPPY
8910         if (appData.zippyPlay && first.initDone) {
8911             if (result == GameIsDrawn) {
8912                 /* In case draw still needs to be claimed */
8913                 SendToICS(ics_prefix);
8914                 SendToICS("draw\n");
8915             } else if (StrCaseStr(resultDetails, "resign")) {
8916                 SendToICS(ics_prefix);
8917                 SendToICS("resign\n");
8918             }
8919         }
8920 #endif
8921         endingGame = 0; /* [HGM] crash */
8922         return;
8923     }
8924
8925     /* If we're loading the game from a file, stop */
8926     if (whosays == GE_FILE) {
8927       (void) StopLoadGameTimer();
8928       gameFileFP = NULL;
8929     }
8930
8931     /* Cancel draw offers */
8932     first.offeredDraw = second.offeredDraw = 0;
8933
8934     /* If this is an ICS game, only ICS can really say it's done;
8935        if not, anyone can. */
8936     isIcsGame = (gameMode == IcsPlayingWhite || 
8937                  gameMode == IcsPlayingBlack || 
8938                  gameMode == IcsObserving    || 
8939                  gameMode == IcsExamining);
8940
8941     if (!isIcsGame || whosays == GE_ICS) {
8942         /* OK -- not an ICS game, or ICS said it was done */
8943         StopClocks();
8944         if (!isIcsGame && !appData.noChessProgram) 
8945           SetUserThinkingEnables();
8946     
8947         /* [HGM] if a machine claims the game end we verify this claim */
8948         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8949             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8950                 char claimer;
8951                 ChessMove trueResult = (ChessMove) -1;
8952
8953                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8954                                             first.twoMachinesColor[0] :
8955                                             second.twoMachinesColor[0] ;
8956
8957                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8958                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8959                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8960                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8961                 } else
8962                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8963                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8964                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8965                 } else
8966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8967                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8968                 }
8969
8970                 // now verify win claims, but not in drop games, as we don't understand those yet
8971                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8972                                                  || gameInfo.variant == VariantGreat) &&
8973                     (result == WhiteWins && claimer == 'w' ||
8974                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8975                       if (appData.debugMode) {
8976                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8977                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8978                       }
8979                       if(result != trueResult) {
8980                               sprintf(buf, "False win claim: '%s'", resultDetails);
8981                               result = claimer == 'w' ? BlackWins : WhiteWins;
8982                               resultDetails = buf;
8983                       }
8984                 } else
8985                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8986                     && (forwardMostMove <= backwardMostMove ||
8987                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8988                         (claimer=='b')==(forwardMostMove&1))
8989                                                                                   ) {
8990                       /* [HGM] verify: draws that were not flagged are false claims */
8991                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8992                       result = claimer == 'w' ? BlackWins : WhiteWins;
8993                       resultDetails = buf;
8994                 }
8995                 /* (Claiming a loss is accepted no questions asked!) */
8996             }
8997             /* [HGM] bare: don't allow bare King to win */
8998             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8999                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9000                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9001                && result != GameIsDrawn)
9002             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9003                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9004                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9005                         if(p >= 0 && p <= (int)WhiteKing) k++;
9006                 }
9007                 if (appData.debugMode) {
9008                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9009                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9010                 }
9011                 if(k <= 1) {
9012                         result = GameIsDrawn;
9013                         sprintf(buf, "%s but bare king", resultDetails);
9014                         resultDetails = buf;
9015                 }
9016             }
9017         }
9018
9019
9020         if(serverMoves != NULL && !loadFlag) { char c = '=';
9021             if(result==WhiteWins) c = '+';
9022             if(result==BlackWins) c = '-';
9023             if(resultDetails != NULL)
9024                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9025         }
9026         if (resultDetails != NULL) {
9027             gameInfo.result = result;
9028             gameInfo.resultDetails = StrSave(resultDetails);
9029
9030             /* display last move only if game was not loaded from file */
9031             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9032                 DisplayMove(currentMove - 1);
9033     
9034             if (forwardMostMove != 0) {
9035                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9036                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9037                                                                 ) {
9038                     if (*appData.saveGameFile != NULLCHAR) {
9039                         SaveGameToFile(appData.saveGameFile, TRUE);
9040                     } else if (appData.autoSaveGames) {
9041                         AutoSaveGame();
9042                     }
9043                     if (*appData.savePositionFile != NULLCHAR) {
9044                         SavePositionToFile(appData.savePositionFile);
9045                     }
9046                 }
9047             }
9048
9049             /* Tell program how game ended in case it is learning */
9050             /* [HGM] Moved this to after saving the PGN, just in case */
9051             /* engine died and we got here through time loss. In that */
9052             /* case we will get a fatal error writing the pipe, which */
9053             /* would otherwise lose us the PGN.                       */
9054             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9055             /* output during GameEnds should never be fatal anymore   */
9056             if (gameMode == MachinePlaysWhite ||
9057                 gameMode == MachinePlaysBlack ||
9058                 gameMode == TwoMachinesPlay ||
9059                 gameMode == IcsPlayingWhite ||
9060                 gameMode == IcsPlayingBlack ||
9061                 gameMode == BeginningOfGame) {
9062                 char buf[MSG_SIZ];
9063                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9064                         resultDetails);
9065                 if (first.pr != NoProc) {
9066                     SendToProgram(buf, &first);
9067                 }
9068                 if (second.pr != NoProc &&
9069                     gameMode == TwoMachinesPlay) {
9070                     SendToProgram(buf, &second);
9071                 }
9072             }
9073         }
9074
9075         if (appData.icsActive) {
9076             if (appData.quietPlay &&
9077                 (gameMode == IcsPlayingWhite ||
9078                  gameMode == IcsPlayingBlack)) {
9079                 SendToICS(ics_prefix);
9080                 SendToICS("set shout 1\n");
9081             }
9082             nextGameMode = IcsIdle;
9083             ics_user_moved = FALSE;
9084             /* clean up premove.  It's ugly when the game has ended and the
9085              * premove highlights are still on the board.
9086              */
9087             if (gotPremove) {
9088               gotPremove = FALSE;
9089               ClearPremoveHighlights();
9090               DrawPosition(FALSE, boards[currentMove]);
9091             }
9092             if (whosays == GE_ICS) {
9093                 switch (result) {
9094                 case WhiteWins:
9095                     if (gameMode == IcsPlayingWhite)
9096                         PlayIcsWinSound();
9097                     else if(gameMode == IcsPlayingBlack)
9098                         PlayIcsLossSound();
9099                     break;
9100                 case BlackWins:
9101                     if (gameMode == IcsPlayingBlack)
9102                         PlayIcsWinSound();
9103                     else if(gameMode == IcsPlayingWhite)
9104                         PlayIcsLossSound();
9105                     break;
9106                 case GameIsDrawn:
9107                     PlayIcsDrawSound();
9108                     break;
9109                 default:
9110                     PlayIcsUnfinishedSound();
9111                 }
9112             }
9113         } else if (gameMode == EditGame ||
9114                    gameMode == PlayFromGameFile || 
9115                    gameMode == AnalyzeMode || 
9116                    gameMode == AnalyzeFile) {
9117             nextGameMode = gameMode;
9118         } else {
9119             nextGameMode = EndOfGame;
9120         }
9121         pausing = FALSE;
9122         ModeHighlight();
9123     } else {
9124         nextGameMode = gameMode;
9125     }
9126
9127     if (appData.noChessProgram) {
9128         gameMode = nextGameMode;
9129         ModeHighlight();
9130         endingGame = 0; /* [HGM] crash */
9131         return;
9132     }
9133
9134     if (first.reuse) {
9135         /* Put first chess program into idle state */
9136         if (first.pr != NoProc &&
9137             (gameMode == MachinePlaysWhite ||
9138              gameMode == MachinePlaysBlack ||
9139              gameMode == TwoMachinesPlay ||
9140              gameMode == IcsPlayingWhite ||
9141              gameMode == IcsPlayingBlack ||
9142              gameMode == BeginningOfGame)) {
9143             SendToProgram("force\n", &first);
9144             if (first.usePing) {
9145               char buf[MSG_SIZ];
9146               sprintf(buf, "ping %d\n", ++first.lastPing);
9147               SendToProgram(buf, &first);
9148             }
9149         }
9150     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9151         /* Kill off first chess program */
9152         if (first.isr != NULL)
9153           RemoveInputSource(first.isr);
9154         first.isr = NULL;
9155     
9156         if (first.pr != NoProc) {
9157             ExitAnalyzeMode();
9158             DoSleep( appData.delayBeforeQuit );
9159             SendToProgram("quit\n", &first);
9160             DoSleep( appData.delayAfterQuit );
9161             DestroyChildProcess(first.pr, first.useSigterm);
9162         }
9163         first.pr = NoProc;
9164     }
9165     if (second.reuse) {
9166         /* Put second chess program into idle state */
9167         if (second.pr != NoProc &&
9168             gameMode == TwoMachinesPlay) {
9169             SendToProgram("force\n", &second);
9170             if (second.usePing) {
9171               char buf[MSG_SIZ];
9172               sprintf(buf, "ping %d\n", ++second.lastPing);
9173               SendToProgram(buf, &second);
9174             }
9175         }
9176     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9177         /* Kill off second chess program */
9178         if (second.isr != NULL)
9179           RemoveInputSource(second.isr);
9180         second.isr = NULL;
9181     
9182         if (second.pr != NoProc) {
9183             DoSleep( appData.delayBeforeQuit );
9184             SendToProgram("quit\n", &second);
9185             DoSleep( appData.delayAfterQuit );
9186             DestroyChildProcess(second.pr, second.useSigterm);
9187         }
9188         second.pr = NoProc;
9189     }
9190
9191     if (matchMode && gameMode == TwoMachinesPlay) {
9192         switch (result) {
9193         case WhiteWins:
9194           if (first.twoMachinesColor[0] == 'w') {
9195             first.matchWins++;
9196           } else {
9197             second.matchWins++;
9198           }
9199           break;
9200         case BlackWins:
9201           if (first.twoMachinesColor[0] == 'b') {
9202             first.matchWins++;
9203           } else {
9204             second.matchWins++;
9205           }
9206           break;
9207         default:
9208           break;
9209         }
9210         if (matchGame < appData.matchGames) {
9211             char *tmp;
9212             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9213                 tmp = first.twoMachinesColor;
9214                 first.twoMachinesColor = second.twoMachinesColor;
9215                 second.twoMachinesColor = tmp;
9216             }
9217             gameMode = nextGameMode;
9218             matchGame++;
9219             if(appData.matchPause>10000 || appData.matchPause<10)
9220                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9221             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9222             endingGame = 0; /* [HGM] crash */
9223             return;
9224         } else {
9225             char buf[MSG_SIZ];
9226             gameMode = nextGameMode;
9227             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9228                     first.tidy, second.tidy,
9229                     first.matchWins, second.matchWins,
9230                     appData.matchGames - (first.matchWins + second.matchWins));
9231             DisplayFatalError(buf, 0, 0);
9232         }
9233     }
9234     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9235         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9236       ExitAnalyzeMode();
9237     gameMode = nextGameMode;
9238     ModeHighlight();
9239     endingGame = 0;  /* [HGM] crash */
9240 }
9241
9242 /* Assumes program was just initialized (initString sent).
9243    Leaves program in force mode. */
9244 void
9245 FeedMovesToProgram(cps, upto) 
9246      ChessProgramState *cps;
9247      int upto;
9248 {
9249     int i;
9250     
9251     if (appData.debugMode)
9252       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9253               startedFromSetupPosition ? "position and " : "",
9254               backwardMostMove, upto, cps->which);
9255     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9256         // [HGM] variantswitch: make engine aware of new variant
9257         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9258                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9259         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9260         SendToProgram(buf, cps);
9261         currentlyInitializedVariant = gameInfo.variant;
9262     }
9263     SendToProgram("force\n", cps);
9264     if (startedFromSetupPosition) {
9265         SendBoard(cps, backwardMostMove);
9266     if (appData.debugMode) {
9267         fprintf(debugFP, "feedMoves\n");
9268     }
9269     }
9270     for (i = backwardMostMove; i < upto; i++) {
9271         SendMoveToProgram(i, cps);
9272     }
9273 }
9274
9275
9276 void
9277 ResurrectChessProgram()
9278 {
9279      /* The chess program may have exited.
9280         If so, restart it and feed it all the moves made so far. */
9281
9282     if (appData.noChessProgram || first.pr != NoProc) return;
9283     
9284     StartChessProgram(&first);
9285     InitChessProgram(&first, FALSE);
9286     FeedMovesToProgram(&first, currentMove);
9287
9288     if (!first.sendTime) {
9289         /* can't tell gnuchess what its clock should read,
9290            so we bow to its notion. */
9291         ResetClocks();
9292         timeRemaining[0][currentMove] = whiteTimeRemaining;
9293         timeRemaining[1][currentMove] = blackTimeRemaining;
9294     }
9295
9296     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9297                 appData.icsEngineAnalyze) && first.analysisSupport) {
9298       SendToProgram("analyze\n", &first);
9299       first.analyzing = TRUE;
9300     }
9301 }
9302
9303 /*
9304  * Button procedures
9305  */
9306 void
9307 Reset(redraw, init)
9308      int redraw, init;
9309 {
9310     int i;
9311
9312     if (appData.debugMode) {
9313         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9314                 redraw, init, gameMode);
9315     }
9316     CleanupTail(); // [HGM] vari: delete any stored variations
9317     pausing = pauseExamInvalid = FALSE;
9318     startedFromSetupPosition = blackPlaysFirst = FALSE;
9319     firstMove = TRUE;
9320     whiteFlag = blackFlag = FALSE;
9321     userOfferedDraw = FALSE;
9322     hintRequested = bookRequested = FALSE;
9323     first.maybeThinking = FALSE;
9324     second.maybeThinking = FALSE;
9325     first.bookSuspend = FALSE; // [HGM] book
9326     second.bookSuspend = FALSE;
9327     thinkOutput[0] = NULLCHAR;
9328     lastHint[0] = NULLCHAR;
9329     ClearGameInfo(&gameInfo);
9330     gameInfo.variant = StringToVariant(appData.variant);
9331     ics_user_moved = ics_clock_paused = FALSE;
9332     ics_getting_history = H_FALSE;
9333     ics_gamenum = -1;
9334     white_holding[0] = black_holding[0] = NULLCHAR;
9335     ClearProgramStats();
9336     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9337     
9338     ResetFrontEnd();
9339     ClearHighlights();
9340     flipView = appData.flipView;
9341     ClearPremoveHighlights();
9342     gotPremove = FALSE;
9343     alarmSounded = FALSE;
9344
9345     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9346     if(appData.serverMovesName != NULL) {
9347         /* [HGM] prepare to make moves file for broadcasting */
9348         clock_t t = clock();
9349         if(serverMoves != NULL) fclose(serverMoves);
9350         serverMoves = fopen(appData.serverMovesName, "r");
9351         if(serverMoves != NULL) {
9352             fclose(serverMoves);
9353             /* delay 15 sec before overwriting, so all clients can see end */
9354             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9355         }
9356         serverMoves = fopen(appData.serverMovesName, "w");
9357     }
9358
9359     ExitAnalyzeMode();
9360     gameMode = BeginningOfGame;
9361     ModeHighlight();
9362     if(appData.icsActive) gameInfo.variant = VariantNormal;
9363     currentMove = forwardMostMove = backwardMostMove = 0;
9364     InitPosition(redraw);
9365     for (i = 0; i < MAX_MOVES; i++) {
9366         if (commentList[i] != NULL) {
9367             free(commentList[i]);
9368             commentList[i] = NULL;
9369         }
9370     }
9371     ResetClocks();
9372     timeRemaining[0][0] = whiteTimeRemaining;
9373     timeRemaining[1][0] = blackTimeRemaining;
9374     if (first.pr == NULL) {
9375         StartChessProgram(&first);
9376     }
9377     if (init) {
9378             InitChessProgram(&first, startedFromSetupPosition);
9379     }
9380     DisplayTitle("");
9381     DisplayMessage("", "");
9382     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9383     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9384 }
9385
9386 void
9387 AutoPlayGameLoop()
9388 {
9389     for (;;) {
9390         if (!AutoPlayOneMove())
9391           return;
9392         if (matchMode || appData.timeDelay == 0)
9393           continue;
9394         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9395           return;
9396         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9397         break;
9398     }
9399 }
9400
9401
9402 int
9403 AutoPlayOneMove()
9404 {
9405     int fromX, fromY, toX, toY;
9406
9407     if (appData.debugMode) {
9408       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9409     }
9410
9411     if (gameMode != PlayFromGameFile)
9412       return FALSE;
9413
9414     if (currentMove >= forwardMostMove) {
9415       gameMode = EditGame;
9416       ModeHighlight();
9417
9418       /* [AS] Clear current move marker at the end of a game */
9419       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9420
9421       return FALSE;
9422     }
9423     
9424     toX = moveList[currentMove][2] - AAA;
9425     toY = moveList[currentMove][3] - ONE;
9426
9427     if (moveList[currentMove][1] == '@') {
9428         if (appData.highlightLastMove) {
9429             SetHighlights(-1, -1, toX, toY);
9430         }
9431     } else {
9432         fromX = moveList[currentMove][0] - AAA;
9433         fromY = moveList[currentMove][1] - ONE;
9434
9435         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9436
9437         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9438
9439         if (appData.highlightLastMove) {
9440             SetHighlights(fromX, fromY, toX, toY);
9441         }
9442     }
9443     DisplayMove(currentMove);
9444     SendMoveToProgram(currentMove++, &first);
9445     DisplayBothClocks();
9446     DrawPosition(FALSE, boards[currentMove]);
9447     // [HGM] PV info: always display, routine tests if empty
9448     DisplayComment(currentMove - 1, commentList[currentMove]);
9449     return TRUE;
9450 }
9451
9452
9453 int
9454 LoadGameOneMove(readAhead)
9455      ChessMove readAhead;
9456 {
9457     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9458     char promoChar = NULLCHAR;
9459     ChessMove moveType;
9460     char move[MSG_SIZ];
9461     char *p, *q;
9462     
9463     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9464         gameMode != AnalyzeMode && gameMode != Training) {
9465         gameFileFP = NULL;
9466         return FALSE;
9467     }
9468     
9469     yyboardindex = forwardMostMove;
9470     if (readAhead != (ChessMove)0) {
9471       moveType = readAhead;
9472     } else {
9473       if (gameFileFP == NULL)
9474           return FALSE;
9475       moveType = (ChessMove) yylex();
9476     }
9477     
9478     done = FALSE;
9479     switch (moveType) {
9480       case Comment:
9481         if (appData.debugMode) 
9482           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9483         p = yy_text;
9484
9485         /* append the comment but don't display it */
9486         AppendComment(currentMove, p, FALSE);
9487         return TRUE;
9488
9489       case WhiteCapturesEnPassant:
9490       case BlackCapturesEnPassant:
9491       case WhitePromotionChancellor:
9492       case BlackPromotionChancellor:
9493       case WhitePromotionArchbishop:
9494       case BlackPromotionArchbishop:
9495       case WhitePromotionCentaur:
9496       case BlackPromotionCentaur:
9497       case WhitePromotionQueen:
9498       case BlackPromotionQueen:
9499       case WhitePromotionRook:
9500       case BlackPromotionRook:
9501       case WhitePromotionBishop:
9502       case BlackPromotionBishop:
9503       case WhitePromotionKnight:
9504       case BlackPromotionKnight:
9505       case WhitePromotionKing:
9506       case BlackPromotionKing:
9507       case NormalMove:
9508       case WhiteKingSideCastle:
9509       case WhiteQueenSideCastle:
9510       case BlackKingSideCastle:
9511       case BlackQueenSideCastle:
9512       case WhiteKingSideCastleWild:
9513       case WhiteQueenSideCastleWild:
9514       case BlackKingSideCastleWild:
9515       case BlackQueenSideCastleWild:
9516       /* PUSH Fabien */
9517       case WhiteHSideCastleFR:
9518       case WhiteASideCastleFR:
9519       case BlackHSideCastleFR:
9520       case BlackASideCastleFR:
9521       /* POP Fabien */
9522         if (appData.debugMode)
9523           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9524         fromX = currentMoveString[0] - AAA;
9525         fromY = currentMoveString[1] - ONE;
9526         toX = currentMoveString[2] - AAA;
9527         toY = currentMoveString[3] - ONE;
9528         promoChar = currentMoveString[4];
9529         break;
9530
9531       case WhiteDrop:
9532       case BlackDrop:
9533         if (appData.debugMode)
9534           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9535         fromX = moveType == WhiteDrop ?
9536           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9537         (int) CharToPiece(ToLower(currentMoveString[0]));
9538         fromY = DROP_RANK;
9539         toX = currentMoveString[2] - AAA;
9540         toY = currentMoveString[3] - ONE;
9541         break;
9542
9543       case WhiteWins:
9544       case BlackWins:
9545       case GameIsDrawn:
9546       case GameUnfinished:
9547         if (appData.debugMode)
9548           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9549         p = strchr(yy_text, '{');
9550         if (p == NULL) p = strchr(yy_text, '(');
9551         if (p == NULL) {
9552             p = yy_text;
9553             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9554         } else {
9555             q = strchr(p, *p == '{' ? '}' : ')');
9556             if (q != NULL) *q = NULLCHAR;
9557             p++;
9558         }
9559         GameEnds(moveType, p, GE_FILE);
9560         done = TRUE;
9561         if (cmailMsgLoaded) {
9562             ClearHighlights();
9563             flipView = WhiteOnMove(currentMove);
9564             if (moveType == GameUnfinished) flipView = !flipView;
9565             if (appData.debugMode)
9566               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9567         }
9568         break;
9569
9570       case (ChessMove) 0:       /* end of file */
9571         if (appData.debugMode)
9572           fprintf(debugFP, "Parser hit end of file\n");
9573         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9574           case MT_NONE:
9575           case MT_CHECK:
9576             break;
9577           case MT_CHECKMATE:
9578           case MT_STAINMATE:
9579             if (WhiteOnMove(currentMove)) {
9580                 GameEnds(BlackWins, "Black mates", GE_FILE);
9581             } else {
9582                 GameEnds(WhiteWins, "White mates", GE_FILE);
9583             }
9584             break;
9585           case MT_STALEMATE:
9586             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9587             break;
9588         }
9589         done = TRUE;
9590         break;
9591
9592       case MoveNumberOne:
9593         if (lastLoadGameStart == GNUChessGame) {
9594             /* GNUChessGames have numbers, but they aren't move numbers */
9595             if (appData.debugMode)
9596               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9597                       yy_text, (int) moveType);
9598             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9599         }
9600         /* else fall thru */
9601
9602       case XBoardGame:
9603       case GNUChessGame:
9604       case PGNTag:
9605         /* Reached start of next game in file */
9606         if (appData.debugMode)
9607           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9608         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9609           case MT_NONE:
9610           case MT_CHECK:
9611             break;
9612           case MT_CHECKMATE:
9613           case MT_STAINMATE:
9614             if (WhiteOnMove(currentMove)) {
9615                 GameEnds(BlackWins, "Black mates", GE_FILE);
9616             } else {
9617                 GameEnds(WhiteWins, "White mates", GE_FILE);
9618             }
9619             break;
9620           case MT_STALEMATE:
9621             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9622             break;
9623         }
9624         done = TRUE;
9625         break;
9626
9627       case PositionDiagram:     /* should not happen; ignore */
9628       case ElapsedTime:         /* ignore */
9629       case NAG:                 /* ignore */
9630         if (appData.debugMode)
9631           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9632                   yy_text, (int) moveType);
9633         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9634
9635       case IllegalMove:
9636         if (appData.testLegality) {
9637             if (appData.debugMode)
9638               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9639             sprintf(move, _("Illegal move: %d.%s%s"),
9640                     (forwardMostMove / 2) + 1,
9641                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9642             DisplayError(move, 0);
9643             done = TRUE;
9644         } else {
9645             if (appData.debugMode)
9646               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9647                       yy_text, currentMoveString);
9648             fromX = currentMoveString[0] - AAA;
9649             fromY = currentMoveString[1] - ONE;
9650             toX = currentMoveString[2] - AAA;
9651             toY = currentMoveString[3] - ONE;
9652             promoChar = currentMoveString[4];
9653         }
9654         break;
9655
9656       case AmbiguousMove:
9657         if (appData.debugMode)
9658           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9659         sprintf(move, _("Ambiguous move: %d.%s%s"),
9660                 (forwardMostMove / 2) + 1,
9661                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9662         DisplayError(move, 0);
9663         done = TRUE;
9664         break;
9665
9666       default:
9667       case ImpossibleMove:
9668         if (appData.debugMode)
9669           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9670         sprintf(move, _("Illegal move: %d.%s%s"),
9671                 (forwardMostMove / 2) + 1,
9672                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9673         DisplayError(move, 0);
9674         done = TRUE;
9675         break;
9676     }
9677
9678     if (done) {
9679         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9680             DrawPosition(FALSE, boards[currentMove]);
9681             DisplayBothClocks();
9682             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9683               DisplayComment(currentMove - 1, commentList[currentMove]);
9684         }
9685         (void) StopLoadGameTimer();
9686         gameFileFP = NULL;
9687         cmailOldMove = forwardMostMove;
9688         return FALSE;
9689     } else {
9690         /* currentMoveString is set as a side-effect of yylex */
9691         strcat(currentMoveString, "\n");
9692         strcpy(moveList[forwardMostMove], currentMoveString);
9693         
9694         thinkOutput[0] = NULLCHAR;
9695         MakeMove(fromX, fromY, toX, toY, promoChar);
9696         currentMove = forwardMostMove;
9697         return TRUE;
9698     }
9699 }
9700
9701 /* Load the nth game from the given file */
9702 int
9703 LoadGameFromFile(filename, n, title, useList)
9704      char *filename;
9705      int n;
9706      char *title;
9707      /*Boolean*/ int useList;
9708 {
9709     FILE *f;
9710     char buf[MSG_SIZ];
9711
9712     if (strcmp(filename, "-") == 0) {
9713         f = stdin;
9714         title = "stdin";
9715     } else {
9716         f = fopen(filename, "rb");
9717         if (f == NULL) {
9718           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9719             DisplayError(buf, errno);
9720             return FALSE;
9721         }
9722     }
9723     if (fseek(f, 0, 0) == -1) {
9724         /* f is not seekable; probably a pipe */
9725         useList = FALSE;
9726     }
9727     if (useList && n == 0) {
9728         int error = GameListBuild(f);
9729         if (error) {
9730             DisplayError(_("Cannot build game list"), error);
9731         } else if (!ListEmpty(&gameList) &&
9732                    ((ListGame *) gameList.tailPred)->number > 1) {
9733             GameListPopUp(f, title);
9734             return TRUE;
9735         }
9736         GameListDestroy();
9737         n = 1;
9738     }
9739     if (n == 0) n = 1;
9740     return LoadGame(f, n, title, FALSE);
9741 }
9742
9743
9744 void
9745 MakeRegisteredMove()
9746 {
9747     int fromX, fromY, toX, toY;
9748     char promoChar;
9749     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9750         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9751           case CMAIL_MOVE:
9752           case CMAIL_DRAW:
9753             if (appData.debugMode)
9754               fprintf(debugFP, "Restoring %s for game %d\n",
9755                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9756     
9757             thinkOutput[0] = NULLCHAR;
9758             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9759             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9760             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9761             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9762             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9763             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9764             MakeMove(fromX, fromY, toX, toY, promoChar);
9765             ShowMove(fromX, fromY, toX, toY);
9766               
9767             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9768               case MT_NONE:
9769               case MT_CHECK:
9770                 break;
9771                 
9772               case MT_CHECKMATE:
9773               case MT_STAINMATE:
9774                 if (WhiteOnMove(currentMove)) {
9775                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9776                 } else {
9777                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9778                 }
9779                 break;
9780                 
9781               case MT_STALEMATE:
9782                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9783                 break;
9784             }
9785
9786             break;
9787             
9788           case CMAIL_RESIGN:
9789             if (WhiteOnMove(currentMove)) {
9790                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9791             } else {
9792                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9793             }
9794             break;
9795             
9796           case CMAIL_ACCEPT:
9797             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9798             break;
9799               
9800           default:
9801             break;
9802         }
9803     }
9804
9805     return;
9806 }
9807
9808 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9809 int
9810 CmailLoadGame(f, gameNumber, title, useList)
9811      FILE *f;
9812      int gameNumber;
9813      char *title;
9814      int useList;
9815 {
9816     int retVal;
9817
9818     if (gameNumber > nCmailGames) {
9819         DisplayError(_("No more games in this message"), 0);
9820         return FALSE;
9821     }
9822     if (f == lastLoadGameFP) {
9823         int offset = gameNumber - lastLoadGameNumber;
9824         if (offset == 0) {
9825             cmailMsg[0] = NULLCHAR;
9826             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9827                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9828                 nCmailMovesRegistered--;
9829             }
9830             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9831             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9832                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9833             }
9834         } else {
9835             if (! RegisterMove()) return FALSE;
9836         }
9837     }
9838
9839     retVal = LoadGame(f, gameNumber, title, useList);
9840
9841     /* Make move registered during previous look at this game, if any */
9842     MakeRegisteredMove();
9843
9844     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9845         commentList[currentMove]
9846           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9847         DisplayComment(currentMove - 1, commentList[currentMove]);
9848     }
9849
9850     return retVal;
9851 }
9852
9853 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9854 int
9855 ReloadGame(offset)
9856      int offset;
9857 {
9858     int gameNumber = lastLoadGameNumber + offset;
9859     if (lastLoadGameFP == NULL) {
9860         DisplayError(_("No game has been loaded yet"), 0);
9861         return FALSE;
9862     }
9863     if (gameNumber <= 0) {
9864         DisplayError(_("Can't back up any further"), 0);
9865         return FALSE;
9866     }
9867     if (cmailMsgLoaded) {
9868         return CmailLoadGame(lastLoadGameFP, gameNumber,
9869                              lastLoadGameTitle, lastLoadGameUseList);
9870     } else {
9871         return LoadGame(lastLoadGameFP, gameNumber,
9872                         lastLoadGameTitle, lastLoadGameUseList);
9873     }
9874 }
9875
9876
9877
9878 /* Load the nth game from open file f */
9879 int
9880 LoadGame(f, gameNumber, title, useList)
9881      FILE *f;
9882      int gameNumber;
9883      char *title;
9884      int useList;
9885 {
9886     ChessMove cm;
9887     char buf[MSG_SIZ];
9888     int gn = gameNumber;
9889     ListGame *lg = NULL;
9890     int numPGNTags = 0;
9891     int err;
9892     GameMode oldGameMode;
9893     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9894
9895     if (appData.debugMode) 
9896         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9897
9898     if (gameMode == Training )
9899         SetTrainingModeOff();
9900
9901     oldGameMode = gameMode;
9902     if (gameMode != BeginningOfGame) {
9903       Reset(FALSE, TRUE);
9904     }
9905
9906     gameFileFP = f;
9907     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9908         fclose(lastLoadGameFP);
9909     }
9910
9911     if (useList) {
9912         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9913         
9914         if (lg) {
9915             fseek(f, lg->offset, 0);
9916             GameListHighlight(gameNumber);
9917             gn = 1;
9918         }
9919         else {
9920             DisplayError(_("Game number out of range"), 0);
9921             return FALSE;
9922         }
9923     } else {
9924         GameListDestroy();
9925         if (fseek(f, 0, 0) == -1) {
9926             if (f == lastLoadGameFP ?
9927                 gameNumber == lastLoadGameNumber + 1 :
9928                 gameNumber == 1) {
9929                 gn = 1;
9930             } else {
9931                 DisplayError(_("Can't seek on game file"), 0);
9932                 return FALSE;
9933             }
9934         }
9935     }
9936     lastLoadGameFP = f;
9937     lastLoadGameNumber = gameNumber;
9938     strcpy(lastLoadGameTitle, title);
9939     lastLoadGameUseList = useList;
9940
9941     yynewfile(f);
9942
9943     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9944       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9945                 lg->gameInfo.black);
9946             DisplayTitle(buf);
9947     } else if (*title != NULLCHAR) {
9948         if (gameNumber > 1) {
9949             sprintf(buf, "%s %d", title, gameNumber);
9950             DisplayTitle(buf);
9951         } else {
9952             DisplayTitle(title);
9953         }
9954     }
9955
9956     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9957         gameMode = PlayFromGameFile;
9958         ModeHighlight();
9959     }
9960
9961     currentMove = forwardMostMove = backwardMostMove = 0;
9962     CopyBoard(boards[0], initialPosition);
9963     StopClocks();
9964
9965     /*
9966      * Skip the first gn-1 games in the file.
9967      * Also skip over anything that precedes an identifiable 
9968      * start of game marker, to avoid being confused by 
9969      * garbage at the start of the file.  Currently 
9970      * recognized start of game markers are the move number "1",
9971      * the pattern "gnuchess .* game", the pattern
9972      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9973      * A game that starts with one of the latter two patterns
9974      * will also have a move number 1, possibly
9975      * following a position diagram.
9976      * 5-4-02: Let's try being more lenient and allowing a game to
9977      * start with an unnumbered move.  Does that break anything?
9978      */
9979     cm = lastLoadGameStart = (ChessMove) 0;
9980     while (gn > 0) {
9981         yyboardindex = forwardMostMove;
9982         cm = (ChessMove) yylex();
9983         switch (cm) {
9984           case (ChessMove) 0:
9985             if (cmailMsgLoaded) {
9986                 nCmailGames = CMAIL_MAX_GAMES - gn;
9987             } else {
9988                 Reset(TRUE, TRUE);
9989                 DisplayError(_("Game not found in file"), 0);
9990             }
9991             return FALSE;
9992
9993           case GNUChessGame:
9994           case XBoardGame:
9995             gn--;
9996             lastLoadGameStart = cm;
9997             break;
9998             
9999           case MoveNumberOne:
10000             switch (lastLoadGameStart) {
10001               case GNUChessGame:
10002               case XBoardGame:
10003               case PGNTag:
10004                 break;
10005               case MoveNumberOne:
10006               case (ChessMove) 0:
10007                 gn--;           /* count this game */
10008                 lastLoadGameStart = cm;
10009                 break;
10010               default:
10011                 /* impossible */
10012                 break;
10013             }
10014             break;
10015
10016           case PGNTag:
10017             switch (lastLoadGameStart) {
10018               case GNUChessGame:
10019               case PGNTag:
10020               case MoveNumberOne:
10021               case (ChessMove) 0:
10022                 gn--;           /* count this game */
10023                 lastLoadGameStart = cm;
10024                 break;
10025               case XBoardGame:
10026                 lastLoadGameStart = cm; /* game counted already */
10027                 break;
10028               default:
10029                 /* impossible */
10030                 break;
10031             }
10032             if (gn > 0) {
10033                 do {
10034                     yyboardindex = forwardMostMove;
10035                     cm = (ChessMove) yylex();
10036                 } while (cm == PGNTag || cm == Comment);
10037             }
10038             break;
10039
10040           case WhiteWins:
10041           case BlackWins:
10042           case GameIsDrawn:
10043             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10044                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10045                     != CMAIL_OLD_RESULT) {
10046                     nCmailResults ++ ;
10047                     cmailResult[  CMAIL_MAX_GAMES
10048                                 - gn - 1] = CMAIL_OLD_RESULT;
10049                 }
10050             }
10051             break;
10052
10053           case NormalMove:
10054             /* Only a NormalMove can be at the start of a game
10055              * without a position diagram. */
10056             if (lastLoadGameStart == (ChessMove) 0) {
10057               gn--;
10058               lastLoadGameStart = MoveNumberOne;
10059             }
10060             break;
10061
10062           default:
10063             break;
10064         }
10065     }
10066     
10067     if (appData.debugMode)
10068       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10069
10070     if (cm == XBoardGame) {
10071         /* Skip any header junk before position diagram and/or move 1 */
10072         for (;;) {
10073             yyboardindex = forwardMostMove;
10074             cm = (ChessMove) yylex();
10075
10076             if (cm == (ChessMove) 0 ||
10077                 cm == GNUChessGame || cm == XBoardGame) {
10078                 /* Empty game; pretend end-of-file and handle later */
10079                 cm = (ChessMove) 0;
10080                 break;
10081             }
10082
10083             if (cm == MoveNumberOne || cm == PositionDiagram ||
10084                 cm == PGNTag || cm == Comment)
10085               break;
10086         }
10087     } else if (cm == GNUChessGame) {
10088         if (gameInfo.event != NULL) {
10089             free(gameInfo.event);
10090         }
10091         gameInfo.event = StrSave(yy_text);
10092     }   
10093
10094     startedFromSetupPosition = FALSE;
10095     while (cm == PGNTag) {
10096         if (appData.debugMode) 
10097           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10098         err = ParsePGNTag(yy_text, &gameInfo);
10099         if (!err) numPGNTags++;
10100
10101         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10102         if(gameInfo.variant != oldVariant) {
10103             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10104             InitPosition(TRUE);
10105             oldVariant = gameInfo.variant;
10106             if (appData.debugMode) 
10107               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10108         }
10109
10110
10111         if (gameInfo.fen != NULL) {
10112           Board initial_position;
10113           startedFromSetupPosition = TRUE;
10114           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10115             Reset(TRUE, TRUE);
10116             DisplayError(_("Bad FEN position in file"), 0);
10117             return FALSE;
10118           }
10119           CopyBoard(boards[0], initial_position);
10120           if (blackPlaysFirst) {
10121             currentMove = forwardMostMove = backwardMostMove = 1;
10122             CopyBoard(boards[1], initial_position);
10123             strcpy(moveList[0], "");
10124             strcpy(parseList[0], "");
10125             timeRemaining[0][1] = whiteTimeRemaining;
10126             timeRemaining[1][1] = blackTimeRemaining;
10127             if (commentList[0] != NULL) {
10128               commentList[1] = commentList[0];
10129               commentList[0] = NULL;
10130             }
10131           } else {
10132             currentMove = forwardMostMove = backwardMostMove = 0;
10133           }
10134           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10135           {   int i;
10136               initialRulePlies = FENrulePlies;
10137               for( i=0; i< nrCastlingRights; i++ )
10138                   initialRights[i] = initial_position[CASTLING][i];
10139           }
10140           yyboardindex = forwardMostMove;
10141           free(gameInfo.fen);
10142           gameInfo.fen = NULL;
10143         }
10144
10145         yyboardindex = forwardMostMove;
10146         cm = (ChessMove) yylex();
10147
10148         /* Handle comments interspersed among the tags */
10149         while (cm == Comment) {
10150             char *p;
10151             if (appData.debugMode) 
10152               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10153             p = yy_text;
10154             AppendComment(currentMove, p, FALSE);
10155             yyboardindex = forwardMostMove;
10156             cm = (ChessMove) yylex();
10157         }
10158     }
10159
10160     /* don't rely on existence of Event tag since if game was
10161      * pasted from clipboard the Event tag may not exist
10162      */
10163     if (numPGNTags > 0){
10164         char *tags;
10165         if (gameInfo.variant == VariantNormal) {
10166           gameInfo.variant = StringToVariant(gameInfo.event);
10167         }
10168         if (!matchMode) {
10169           if( appData.autoDisplayTags ) {
10170             tags = PGNTags(&gameInfo);
10171             TagsPopUp(tags, CmailMsg());
10172             free(tags);
10173           }
10174         }
10175     } else {
10176         /* Make something up, but don't display it now */
10177         SetGameInfo();
10178         TagsPopDown();
10179     }
10180
10181     if (cm == PositionDiagram) {
10182         int i, j;
10183         char *p;
10184         Board initial_position;
10185
10186         if (appData.debugMode)
10187           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10188
10189         if (!startedFromSetupPosition) {
10190             p = yy_text;
10191             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10192               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10193                 switch (*p) {
10194                   case '[':
10195                   case '-':
10196                   case ' ':
10197                   case '\t':
10198                   case '\n':
10199                   case '\r':
10200                     break;
10201                   default:
10202                     initial_position[i][j++] = CharToPiece(*p);
10203                     break;
10204                 }
10205             while (*p == ' ' || *p == '\t' ||
10206                    *p == '\n' || *p == '\r') p++;
10207         
10208             if (strncmp(p, "black", strlen("black"))==0)
10209               blackPlaysFirst = TRUE;
10210             else
10211               blackPlaysFirst = FALSE;
10212             startedFromSetupPosition = TRUE;
10213         
10214             CopyBoard(boards[0], initial_position);
10215             if (blackPlaysFirst) {
10216                 currentMove = forwardMostMove = backwardMostMove = 1;
10217                 CopyBoard(boards[1], initial_position);
10218                 strcpy(moveList[0], "");
10219                 strcpy(parseList[0], "");
10220                 timeRemaining[0][1] = whiteTimeRemaining;
10221                 timeRemaining[1][1] = blackTimeRemaining;
10222                 if (commentList[0] != NULL) {
10223                     commentList[1] = commentList[0];
10224                     commentList[0] = NULL;
10225                 }
10226             } else {
10227                 currentMove = forwardMostMove = backwardMostMove = 0;
10228             }
10229         }
10230         yyboardindex = forwardMostMove;
10231         cm = (ChessMove) yylex();
10232     }
10233
10234     if (first.pr == NoProc) {
10235         StartChessProgram(&first);
10236     }
10237     InitChessProgram(&first, FALSE);
10238     SendToProgram("force\n", &first);
10239     if (startedFromSetupPosition) {
10240         SendBoard(&first, forwardMostMove);
10241     if (appData.debugMode) {
10242         fprintf(debugFP, "Load Game\n");
10243     }
10244         DisplayBothClocks();
10245     }      
10246
10247     /* [HGM] server: flag to write setup moves in broadcast file as one */
10248     loadFlag = appData.suppressLoadMoves;
10249
10250     while (cm == Comment) {
10251         char *p;
10252         if (appData.debugMode) 
10253           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10254         p = yy_text;
10255         AppendComment(currentMove, p, FALSE);
10256         yyboardindex = forwardMostMove;
10257         cm = (ChessMove) yylex();
10258     }
10259
10260     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10261         cm == WhiteWins || cm == BlackWins ||
10262         cm == GameIsDrawn || cm == GameUnfinished) {
10263         DisplayMessage("", _("No moves in game"));
10264         if (cmailMsgLoaded) {
10265             if (appData.debugMode)
10266               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10267             ClearHighlights();
10268             flipView = FALSE;
10269         }
10270         DrawPosition(FALSE, boards[currentMove]);
10271         DisplayBothClocks();
10272         gameMode = EditGame;
10273         ModeHighlight();
10274         gameFileFP = NULL;
10275         cmailOldMove = 0;
10276         return TRUE;
10277     }
10278
10279     // [HGM] PV info: routine tests if comment empty
10280     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10281         DisplayComment(currentMove - 1, commentList[currentMove]);
10282     }
10283     if (!matchMode && appData.timeDelay != 0) 
10284       DrawPosition(FALSE, boards[currentMove]);
10285
10286     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10287       programStats.ok_to_send = 1;
10288     }
10289
10290     /* if the first token after the PGN tags is a move
10291      * and not move number 1, retrieve it from the parser 
10292      */
10293     if (cm != MoveNumberOne)
10294         LoadGameOneMove(cm);
10295
10296     /* load the remaining moves from the file */
10297     while (LoadGameOneMove((ChessMove)0)) {
10298       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10299       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10300     }
10301
10302     /* rewind to the start of the game */
10303     currentMove = backwardMostMove;
10304
10305     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10306
10307     if (oldGameMode == AnalyzeFile ||
10308         oldGameMode == AnalyzeMode) {
10309       AnalyzeFileEvent();
10310     }
10311
10312     if (matchMode || appData.timeDelay == 0) {
10313       ToEndEvent();
10314       gameMode = EditGame;
10315       ModeHighlight();
10316     } else if (appData.timeDelay > 0) {
10317       AutoPlayGameLoop();
10318     }
10319
10320     if (appData.debugMode) 
10321         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10322
10323     loadFlag = 0; /* [HGM] true game starts */
10324     return TRUE;
10325 }
10326
10327 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10328 int
10329 ReloadPosition(offset)
10330      int offset;
10331 {
10332     int positionNumber = lastLoadPositionNumber + offset;
10333     if (lastLoadPositionFP == NULL) {
10334         DisplayError(_("No position has been loaded yet"), 0);
10335         return FALSE;
10336     }
10337     if (positionNumber <= 0) {
10338         DisplayError(_("Can't back up any further"), 0);
10339         return FALSE;
10340     }
10341     return LoadPosition(lastLoadPositionFP, positionNumber,
10342                         lastLoadPositionTitle);
10343 }
10344
10345 /* Load the nth position from the given file */
10346 int
10347 LoadPositionFromFile(filename, n, title)
10348      char *filename;
10349      int n;
10350      char *title;
10351 {
10352     FILE *f;
10353     char buf[MSG_SIZ];
10354
10355     if (strcmp(filename, "-") == 0) {
10356         return LoadPosition(stdin, n, "stdin");
10357     } else {
10358         f = fopen(filename, "rb");
10359         if (f == NULL) {
10360             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10361             DisplayError(buf, errno);
10362             return FALSE;
10363         } else {
10364             return LoadPosition(f, n, title);
10365         }
10366     }
10367 }
10368
10369 /* Load the nth position from the given open file, and close it */
10370 int
10371 LoadPosition(f, positionNumber, title)
10372      FILE *f;
10373      int positionNumber;
10374      char *title;
10375 {
10376     char *p, line[MSG_SIZ];
10377     Board initial_position;
10378     int i, j, fenMode, pn;
10379     
10380     if (gameMode == Training )
10381         SetTrainingModeOff();
10382
10383     if (gameMode != BeginningOfGame) {
10384         Reset(FALSE, TRUE);
10385     }
10386     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10387         fclose(lastLoadPositionFP);
10388     }
10389     if (positionNumber == 0) positionNumber = 1;
10390     lastLoadPositionFP = f;
10391     lastLoadPositionNumber = positionNumber;
10392     strcpy(lastLoadPositionTitle, title);
10393     if (first.pr == NoProc) {
10394       StartChessProgram(&first);
10395       InitChessProgram(&first, FALSE);
10396     }    
10397     pn = positionNumber;
10398     if (positionNumber < 0) {
10399         /* Negative position number means to seek to that byte offset */
10400         if (fseek(f, -positionNumber, 0) == -1) {
10401             DisplayError(_("Can't seek on position file"), 0);
10402             return FALSE;
10403         };
10404         pn = 1;
10405     } else {
10406         if (fseek(f, 0, 0) == -1) {
10407             if (f == lastLoadPositionFP ?
10408                 positionNumber == lastLoadPositionNumber + 1 :
10409                 positionNumber == 1) {
10410                 pn = 1;
10411             } else {
10412                 DisplayError(_("Can't seek on position file"), 0);
10413                 return FALSE;
10414             }
10415         }
10416     }
10417     /* See if this file is FEN or old-style xboard */
10418     if (fgets(line, MSG_SIZ, f) == NULL) {
10419         DisplayError(_("Position not found in file"), 0);
10420         return FALSE;
10421     }
10422     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10423     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10424
10425     if (pn >= 2) {
10426         if (fenMode || line[0] == '#') pn--;
10427         while (pn > 0) {
10428             /* skip positions before number pn */
10429             if (fgets(line, MSG_SIZ, f) == NULL) {
10430                 Reset(TRUE, TRUE);
10431                 DisplayError(_("Position not found in file"), 0);
10432                 return FALSE;
10433             }
10434             if (fenMode || line[0] == '#') pn--;
10435         }
10436     }
10437
10438     if (fenMode) {
10439         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10440             DisplayError(_("Bad FEN position in file"), 0);
10441             return FALSE;
10442         }
10443     } else {
10444         (void) fgets(line, MSG_SIZ, f);
10445         (void) fgets(line, MSG_SIZ, f);
10446     
10447         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10448             (void) fgets(line, MSG_SIZ, f);
10449             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10450                 if (*p == ' ')
10451                   continue;
10452                 initial_position[i][j++] = CharToPiece(*p);
10453             }
10454         }
10455     
10456         blackPlaysFirst = FALSE;
10457         if (!feof(f)) {
10458             (void) fgets(line, MSG_SIZ, f);
10459             if (strncmp(line, "black", strlen("black"))==0)
10460               blackPlaysFirst = TRUE;
10461         }
10462     }
10463     startedFromSetupPosition = TRUE;
10464     
10465     SendToProgram("force\n", &first);
10466     CopyBoard(boards[0], initial_position);
10467     if (blackPlaysFirst) {
10468         currentMove = forwardMostMove = backwardMostMove = 1;
10469         strcpy(moveList[0], "");
10470         strcpy(parseList[0], "");
10471         CopyBoard(boards[1], initial_position);
10472         DisplayMessage("", _("Black to play"));
10473     } else {
10474         currentMove = forwardMostMove = backwardMostMove = 0;
10475         DisplayMessage("", _("White to play"));
10476     }
10477     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10478     SendBoard(&first, forwardMostMove);
10479     if (appData.debugMode) {
10480 int i, j;
10481   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10482   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10483         fprintf(debugFP, "Load Position\n");
10484     }
10485
10486     if (positionNumber > 1) {
10487         sprintf(line, "%s %d", title, positionNumber);
10488         DisplayTitle(line);
10489     } else {
10490         DisplayTitle(title);
10491     }
10492     gameMode = EditGame;
10493     ModeHighlight();
10494     ResetClocks();
10495     timeRemaining[0][1] = whiteTimeRemaining;
10496     timeRemaining[1][1] = blackTimeRemaining;
10497     DrawPosition(FALSE, boards[currentMove]);
10498    
10499     return TRUE;
10500 }
10501
10502
10503 void
10504 CopyPlayerNameIntoFileName(dest, src)
10505      char **dest, *src;
10506 {
10507     while (*src != NULLCHAR && *src != ',') {
10508         if (*src == ' ') {
10509             *(*dest)++ = '_';
10510             src++;
10511         } else {
10512             *(*dest)++ = *src++;
10513         }
10514     }
10515 }
10516
10517 char *DefaultFileName(ext)
10518      char *ext;
10519 {
10520     static char def[MSG_SIZ];
10521     char *p;
10522
10523     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10524         p = def;
10525         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10526         *p++ = '-';
10527         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10528         *p++ = '.';
10529         strcpy(p, ext);
10530     } else {
10531         def[0] = NULLCHAR;
10532     }
10533     return def;
10534 }
10535
10536 /* Save the current game to the given file */
10537 int
10538 SaveGameToFile(filename, append)
10539      char *filename;
10540      int append;
10541 {
10542     FILE *f;
10543     char buf[MSG_SIZ];
10544
10545     if (strcmp(filename, "-") == 0) {
10546         return SaveGame(stdout, 0, NULL);
10547     } else {
10548         f = fopen(filename, append ? "a" : "w");
10549         if (f == NULL) {
10550             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10551             DisplayError(buf, errno);
10552             return FALSE;
10553         } else {
10554             return SaveGame(f, 0, NULL);
10555         }
10556     }
10557 }
10558
10559 char *
10560 SavePart(str)
10561      char *str;
10562 {
10563     static char buf[MSG_SIZ];
10564     char *p;
10565     
10566     p = strchr(str, ' ');
10567     if (p == NULL) return str;
10568     strncpy(buf, str, p - str);
10569     buf[p - str] = NULLCHAR;
10570     return buf;
10571 }
10572
10573 #define PGN_MAX_LINE 75
10574
10575 #define PGN_SIDE_WHITE  0
10576 #define PGN_SIDE_BLACK  1
10577
10578 /* [AS] */
10579 static int FindFirstMoveOutOfBook( int side )
10580 {
10581     int result = -1;
10582
10583     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10584         int index = backwardMostMove;
10585         int has_book_hit = 0;
10586
10587         if( (index % 2) != side ) {
10588             index++;
10589         }
10590
10591         while( index < forwardMostMove ) {
10592             /* Check to see if engine is in book */
10593             int depth = pvInfoList[index].depth;
10594             int score = pvInfoList[index].score;
10595             int in_book = 0;
10596
10597             if( depth <= 2 ) {
10598                 in_book = 1;
10599             }
10600             else if( score == 0 && depth == 63 ) {
10601                 in_book = 1; /* Zappa */
10602             }
10603             else if( score == 2 && depth == 99 ) {
10604                 in_book = 1; /* Abrok */
10605             }
10606
10607             has_book_hit += in_book;
10608
10609             if( ! in_book ) {
10610                 result = index;
10611
10612                 break;
10613             }
10614
10615             index += 2;
10616         }
10617     }
10618
10619     return result;
10620 }
10621
10622 /* [AS] */
10623 void GetOutOfBookInfo( char * buf )
10624 {
10625     int oob[2];
10626     int i;
10627     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10628
10629     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10630     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10631
10632     *buf = '\0';
10633
10634     if( oob[0] >= 0 || oob[1] >= 0 ) {
10635         for( i=0; i<2; i++ ) {
10636             int idx = oob[i];
10637
10638             if( idx >= 0 ) {
10639                 if( i > 0 && oob[0] >= 0 ) {
10640                     strcat( buf, "   " );
10641                 }
10642
10643                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10644                 sprintf( buf+strlen(buf), "%s%.2f", 
10645                     pvInfoList[idx].score >= 0 ? "+" : "",
10646                     pvInfoList[idx].score / 100.0 );
10647             }
10648         }
10649     }
10650 }
10651
10652 /* Save game in PGN style and close the file */
10653 int
10654 SaveGamePGN(f)
10655      FILE *f;
10656 {
10657     int i, offset, linelen, newblock;
10658     time_t tm;
10659 //    char *movetext;
10660     char numtext[32];
10661     int movelen, numlen, blank;
10662     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10663
10664     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10665     
10666     tm = time((time_t *) NULL);
10667     
10668     PrintPGNTags(f, &gameInfo);
10669     
10670     if (backwardMostMove > 0 || startedFromSetupPosition) {
10671         char *fen = PositionToFEN(backwardMostMove, NULL);
10672         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10673         fprintf(f, "\n{--------------\n");
10674         PrintPosition(f, backwardMostMove);
10675         fprintf(f, "--------------}\n");
10676         free(fen);
10677     }
10678     else {
10679         /* [AS] Out of book annotation */
10680         if( appData.saveOutOfBookInfo ) {
10681             char buf[64];
10682
10683             GetOutOfBookInfo( buf );
10684
10685             if( buf[0] != '\0' ) {
10686                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10687             }
10688         }
10689
10690         fprintf(f, "\n");
10691     }
10692
10693     i = backwardMostMove;
10694     linelen = 0;
10695     newblock = TRUE;
10696
10697     while (i < forwardMostMove) {
10698         /* Print comments preceding this move */
10699         if (commentList[i] != NULL) {
10700             if (linelen > 0) fprintf(f, "\n");
10701             fprintf(f, "%s", commentList[i]);
10702             linelen = 0;
10703             newblock = TRUE;
10704         }
10705
10706         /* Format move number */
10707         if ((i % 2) == 0) {
10708             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10709         } else {
10710             if (newblock) {
10711                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10712             } else {
10713                 numtext[0] = NULLCHAR;
10714             }
10715         }
10716         numlen = strlen(numtext);
10717         newblock = FALSE;
10718
10719         /* Print move number */
10720         blank = linelen > 0 && numlen > 0;
10721         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10722             fprintf(f, "\n");
10723             linelen = 0;
10724             blank = 0;
10725         }
10726         if (blank) {
10727             fprintf(f, " ");
10728             linelen++;
10729         }
10730         fprintf(f, "%s", numtext);
10731         linelen += numlen;
10732
10733         /* Get move */
10734         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10735         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10736
10737         /* Print move */
10738         blank = linelen > 0 && movelen > 0;
10739         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10740             fprintf(f, "\n");
10741             linelen = 0;
10742             blank = 0;
10743         }
10744         if (blank) {
10745             fprintf(f, " ");
10746             linelen++;
10747         }
10748         fprintf(f, "%s", move_buffer);
10749         linelen += movelen;
10750
10751         /* [AS] Add PV info if present */
10752         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10753             /* [HGM] add time */
10754             char buf[MSG_SIZ]; int seconds;
10755
10756             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10757
10758             if( seconds <= 0) buf[0] = 0; else
10759             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10760                 seconds = (seconds + 4)/10; // round to full seconds
10761                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10762                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10763             }
10764
10765             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10766                 pvInfoList[i].score >= 0 ? "+" : "",
10767                 pvInfoList[i].score / 100.0,
10768                 pvInfoList[i].depth,
10769                 buf );
10770
10771             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10772
10773             /* Print score/depth */
10774             blank = linelen > 0 && movelen > 0;
10775             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10776                 fprintf(f, "\n");
10777                 linelen = 0;
10778                 blank = 0;
10779             }
10780             if (blank) {
10781                 fprintf(f, " ");
10782                 linelen++;
10783             }
10784             fprintf(f, "%s", move_buffer);
10785             linelen += movelen;
10786         }
10787
10788         i++;
10789     }
10790     
10791     /* Start a new line */
10792     if (linelen > 0) fprintf(f, "\n");
10793
10794     /* Print comments after last move */
10795     if (commentList[i] != NULL) {
10796         fprintf(f, "%s\n", commentList[i]);
10797     }
10798
10799     /* Print result */
10800     if (gameInfo.resultDetails != NULL &&
10801         gameInfo.resultDetails[0] != NULLCHAR) {
10802         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10803                 PGNResult(gameInfo.result));
10804     } else {
10805         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10806     }
10807
10808     fclose(f);
10809     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10810     return TRUE;
10811 }
10812
10813 /* Save game in old style and close the file */
10814 int
10815 SaveGameOldStyle(f)
10816      FILE *f;
10817 {
10818     int i, offset;
10819     time_t tm;
10820     
10821     tm = time((time_t *) NULL);
10822     
10823     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10824     PrintOpponents(f);
10825     
10826     if (backwardMostMove > 0 || startedFromSetupPosition) {
10827         fprintf(f, "\n[--------------\n");
10828         PrintPosition(f, backwardMostMove);
10829         fprintf(f, "--------------]\n");
10830     } else {
10831         fprintf(f, "\n");
10832     }
10833
10834     i = backwardMostMove;
10835     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10836
10837     while (i < forwardMostMove) {
10838         if (commentList[i] != NULL) {
10839             fprintf(f, "[%s]\n", commentList[i]);
10840         }
10841
10842         if ((i % 2) == 1) {
10843             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10844             i++;
10845         } else {
10846             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10847             i++;
10848             if (commentList[i] != NULL) {
10849                 fprintf(f, "\n");
10850                 continue;
10851             }
10852             if (i >= forwardMostMove) {
10853                 fprintf(f, "\n");
10854                 break;
10855             }
10856             fprintf(f, "%s\n", parseList[i]);
10857             i++;
10858         }
10859     }
10860     
10861     if (commentList[i] != NULL) {
10862         fprintf(f, "[%s]\n", commentList[i]);
10863     }
10864
10865     /* This isn't really the old style, but it's close enough */
10866     if (gameInfo.resultDetails != NULL &&
10867         gameInfo.resultDetails[0] != NULLCHAR) {
10868         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10869                 gameInfo.resultDetails);
10870     } else {
10871         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10872     }
10873
10874     fclose(f);
10875     return TRUE;
10876 }
10877
10878 /* Save the current game to open file f and close the file */
10879 int
10880 SaveGame(f, dummy, dummy2)
10881      FILE *f;
10882      int dummy;
10883      char *dummy2;
10884 {
10885     if (gameMode == EditPosition) EditPositionDone(TRUE);
10886     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10887     if (appData.oldSaveStyle)
10888       return SaveGameOldStyle(f);
10889     else
10890       return SaveGamePGN(f);
10891 }
10892
10893 /* Save the current position to the given file */
10894 int
10895 SavePositionToFile(filename)
10896      char *filename;
10897 {
10898     FILE *f;
10899     char buf[MSG_SIZ];
10900
10901     if (strcmp(filename, "-") == 0) {
10902         return SavePosition(stdout, 0, NULL);
10903     } else {
10904         f = fopen(filename, "a");
10905         if (f == NULL) {
10906             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10907             DisplayError(buf, errno);
10908             return FALSE;
10909         } else {
10910             SavePosition(f, 0, NULL);
10911             return TRUE;
10912         }
10913     }
10914 }
10915
10916 /* Save the current position to the given open file and close the file */
10917 int
10918 SavePosition(f, dummy, dummy2)
10919      FILE *f;
10920      int dummy;
10921      char *dummy2;
10922 {
10923     time_t tm;
10924     char *fen;
10925     
10926     if (gameMode == EditPosition) EditPositionDone(TRUE);
10927     if (appData.oldSaveStyle) {
10928         tm = time((time_t *) NULL);
10929     
10930         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10931         PrintOpponents(f);
10932         fprintf(f, "[--------------\n");
10933         PrintPosition(f, currentMove);
10934         fprintf(f, "--------------]\n");
10935     } else {
10936         fen = PositionToFEN(currentMove, NULL);
10937         fprintf(f, "%s\n", fen);
10938         free(fen);
10939     }
10940     fclose(f);
10941     return TRUE;
10942 }
10943
10944 void
10945 ReloadCmailMsgEvent(unregister)
10946      int unregister;
10947 {
10948 #if !WIN32
10949     static char *inFilename = NULL;
10950     static char *outFilename;
10951     int i;
10952     struct stat inbuf, outbuf;
10953     int status;
10954     
10955     /* Any registered moves are unregistered if unregister is set, */
10956     /* i.e. invoked by the signal handler */
10957     if (unregister) {
10958         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10959             cmailMoveRegistered[i] = FALSE;
10960             if (cmailCommentList[i] != NULL) {
10961                 free(cmailCommentList[i]);
10962                 cmailCommentList[i] = NULL;
10963             }
10964         }
10965         nCmailMovesRegistered = 0;
10966     }
10967
10968     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10969         cmailResult[i] = CMAIL_NOT_RESULT;
10970     }
10971     nCmailResults = 0;
10972
10973     if (inFilename == NULL) {
10974         /* Because the filenames are static they only get malloced once  */
10975         /* and they never get freed                                      */
10976         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10977         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10978
10979         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10980         sprintf(outFilename, "%s.out", appData.cmailGameName);
10981     }
10982     
10983     status = stat(outFilename, &outbuf);
10984     if (status < 0) {
10985         cmailMailedMove = FALSE;
10986     } else {
10987         status = stat(inFilename, &inbuf);
10988         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10989     }
10990     
10991     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10992        counts the games, notes how each one terminated, etc.
10993        
10994        It would be nice to remove this kludge and instead gather all
10995        the information while building the game list.  (And to keep it
10996        in the game list nodes instead of having a bunch of fixed-size
10997        parallel arrays.)  Note this will require getting each game's
10998        termination from the PGN tags, as the game list builder does
10999        not process the game moves.  --mann
11000        */
11001     cmailMsgLoaded = TRUE;
11002     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11003     
11004     /* Load first game in the file or popup game menu */
11005     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11006
11007 #endif /* !WIN32 */
11008     return;
11009 }
11010
11011 int
11012 RegisterMove()
11013 {
11014     FILE *f;
11015     char string[MSG_SIZ];
11016
11017     if (   cmailMailedMove
11018         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11019         return TRUE;            /* Allow free viewing  */
11020     }
11021
11022     /* Unregister move to ensure that we don't leave RegisterMove        */
11023     /* with the move registered when the conditions for registering no   */
11024     /* longer hold                                                       */
11025     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11026         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11027         nCmailMovesRegistered --;
11028
11029         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11030           {
11031               free(cmailCommentList[lastLoadGameNumber - 1]);
11032               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11033           }
11034     }
11035
11036     if (cmailOldMove == -1) {
11037         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11038         return FALSE;
11039     }
11040
11041     if (currentMove > cmailOldMove + 1) {
11042         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11043         return FALSE;
11044     }
11045
11046     if (currentMove < cmailOldMove) {
11047         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11048         return FALSE;
11049     }
11050
11051     if (forwardMostMove > currentMove) {
11052         /* Silently truncate extra moves */
11053         TruncateGame();
11054     }
11055
11056     if (   (currentMove == cmailOldMove + 1)
11057         || (   (currentMove == cmailOldMove)
11058             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11059                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11060         if (gameInfo.result != GameUnfinished) {
11061             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11062         }
11063
11064         if (commentList[currentMove] != NULL) {
11065             cmailCommentList[lastLoadGameNumber - 1]
11066               = StrSave(commentList[currentMove]);
11067         }
11068         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11069
11070         if (appData.debugMode)
11071           fprintf(debugFP, "Saving %s for game %d\n",
11072                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11073
11074         sprintf(string,
11075                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11076         
11077         f = fopen(string, "w");
11078         if (appData.oldSaveStyle) {
11079             SaveGameOldStyle(f); /* also closes the file */
11080             
11081             sprintf(string, "%s.pos.out", appData.cmailGameName);
11082             f = fopen(string, "w");
11083             SavePosition(f, 0, NULL); /* also closes the file */
11084         } else {
11085             fprintf(f, "{--------------\n");
11086             PrintPosition(f, currentMove);
11087             fprintf(f, "--------------}\n\n");
11088             
11089             SaveGame(f, 0, NULL); /* also closes the file*/
11090         }
11091         
11092         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11093         nCmailMovesRegistered ++;
11094     } else if (nCmailGames == 1) {
11095         DisplayError(_("You have not made a move yet"), 0);
11096         return FALSE;
11097     }
11098
11099     return TRUE;
11100 }
11101
11102 void
11103 MailMoveEvent()
11104 {
11105 #if !WIN32
11106     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11107     FILE *commandOutput;
11108     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11109     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11110     int nBuffers;
11111     int i;
11112     int archived;
11113     char *arcDir;
11114
11115     if (! cmailMsgLoaded) {
11116         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11117         return;
11118     }
11119
11120     if (nCmailGames == nCmailResults) {
11121         DisplayError(_("No unfinished games"), 0);
11122         return;
11123     }
11124
11125 #if CMAIL_PROHIBIT_REMAIL
11126     if (cmailMailedMove) {
11127         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);
11128         DisplayError(msg, 0);
11129         return;
11130     }
11131 #endif
11132
11133     if (! (cmailMailedMove || RegisterMove())) return;
11134     
11135     if (   cmailMailedMove
11136         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11137         sprintf(string, partCommandString,
11138                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11139         commandOutput = popen(string, "r");
11140
11141         if (commandOutput == NULL) {
11142             DisplayError(_("Failed to invoke cmail"), 0);
11143         } else {
11144             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11145                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11146             }
11147             if (nBuffers > 1) {
11148                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11149                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11150                 nBytes = MSG_SIZ - 1;
11151             } else {
11152                 (void) memcpy(msg, buffer, nBytes);
11153             }
11154             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11155
11156             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11157                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11158
11159                 archived = TRUE;
11160                 for (i = 0; i < nCmailGames; i ++) {
11161                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11162                         archived = FALSE;
11163                     }
11164                 }
11165                 if (   archived
11166                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11167                         != NULL)) {
11168                     sprintf(buffer, "%s/%s.%s.archive",
11169                             arcDir,
11170                             appData.cmailGameName,
11171                             gameInfo.date);
11172                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11173                     cmailMsgLoaded = FALSE;
11174                 }
11175             }
11176
11177             DisplayInformation(msg);
11178             pclose(commandOutput);
11179         }
11180     } else {
11181         if ((*cmailMsg) != '\0') {
11182             DisplayInformation(cmailMsg);
11183         }
11184     }
11185
11186     return;
11187 #endif /* !WIN32 */
11188 }
11189
11190 char *
11191 CmailMsg()
11192 {
11193 #if WIN32
11194     return NULL;
11195 #else
11196     int  prependComma = 0;
11197     char number[5];
11198     char string[MSG_SIZ];       /* Space for game-list */
11199     int  i;
11200     
11201     if (!cmailMsgLoaded) return "";
11202
11203     if (cmailMailedMove) {
11204         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11205     } else {
11206         /* Create a list of games left */
11207         sprintf(string, "[");
11208         for (i = 0; i < nCmailGames; i ++) {
11209             if (! (   cmailMoveRegistered[i]
11210                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11211                 if (prependComma) {
11212                     sprintf(number, ",%d", i + 1);
11213                 } else {
11214                     sprintf(number, "%d", i + 1);
11215                     prependComma = 1;
11216                 }
11217                 
11218                 strcat(string, number);
11219             }
11220         }
11221         strcat(string, "]");
11222
11223         if (nCmailMovesRegistered + nCmailResults == 0) {
11224             switch (nCmailGames) {
11225               case 1:
11226                 sprintf(cmailMsg,
11227                         _("Still need to make move for game\n"));
11228                 break;
11229                 
11230               case 2:
11231                 sprintf(cmailMsg,
11232                         _("Still need to make moves for both games\n"));
11233                 break;
11234                 
11235               default:
11236                 sprintf(cmailMsg,
11237                         _("Still need to make moves for all %d games\n"),
11238                         nCmailGames);
11239                 break;
11240             }
11241         } else {
11242             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11243               case 1:
11244                 sprintf(cmailMsg,
11245                         _("Still need to make a move for game %s\n"),
11246                         string);
11247                 break;
11248                 
11249               case 0:
11250                 if (nCmailResults == nCmailGames) {
11251                     sprintf(cmailMsg, _("No unfinished games\n"));
11252                 } else {
11253                     sprintf(cmailMsg, _("Ready to send mail\n"));
11254                 }
11255                 break;
11256                 
11257               default:
11258                 sprintf(cmailMsg,
11259                         _("Still need to make moves for games %s\n"),
11260                         string);
11261             }
11262         }
11263     }
11264     return cmailMsg;
11265 #endif /* WIN32 */
11266 }
11267
11268 void
11269 ResetGameEvent()
11270 {
11271     if (gameMode == Training)
11272       SetTrainingModeOff();
11273
11274     Reset(TRUE, TRUE);
11275     cmailMsgLoaded = FALSE;
11276     if (appData.icsActive) {
11277       SendToICS(ics_prefix);
11278       SendToICS("refresh\n");
11279     }
11280 }
11281
11282 void
11283 ExitEvent(status)
11284      int status;
11285 {
11286     exiting++;
11287     if (exiting > 2) {
11288       /* Give up on clean exit */
11289       exit(status);
11290     }
11291     if (exiting > 1) {
11292       /* Keep trying for clean exit */
11293       return;
11294     }
11295
11296     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11297
11298     if (telnetISR != NULL) {
11299       RemoveInputSource(telnetISR);
11300     }
11301     if (icsPR != NoProc) {
11302       DestroyChildProcess(icsPR, TRUE);
11303     }
11304
11305     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11306     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11307
11308     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11309     /* make sure this other one finishes before killing it!                  */
11310     if(endingGame) { int count = 0;
11311         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11312         while(endingGame && count++ < 10) DoSleep(1);
11313         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11314     }
11315
11316     /* Kill off chess programs */
11317     if (first.pr != NoProc) {
11318         ExitAnalyzeMode();
11319         
11320         DoSleep( appData.delayBeforeQuit );
11321         SendToProgram("quit\n", &first);
11322         DoSleep( appData.delayAfterQuit );
11323         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11324     }
11325     if (second.pr != NoProc) {
11326         DoSleep( appData.delayBeforeQuit );
11327         SendToProgram("quit\n", &second);
11328         DoSleep( appData.delayAfterQuit );
11329         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11330     }
11331     if (first.isr != NULL) {
11332         RemoveInputSource(first.isr);
11333     }
11334     if (second.isr != NULL) {
11335         RemoveInputSource(second.isr);
11336     }
11337
11338     ShutDownFrontEnd();
11339     exit(status);
11340 }
11341
11342 void
11343 PauseEvent()
11344 {
11345     if (appData.debugMode)
11346         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11347     if (pausing) {
11348         pausing = FALSE;
11349         ModeHighlight();
11350         if (gameMode == MachinePlaysWhite ||
11351             gameMode == MachinePlaysBlack) {
11352             StartClocks();
11353         } else {
11354             DisplayBothClocks();
11355         }
11356         if (gameMode == PlayFromGameFile) {
11357             if (appData.timeDelay >= 0) 
11358                 AutoPlayGameLoop();
11359         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11360             Reset(FALSE, TRUE);
11361             SendToICS(ics_prefix);
11362             SendToICS("refresh\n");
11363         } else if (currentMove < forwardMostMove) {
11364             ForwardInner(forwardMostMove);
11365         }
11366         pauseExamInvalid = FALSE;
11367     } else {
11368         switch (gameMode) {
11369           default:
11370             return;
11371           case IcsExamining:
11372             pauseExamForwardMostMove = forwardMostMove;
11373             pauseExamInvalid = FALSE;
11374             /* fall through */
11375           case IcsObserving:
11376           case IcsPlayingWhite:
11377           case IcsPlayingBlack:
11378             pausing = TRUE;
11379             ModeHighlight();
11380             return;
11381           case PlayFromGameFile:
11382             (void) StopLoadGameTimer();
11383             pausing = TRUE;
11384             ModeHighlight();
11385             break;
11386           case BeginningOfGame:
11387             if (appData.icsActive) return;
11388             /* else fall through */
11389           case MachinePlaysWhite:
11390           case MachinePlaysBlack:
11391           case TwoMachinesPlay:
11392             if (forwardMostMove == 0)
11393               return;           /* don't pause if no one has moved */
11394             if ((gameMode == MachinePlaysWhite &&
11395                  !WhiteOnMove(forwardMostMove)) ||
11396                 (gameMode == MachinePlaysBlack &&
11397                  WhiteOnMove(forwardMostMove))) {
11398                 StopClocks();
11399             }
11400             pausing = TRUE;
11401             ModeHighlight();
11402             break;
11403         }
11404     }
11405 }
11406
11407 void
11408 EditCommentEvent()
11409 {
11410     char title[MSG_SIZ];
11411
11412     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11413         strcpy(title, _("Edit comment"));
11414     } else {
11415         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11416                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11417                 parseList[currentMove - 1]);
11418     }
11419
11420     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11421 }
11422
11423
11424 void
11425 EditTagsEvent()
11426 {
11427     char *tags = PGNTags(&gameInfo);
11428     EditTagsPopUp(tags);
11429     free(tags);
11430 }
11431
11432 void
11433 AnalyzeModeEvent()
11434 {
11435     if (appData.noChessProgram || gameMode == AnalyzeMode)
11436       return;
11437
11438     if (gameMode != AnalyzeFile) {
11439         if (!appData.icsEngineAnalyze) {
11440                EditGameEvent();
11441                if (gameMode != EditGame) return;
11442         }
11443         ResurrectChessProgram();
11444         SendToProgram("analyze\n", &first);
11445         first.analyzing = TRUE;
11446         /*first.maybeThinking = TRUE;*/
11447         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11448         EngineOutputPopUp();
11449     }
11450     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11451     pausing = FALSE;
11452     ModeHighlight();
11453     SetGameInfo();
11454
11455     StartAnalysisClock();
11456     GetTimeMark(&lastNodeCountTime);
11457     lastNodeCount = 0;
11458 }
11459
11460 void
11461 AnalyzeFileEvent()
11462 {
11463     if (appData.noChessProgram || gameMode == AnalyzeFile)
11464       return;
11465
11466     if (gameMode != AnalyzeMode) {
11467         EditGameEvent();
11468         if (gameMode != EditGame) return;
11469         ResurrectChessProgram();
11470         SendToProgram("analyze\n", &first);
11471         first.analyzing = TRUE;
11472         /*first.maybeThinking = TRUE;*/
11473         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11474         EngineOutputPopUp();
11475     }
11476     gameMode = AnalyzeFile;
11477     pausing = FALSE;
11478     ModeHighlight();
11479     SetGameInfo();
11480
11481     StartAnalysisClock();
11482     GetTimeMark(&lastNodeCountTime);
11483     lastNodeCount = 0;
11484 }
11485
11486 void
11487 MachineWhiteEvent()
11488 {
11489     char buf[MSG_SIZ];
11490     char *bookHit = NULL;
11491
11492     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11493       return;
11494
11495
11496     if (gameMode == PlayFromGameFile || 
11497         gameMode == TwoMachinesPlay  || 
11498         gameMode == Training         || 
11499         gameMode == AnalyzeMode      || 
11500         gameMode == EndOfGame)
11501         EditGameEvent();
11502
11503     if (gameMode == EditPosition) 
11504         EditPositionDone(TRUE);
11505
11506     if (!WhiteOnMove(currentMove)) {
11507         DisplayError(_("It is not White's turn"), 0);
11508         return;
11509     }
11510   
11511     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11512       ExitAnalyzeMode();
11513
11514     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11515         gameMode == AnalyzeFile)
11516         TruncateGame();
11517
11518     ResurrectChessProgram();    /* in case it isn't running */
11519     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11520         gameMode = MachinePlaysWhite;
11521         ResetClocks();
11522     } else
11523     gameMode = MachinePlaysWhite;
11524     pausing = FALSE;
11525     ModeHighlight();
11526     SetGameInfo();
11527     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11528     DisplayTitle(buf);
11529     if (first.sendName) {
11530       sprintf(buf, "name %s\n", gameInfo.black);
11531       SendToProgram(buf, &first);
11532     }
11533     if (first.sendTime) {
11534       if (first.useColors) {
11535         SendToProgram("black\n", &first); /*gnu kludge*/
11536       }
11537       SendTimeRemaining(&first, TRUE);
11538     }
11539     if (first.useColors) {
11540       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11541     }
11542     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11543     SetMachineThinkingEnables();
11544     first.maybeThinking = TRUE;
11545     StartClocks();
11546     firstMove = FALSE;
11547
11548     if (appData.autoFlipView && !flipView) {
11549       flipView = !flipView;
11550       DrawPosition(FALSE, NULL);
11551       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11552     }
11553
11554     if(bookHit) { // [HGM] book: simulate book reply
11555         static char bookMove[MSG_SIZ]; // a bit generous?
11556
11557         programStats.nodes = programStats.depth = programStats.time = 
11558         programStats.score = programStats.got_only_move = 0;
11559         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11560
11561         strcpy(bookMove, "move ");
11562         strcat(bookMove, bookHit);
11563         HandleMachineMove(bookMove, &first);
11564     }
11565 }
11566
11567 void
11568 MachineBlackEvent()
11569 {
11570     char buf[MSG_SIZ];
11571    char *bookHit = NULL;
11572
11573     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11574         return;
11575
11576
11577     if (gameMode == PlayFromGameFile || 
11578         gameMode == TwoMachinesPlay  || 
11579         gameMode == Training         || 
11580         gameMode == AnalyzeMode      || 
11581         gameMode == EndOfGame)
11582         EditGameEvent();
11583
11584     if (gameMode == EditPosition) 
11585         EditPositionDone(TRUE);
11586
11587     if (WhiteOnMove(currentMove)) {
11588         DisplayError(_("It is not Black's turn"), 0);
11589         return;
11590     }
11591     
11592     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11593       ExitAnalyzeMode();
11594
11595     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11596         gameMode == AnalyzeFile)
11597         TruncateGame();
11598
11599     ResurrectChessProgram();    /* in case it isn't running */
11600     gameMode = MachinePlaysBlack;
11601     pausing = FALSE;
11602     ModeHighlight();
11603     SetGameInfo();
11604     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11605     DisplayTitle(buf);
11606     if (first.sendName) {
11607       sprintf(buf, "name %s\n", gameInfo.white);
11608       SendToProgram(buf, &first);
11609     }
11610     if (first.sendTime) {
11611       if (first.useColors) {
11612         SendToProgram("white\n", &first); /*gnu kludge*/
11613       }
11614       SendTimeRemaining(&first, FALSE);
11615     }
11616     if (first.useColors) {
11617       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11618     }
11619     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11620     SetMachineThinkingEnables();
11621     first.maybeThinking = TRUE;
11622     StartClocks();
11623
11624     if (appData.autoFlipView && flipView) {
11625       flipView = !flipView;
11626       DrawPosition(FALSE, NULL);
11627       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11628     }
11629     if(bookHit) { // [HGM] book: simulate book reply
11630         static char bookMove[MSG_SIZ]; // a bit generous?
11631
11632         programStats.nodes = programStats.depth = programStats.time = 
11633         programStats.score = programStats.got_only_move = 0;
11634         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11635
11636         strcpy(bookMove, "move ");
11637         strcat(bookMove, bookHit);
11638         HandleMachineMove(bookMove, &first);
11639     }
11640 }
11641
11642
11643 void
11644 DisplayTwoMachinesTitle()
11645 {
11646     char buf[MSG_SIZ];
11647     if (appData.matchGames > 0) {
11648         if (first.twoMachinesColor[0] == 'w') {
11649             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11650                     gameInfo.white, gameInfo.black,
11651                     first.matchWins, second.matchWins,
11652                     matchGame - 1 - (first.matchWins + second.matchWins));
11653         } else {
11654             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11655                     gameInfo.white, gameInfo.black,
11656                     second.matchWins, first.matchWins,
11657                     matchGame - 1 - (first.matchWins + second.matchWins));
11658         }
11659     } else {
11660         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11661     }
11662     DisplayTitle(buf);
11663 }
11664
11665 void
11666 TwoMachinesEvent P((void))
11667 {
11668     int i;
11669     char buf[MSG_SIZ];
11670     ChessProgramState *onmove;
11671     char *bookHit = NULL;
11672     
11673     if (appData.noChessProgram) return;
11674
11675     switch (gameMode) {
11676       case TwoMachinesPlay:
11677         return;
11678       case MachinePlaysWhite:
11679       case MachinePlaysBlack:
11680         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11681             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11682             return;
11683         }
11684         /* fall through */
11685       case BeginningOfGame:
11686       case PlayFromGameFile:
11687       case EndOfGame:
11688         EditGameEvent();
11689         if (gameMode != EditGame) return;
11690         break;
11691       case EditPosition:
11692         EditPositionDone(TRUE);
11693         break;
11694       case AnalyzeMode:
11695       case AnalyzeFile:
11696         ExitAnalyzeMode();
11697         break;
11698       case EditGame:
11699       default:
11700         break;
11701     }
11702
11703 //    forwardMostMove = currentMove;
11704     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11705     ResurrectChessProgram();    /* in case first program isn't running */
11706
11707     if (second.pr == NULL) {
11708         StartChessProgram(&second);
11709         if (second.protocolVersion == 1) {
11710           TwoMachinesEventIfReady();
11711         } else {
11712           /* kludge: allow timeout for initial "feature" command */
11713           FreezeUI();
11714           DisplayMessage("", _("Starting second chess program"));
11715           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11716         }
11717         return;
11718     }
11719     DisplayMessage("", "");
11720     InitChessProgram(&second, FALSE);
11721     SendToProgram("force\n", &second);
11722     if (startedFromSetupPosition) {
11723         SendBoard(&second, backwardMostMove);
11724     if (appData.debugMode) {
11725         fprintf(debugFP, "Two Machines\n");
11726     }
11727     }
11728     for (i = backwardMostMove; i < forwardMostMove; i++) {
11729         SendMoveToProgram(i, &second);
11730     }
11731
11732     gameMode = TwoMachinesPlay;
11733     pausing = FALSE;
11734     ModeHighlight();
11735     SetGameInfo();
11736     DisplayTwoMachinesTitle();
11737     firstMove = TRUE;
11738     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11739         onmove = &first;
11740     } else {
11741         onmove = &second;
11742     }
11743
11744     SendToProgram(first.computerString, &first);
11745     if (first.sendName) {
11746       sprintf(buf, "name %s\n", second.tidy);
11747       SendToProgram(buf, &first);
11748     }
11749     SendToProgram(second.computerString, &second);
11750     if (second.sendName) {
11751       sprintf(buf, "name %s\n", first.tidy);
11752       SendToProgram(buf, &second);
11753     }
11754
11755     ResetClocks();
11756     if (!first.sendTime || !second.sendTime) {
11757         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11758         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11759     }
11760     if (onmove->sendTime) {
11761       if (onmove->useColors) {
11762         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11763       }
11764       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11765     }
11766     if (onmove->useColors) {
11767       SendToProgram(onmove->twoMachinesColor, onmove);
11768     }
11769     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11770 //    SendToProgram("go\n", onmove);
11771     onmove->maybeThinking = TRUE;
11772     SetMachineThinkingEnables();
11773
11774     StartClocks();
11775
11776     if(bookHit) { // [HGM] book: simulate book reply
11777         static char bookMove[MSG_SIZ]; // a bit generous?
11778
11779         programStats.nodes = programStats.depth = programStats.time = 
11780         programStats.score = programStats.got_only_move = 0;
11781         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11782
11783         strcpy(bookMove, "move ");
11784         strcat(bookMove, bookHit);
11785         savedMessage = bookMove; // args for deferred call
11786         savedState = onmove;
11787         ScheduleDelayedEvent(DeferredBookMove, 1);
11788     }
11789 }
11790
11791 void
11792 TrainingEvent()
11793 {
11794     if (gameMode == Training) {
11795       SetTrainingModeOff();
11796       gameMode = PlayFromGameFile;
11797       DisplayMessage("", _("Training mode off"));
11798     } else {
11799       gameMode = Training;
11800       animateTraining = appData.animate;
11801
11802       /* make sure we are not already at the end of the game */
11803       if (currentMove < forwardMostMove) {
11804         SetTrainingModeOn();
11805         DisplayMessage("", _("Training mode on"));
11806       } else {
11807         gameMode = PlayFromGameFile;
11808         DisplayError(_("Already at end of game"), 0);
11809       }
11810     }
11811     ModeHighlight();
11812 }
11813
11814 void
11815 IcsClientEvent()
11816 {
11817     if (!appData.icsActive) return;
11818     switch (gameMode) {
11819       case IcsPlayingWhite:
11820       case IcsPlayingBlack:
11821       case IcsObserving:
11822       case IcsIdle:
11823       case BeginningOfGame:
11824       case IcsExamining:
11825         return;
11826
11827       case EditGame:
11828         break;
11829
11830       case EditPosition:
11831         EditPositionDone(TRUE);
11832         break;
11833
11834       case AnalyzeMode:
11835       case AnalyzeFile:
11836         ExitAnalyzeMode();
11837         break;
11838         
11839       default:
11840         EditGameEvent();
11841         break;
11842     }
11843
11844     gameMode = IcsIdle;
11845     ModeHighlight();
11846     return;
11847 }
11848
11849
11850 void
11851 EditGameEvent()
11852 {
11853     int i;
11854
11855     switch (gameMode) {
11856       case Training:
11857         SetTrainingModeOff();
11858         break;
11859       case MachinePlaysWhite:
11860       case MachinePlaysBlack:
11861       case BeginningOfGame:
11862         SendToProgram("force\n", &first);
11863         SetUserThinkingEnables();
11864         break;
11865       case PlayFromGameFile:
11866         (void) StopLoadGameTimer();
11867         if (gameFileFP != NULL) {
11868             gameFileFP = NULL;
11869         }
11870         break;
11871       case EditPosition:
11872         EditPositionDone(TRUE);
11873         break;
11874       case AnalyzeMode:
11875       case AnalyzeFile:
11876         ExitAnalyzeMode();
11877         SendToProgram("force\n", &first);
11878         break;
11879       case TwoMachinesPlay:
11880         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11881         ResurrectChessProgram();
11882         SetUserThinkingEnables();
11883         break;
11884       case EndOfGame:
11885         ResurrectChessProgram();
11886         break;
11887       case IcsPlayingBlack:
11888       case IcsPlayingWhite:
11889         DisplayError(_("Warning: You are still playing a game"), 0);
11890         break;
11891       case IcsObserving:
11892         DisplayError(_("Warning: You are still observing a game"), 0);
11893         break;
11894       case IcsExamining:
11895         DisplayError(_("Warning: You are still examining a game"), 0);
11896         break;
11897       case IcsIdle:
11898         break;
11899       case EditGame:
11900       default:
11901         return;
11902     }
11903     
11904     pausing = FALSE;
11905     StopClocks();
11906     first.offeredDraw = second.offeredDraw = 0;
11907
11908     if (gameMode == PlayFromGameFile) {
11909         whiteTimeRemaining = timeRemaining[0][currentMove];
11910         blackTimeRemaining = timeRemaining[1][currentMove];
11911         DisplayTitle("");
11912     }
11913
11914     if (gameMode == MachinePlaysWhite ||
11915         gameMode == MachinePlaysBlack ||
11916         gameMode == TwoMachinesPlay ||
11917         gameMode == EndOfGame) {
11918         i = forwardMostMove;
11919         while (i > currentMove) {
11920             SendToProgram("undo\n", &first);
11921             i--;
11922         }
11923         whiteTimeRemaining = timeRemaining[0][currentMove];
11924         blackTimeRemaining = timeRemaining[1][currentMove];
11925         DisplayBothClocks();
11926         if (whiteFlag || blackFlag) {
11927             whiteFlag = blackFlag = 0;
11928         }
11929         DisplayTitle("");
11930     }           
11931     
11932     gameMode = EditGame;
11933     ModeHighlight();
11934     SetGameInfo();
11935 }
11936
11937
11938 void
11939 EditPositionEvent()
11940 {
11941     if (gameMode == EditPosition) {
11942         EditGameEvent();
11943         return;
11944     }
11945     
11946     EditGameEvent();
11947     if (gameMode != EditGame) return;
11948     
11949     gameMode = EditPosition;
11950     ModeHighlight();
11951     SetGameInfo();
11952     if (currentMove > 0)
11953       CopyBoard(boards[0], boards[currentMove]);
11954     
11955     blackPlaysFirst = !WhiteOnMove(currentMove);
11956     ResetClocks();
11957     currentMove = forwardMostMove = backwardMostMove = 0;
11958     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11959     DisplayMove(-1);
11960 }
11961
11962 void
11963 ExitAnalyzeMode()
11964 {
11965     /* [DM] icsEngineAnalyze - possible call from other functions */
11966     if (appData.icsEngineAnalyze) {
11967         appData.icsEngineAnalyze = FALSE;
11968
11969         DisplayMessage("",_("Close ICS engine analyze..."));
11970     }
11971     if (first.analysisSupport && first.analyzing) {
11972       SendToProgram("exit\n", &first);
11973       first.analyzing = FALSE;
11974     }
11975     thinkOutput[0] = NULLCHAR;
11976 }
11977
11978 void
11979 EditPositionDone(Boolean fakeRights)
11980 {
11981     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11982
11983     startedFromSetupPosition = TRUE;
11984     InitChessProgram(&first, FALSE);
11985     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11986       boards[0][EP_STATUS] = EP_NONE;
11987       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11988     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11989         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11990         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11991       } else boards[0][CASTLING][2] = NoRights;
11992     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11993         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11994         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11995       } else boards[0][CASTLING][5] = NoRights;
11996     }
11997     SendToProgram("force\n", &first);
11998     if (blackPlaysFirst) {
11999         strcpy(moveList[0], "");
12000         strcpy(parseList[0], "");
12001         currentMove = forwardMostMove = backwardMostMove = 1;
12002         CopyBoard(boards[1], boards[0]);
12003     } else {
12004         currentMove = forwardMostMove = backwardMostMove = 0;
12005     }
12006     SendBoard(&first, forwardMostMove);
12007     if (appData.debugMode) {
12008         fprintf(debugFP, "EditPosDone\n");
12009     }
12010     DisplayTitle("");
12011     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12012     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12013     gameMode = EditGame;
12014     ModeHighlight();
12015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12016     ClearHighlights(); /* [AS] */
12017 }
12018
12019 /* Pause for `ms' milliseconds */
12020 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12021 void
12022 TimeDelay(ms)
12023      long ms;
12024 {
12025     TimeMark m1, m2;
12026
12027     GetTimeMark(&m1);
12028     do {
12029         GetTimeMark(&m2);
12030     } while (SubtractTimeMarks(&m2, &m1) < ms);
12031 }
12032
12033 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12034 void
12035 SendMultiLineToICS(buf)
12036      char *buf;
12037 {
12038     char temp[MSG_SIZ+1], *p;
12039     int len;
12040
12041     len = strlen(buf);
12042     if (len > MSG_SIZ)
12043       len = MSG_SIZ;
12044   
12045     strncpy(temp, buf, len);
12046     temp[len] = 0;
12047
12048     p = temp;
12049     while (*p) {
12050         if (*p == '\n' || *p == '\r')
12051           *p = ' ';
12052         ++p;
12053     }
12054
12055     strcat(temp, "\n");
12056     SendToICS(temp);
12057     SendToPlayer(temp, strlen(temp));
12058 }
12059
12060 void
12061 SetWhiteToPlayEvent()
12062 {
12063     if (gameMode == EditPosition) {
12064         blackPlaysFirst = FALSE;
12065         DisplayBothClocks();    /* works because currentMove is 0 */
12066     } else if (gameMode == IcsExamining) {
12067         SendToICS(ics_prefix);
12068         SendToICS("tomove white\n");
12069     }
12070 }
12071
12072 void
12073 SetBlackToPlayEvent()
12074 {
12075     if (gameMode == EditPosition) {
12076         blackPlaysFirst = TRUE;
12077         currentMove = 1;        /* kludge */
12078         DisplayBothClocks();
12079         currentMove = 0;
12080     } else if (gameMode == IcsExamining) {
12081         SendToICS(ics_prefix);
12082         SendToICS("tomove black\n");
12083     }
12084 }
12085
12086 void
12087 EditPositionMenuEvent(selection, x, y)
12088      ChessSquare selection;
12089      int x, y;
12090 {
12091     char buf[MSG_SIZ];
12092     ChessSquare piece = boards[0][y][x];
12093
12094     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12095
12096     switch (selection) {
12097       case ClearBoard:
12098         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12099             SendToICS(ics_prefix);
12100             SendToICS("bsetup clear\n");
12101         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12102             SendToICS(ics_prefix);
12103             SendToICS("clearboard\n");
12104         } else {
12105             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12106                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12107                 for (y = 0; y < BOARD_HEIGHT; y++) {
12108                     if (gameMode == IcsExamining) {
12109                         if (boards[currentMove][y][x] != EmptySquare) {
12110                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12111                                     AAA + x, ONE + y);
12112                             SendToICS(buf);
12113                         }
12114                     } else {
12115                         boards[0][y][x] = p;
12116                     }
12117                 }
12118             }
12119         }
12120         if (gameMode == EditPosition) {
12121             DrawPosition(FALSE, boards[0]);
12122         }
12123         break;
12124
12125       case WhitePlay:
12126         SetWhiteToPlayEvent();
12127         break;
12128
12129       case BlackPlay:
12130         SetBlackToPlayEvent();
12131         break;
12132
12133       case EmptySquare:
12134         if (gameMode == IcsExamining) {
12135             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12136             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12137             SendToICS(buf);
12138         } else {
12139             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12140                 if(x == BOARD_LEFT-2) {
12141                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12142                     boards[0][y][1] = 0;
12143                 } else
12144                 if(x == BOARD_RGHT+1) {
12145                     if(y >= gameInfo.holdingsSize) break;
12146                     boards[0][y][BOARD_WIDTH-2] = 0;
12147                 } else break;
12148             }
12149             boards[0][y][x] = EmptySquare;
12150             DrawPosition(FALSE, boards[0]);
12151         }
12152         break;
12153
12154       case PromotePiece:
12155         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12156            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12157             selection = (ChessSquare) (PROMOTED piece);
12158         } else if(piece == EmptySquare) selection = WhiteSilver;
12159         else selection = (ChessSquare)((int)piece - 1);
12160         goto defaultlabel;
12161
12162       case DemotePiece:
12163         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12164            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12165             selection = (ChessSquare) (DEMOTED piece);
12166         } else if(piece == EmptySquare) selection = BlackSilver;
12167         else selection = (ChessSquare)((int)piece + 1);       
12168         goto defaultlabel;
12169
12170       case WhiteQueen:
12171       case BlackQueen:
12172         if(gameInfo.variant == VariantShatranj ||
12173            gameInfo.variant == VariantXiangqi  ||
12174            gameInfo.variant == VariantCourier  ||
12175            gameInfo.variant == VariantMakruk     )
12176             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12177         goto defaultlabel;
12178
12179       case WhiteKing:
12180       case BlackKing:
12181         if(gameInfo.variant == VariantXiangqi)
12182             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12183         if(gameInfo.variant == VariantKnightmate)
12184             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12185       default:
12186         defaultlabel:
12187         if (gameMode == IcsExamining) {
12188             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12189             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12190                     PieceToChar(selection), AAA + x, ONE + y);
12191             SendToICS(buf);
12192         } else {
12193             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12194                 int n;
12195                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12196                     n = PieceToNumber(selection - BlackPawn);
12197                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12198                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12199                     boards[0][BOARD_HEIGHT-1-n][1]++;
12200                 } else
12201                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12202                     n = PieceToNumber(selection);
12203                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12204                     boards[0][n][BOARD_WIDTH-1] = selection;
12205                     boards[0][n][BOARD_WIDTH-2]++;
12206                 }
12207             } else
12208             boards[0][y][x] = selection;
12209             DrawPosition(TRUE, boards[0]);
12210         }
12211         break;
12212     }
12213 }
12214
12215
12216 void
12217 DropMenuEvent(selection, x, y)
12218      ChessSquare selection;
12219      int x, y;
12220 {
12221     ChessMove moveType;
12222
12223     switch (gameMode) {
12224       case IcsPlayingWhite:
12225       case MachinePlaysBlack:
12226         if (!WhiteOnMove(currentMove)) {
12227             DisplayMoveError(_("It is Black's turn"));
12228             return;
12229         }
12230         moveType = WhiteDrop;
12231         break;
12232       case IcsPlayingBlack:
12233       case MachinePlaysWhite:
12234         if (WhiteOnMove(currentMove)) {
12235             DisplayMoveError(_("It is White's turn"));
12236             return;
12237         }
12238         moveType = BlackDrop;
12239         break;
12240       case EditGame:
12241         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12242         break;
12243       default:
12244         return;
12245     }
12246
12247     if (moveType == BlackDrop && selection < BlackPawn) {
12248       selection = (ChessSquare) ((int) selection
12249                                  + (int) BlackPawn - (int) WhitePawn);
12250     }
12251     if (boards[currentMove][y][x] != EmptySquare) {
12252         DisplayMoveError(_("That square is occupied"));
12253         return;
12254     }
12255
12256     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12257 }
12258
12259 void
12260 AcceptEvent()
12261 {
12262     /* Accept a pending offer of any kind from opponent */
12263     
12264     if (appData.icsActive) {
12265         SendToICS(ics_prefix);
12266         SendToICS("accept\n");
12267     } else if (cmailMsgLoaded) {
12268         if (currentMove == cmailOldMove &&
12269             commentList[cmailOldMove] != NULL &&
12270             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12271                    "Black offers a draw" : "White offers a draw")) {
12272             TruncateGame();
12273             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12274             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12275         } else {
12276             DisplayError(_("There is no pending offer on this move"), 0);
12277             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12278         }
12279     } else {
12280         /* Not used for offers from chess program */
12281     }
12282 }
12283
12284 void
12285 DeclineEvent()
12286 {
12287     /* Decline a pending offer of any kind from opponent */
12288     
12289     if (appData.icsActive) {
12290         SendToICS(ics_prefix);
12291         SendToICS("decline\n");
12292     } else if (cmailMsgLoaded) {
12293         if (currentMove == cmailOldMove &&
12294             commentList[cmailOldMove] != NULL &&
12295             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12296                    "Black offers a draw" : "White offers a draw")) {
12297 #ifdef NOTDEF
12298             AppendComment(cmailOldMove, "Draw declined", TRUE);
12299             DisplayComment(cmailOldMove - 1, "Draw declined");
12300 #endif /*NOTDEF*/
12301         } else {
12302             DisplayError(_("There is no pending offer on this move"), 0);
12303         }
12304     } else {
12305         /* Not used for offers from chess program */
12306     }
12307 }
12308
12309 void
12310 RematchEvent()
12311 {
12312     /* Issue ICS rematch command */
12313     if (appData.icsActive) {
12314         SendToICS(ics_prefix);
12315         SendToICS("rematch\n");
12316     }
12317 }
12318
12319 void
12320 CallFlagEvent()
12321 {
12322     /* Call your opponent's flag (claim a win on time) */
12323     if (appData.icsActive) {
12324         SendToICS(ics_prefix);
12325         SendToICS("flag\n");
12326     } else {
12327         switch (gameMode) {
12328           default:
12329             return;
12330           case MachinePlaysWhite:
12331             if (whiteFlag) {
12332                 if (blackFlag)
12333                   GameEnds(GameIsDrawn, "Both players ran out of time",
12334                            GE_PLAYER);
12335                 else
12336                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12337             } else {
12338                 DisplayError(_("Your opponent is not out of time"), 0);
12339             }
12340             break;
12341           case MachinePlaysBlack:
12342             if (blackFlag) {
12343                 if (whiteFlag)
12344                   GameEnds(GameIsDrawn, "Both players ran out of time",
12345                            GE_PLAYER);
12346                 else
12347                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12348             } else {
12349                 DisplayError(_("Your opponent is not out of time"), 0);
12350             }
12351             break;
12352         }
12353     }
12354 }
12355
12356 void
12357 DrawEvent()
12358 {
12359     /* Offer draw or accept pending draw offer from opponent */
12360     
12361     if (appData.icsActive) {
12362         /* Note: tournament rules require draw offers to be
12363            made after you make your move but before you punch
12364            your clock.  Currently ICS doesn't let you do that;
12365            instead, you immediately punch your clock after making
12366            a move, but you can offer a draw at any time. */
12367         
12368         SendToICS(ics_prefix);
12369         SendToICS("draw\n");
12370         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12371     } else if (cmailMsgLoaded) {
12372         if (currentMove == cmailOldMove &&
12373             commentList[cmailOldMove] != NULL &&
12374             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12375                    "Black offers a draw" : "White offers a draw")) {
12376             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12377             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12378         } else if (currentMove == cmailOldMove + 1) {
12379             char *offer = WhiteOnMove(cmailOldMove) ?
12380               "White offers a draw" : "Black offers a draw";
12381             AppendComment(currentMove, offer, TRUE);
12382             DisplayComment(currentMove - 1, offer);
12383             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12384         } else {
12385             DisplayError(_("You must make your move before offering a draw"), 0);
12386             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12387         }
12388     } else if (first.offeredDraw) {
12389         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12390     } else {
12391         if (first.sendDrawOffers) {
12392             SendToProgram("draw\n", &first);
12393             userOfferedDraw = TRUE;
12394         }
12395     }
12396 }
12397
12398 void
12399 AdjournEvent()
12400 {
12401     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12402     
12403     if (appData.icsActive) {
12404         SendToICS(ics_prefix);
12405         SendToICS("adjourn\n");
12406     } else {
12407         /* Currently GNU Chess doesn't offer or accept Adjourns */
12408     }
12409 }
12410
12411
12412 void
12413 AbortEvent()
12414 {
12415     /* Offer Abort or accept pending Abort offer from opponent */
12416     
12417     if (appData.icsActive) {
12418         SendToICS(ics_prefix);
12419         SendToICS("abort\n");
12420     } else {
12421         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12422     }
12423 }
12424
12425 void
12426 ResignEvent()
12427 {
12428     /* Resign.  You can do this even if it's not your turn. */
12429     
12430     if (appData.icsActive) {
12431         SendToICS(ics_prefix);
12432         SendToICS("resign\n");
12433     } else {
12434         switch (gameMode) {
12435           case MachinePlaysWhite:
12436             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12437             break;
12438           case MachinePlaysBlack:
12439             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12440             break;
12441           case EditGame:
12442             if (cmailMsgLoaded) {
12443                 TruncateGame();
12444                 if (WhiteOnMove(cmailOldMove)) {
12445                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12446                 } else {
12447                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12448                 }
12449                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12450             }
12451             break;
12452           default:
12453             break;
12454         }
12455     }
12456 }
12457
12458
12459 void
12460 StopObservingEvent()
12461 {
12462     /* Stop observing current games */
12463     SendToICS(ics_prefix);
12464     SendToICS("unobserve\n");
12465 }
12466
12467 void
12468 StopExaminingEvent()
12469 {
12470     /* Stop observing current game */
12471     SendToICS(ics_prefix);
12472     SendToICS("unexamine\n");
12473 }
12474
12475 void
12476 ForwardInner(target)
12477      int target;
12478 {
12479     int limit;
12480
12481     if (appData.debugMode)
12482         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12483                 target, currentMove, forwardMostMove);
12484
12485     if (gameMode == EditPosition)
12486       return;
12487
12488     if (gameMode == PlayFromGameFile && !pausing)
12489       PauseEvent();
12490     
12491     if (gameMode == IcsExamining && pausing)
12492       limit = pauseExamForwardMostMove;
12493     else
12494       limit = forwardMostMove;
12495     
12496     if (target > limit) target = limit;
12497
12498     if (target > 0 && moveList[target - 1][0]) {
12499         int fromX, fromY, toX, toY;
12500         toX = moveList[target - 1][2] - AAA;
12501         toY = moveList[target - 1][3] - ONE;
12502         if (moveList[target - 1][1] == '@') {
12503             if (appData.highlightLastMove) {
12504                 SetHighlights(-1, -1, toX, toY);
12505             }
12506         } else {
12507             fromX = moveList[target - 1][0] - AAA;
12508             fromY = moveList[target - 1][1] - ONE;
12509             if (target == currentMove + 1) {
12510                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12511             }
12512             if (appData.highlightLastMove) {
12513                 SetHighlights(fromX, fromY, toX, toY);
12514             }
12515         }
12516     }
12517     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12518         gameMode == Training || gameMode == PlayFromGameFile || 
12519         gameMode == AnalyzeFile) {
12520         while (currentMove < target) {
12521             SendMoveToProgram(currentMove++, &first);
12522         }
12523     } else {
12524         currentMove = target;
12525     }
12526     
12527     if (gameMode == EditGame || gameMode == EndOfGame) {
12528         whiteTimeRemaining = timeRemaining[0][currentMove];
12529         blackTimeRemaining = timeRemaining[1][currentMove];
12530     }
12531     DisplayBothClocks();
12532     DisplayMove(currentMove - 1);
12533     DrawPosition(FALSE, boards[currentMove]);
12534     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12535     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12536         DisplayComment(currentMove - 1, commentList[currentMove]);
12537     }
12538 }
12539
12540
12541 void
12542 ForwardEvent()
12543 {
12544     if (gameMode == IcsExamining && !pausing) {
12545         SendToICS(ics_prefix);
12546         SendToICS("forward\n");
12547     } else {
12548         ForwardInner(currentMove + 1);
12549     }
12550 }
12551
12552 void
12553 ToEndEvent()
12554 {
12555     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12556         /* to optimze, we temporarily turn off analysis mode while we feed
12557          * the remaining moves to the engine. Otherwise we get analysis output
12558          * after each move.
12559          */ 
12560         if (first.analysisSupport) {
12561           SendToProgram("exit\nforce\n", &first);
12562           first.analyzing = FALSE;
12563         }
12564     }
12565         
12566     if (gameMode == IcsExamining && !pausing) {
12567         SendToICS(ics_prefix);
12568         SendToICS("forward 999999\n");
12569     } else {
12570         ForwardInner(forwardMostMove);
12571     }
12572
12573     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12574         /* we have fed all the moves, so reactivate analysis mode */
12575         SendToProgram("analyze\n", &first);
12576         first.analyzing = TRUE;
12577         /*first.maybeThinking = TRUE;*/
12578         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12579     }
12580 }
12581
12582 void
12583 BackwardInner(target)
12584      int target;
12585 {
12586     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12587
12588     if (appData.debugMode)
12589         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12590                 target, currentMove, forwardMostMove);
12591
12592     if (gameMode == EditPosition) return;
12593     if (currentMove <= backwardMostMove) {
12594         ClearHighlights();
12595         DrawPosition(full_redraw, boards[currentMove]);
12596         return;
12597     }
12598     if (gameMode == PlayFromGameFile && !pausing)
12599       PauseEvent();
12600     
12601     if (moveList[target][0]) {
12602         int fromX, fromY, toX, toY;
12603         toX = moveList[target][2] - AAA;
12604         toY = moveList[target][3] - ONE;
12605         if (moveList[target][1] == '@') {
12606             if (appData.highlightLastMove) {
12607                 SetHighlights(-1, -1, toX, toY);
12608             }
12609         } else {
12610             fromX = moveList[target][0] - AAA;
12611             fromY = moveList[target][1] - ONE;
12612             if (target == currentMove - 1) {
12613                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12614             }
12615             if (appData.highlightLastMove) {
12616                 SetHighlights(fromX, fromY, toX, toY);
12617             }
12618         }
12619     }
12620     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12621         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12622         while (currentMove > target) {
12623             SendToProgram("undo\n", &first);
12624             currentMove--;
12625         }
12626     } else {
12627         currentMove = target;
12628     }
12629     
12630     if (gameMode == EditGame || gameMode == EndOfGame) {
12631         whiteTimeRemaining = timeRemaining[0][currentMove];
12632         blackTimeRemaining = timeRemaining[1][currentMove];
12633     }
12634     DisplayBothClocks();
12635     DisplayMove(currentMove - 1);
12636     DrawPosition(full_redraw, boards[currentMove]);
12637     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12638     // [HGM] PV info: routine tests if comment empty
12639     DisplayComment(currentMove - 1, commentList[currentMove]);
12640 }
12641
12642 void
12643 BackwardEvent()
12644 {
12645     if (gameMode == IcsExamining && !pausing) {
12646         SendToICS(ics_prefix);
12647         SendToICS("backward\n");
12648     } else {
12649         BackwardInner(currentMove - 1);
12650     }
12651 }
12652
12653 void
12654 ToStartEvent()
12655 {
12656     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12657         /* to optimize, we temporarily turn off analysis mode while we undo
12658          * all the moves. Otherwise we get analysis output after each undo.
12659          */ 
12660         if (first.analysisSupport) {
12661           SendToProgram("exit\nforce\n", &first);
12662           first.analyzing = FALSE;
12663         }
12664     }
12665
12666     if (gameMode == IcsExamining && !pausing) {
12667         SendToICS(ics_prefix);
12668         SendToICS("backward 999999\n");
12669     } else {
12670         BackwardInner(backwardMostMove);
12671     }
12672
12673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12674         /* we have fed all the moves, so reactivate analysis mode */
12675         SendToProgram("analyze\n", &first);
12676         first.analyzing = TRUE;
12677         /*first.maybeThinking = TRUE;*/
12678         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12679     }
12680 }
12681
12682 void
12683 ToNrEvent(int to)
12684 {
12685   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12686   if (to >= forwardMostMove) to = forwardMostMove;
12687   if (to <= backwardMostMove) to = backwardMostMove;
12688   if (to < currentMove) {
12689     BackwardInner(to);
12690   } else {
12691     ForwardInner(to);
12692   }
12693 }
12694
12695 void
12696 RevertEvent(Boolean annotate)
12697 {
12698     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12699         return;
12700     }
12701     if (gameMode != IcsExamining) {
12702         DisplayError(_("You are not examining a game"), 0);
12703         return;
12704     }
12705     if (pausing) {
12706         DisplayError(_("You can't revert while pausing"), 0);
12707         return;
12708     }
12709     SendToICS(ics_prefix);
12710     SendToICS("revert\n");
12711 }
12712
12713 void
12714 RetractMoveEvent()
12715 {
12716     switch (gameMode) {
12717       case MachinePlaysWhite:
12718       case MachinePlaysBlack:
12719         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12720             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12721             return;
12722         }
12723         if (forwardMostMove < 2) return;
12724         currentMove = forwardMostMove = forwardMostMove - 2;
12725         whiteTimeRemaining = timeRemaining[0][currentMove];
12726         blackTimeRemaining = timeRemaining[1][currentMove];
12727         DisplayBothClocks();
12728         DisplayMove(currentMove - 1);
12729         ClearHighlights();/*!! could figure this out*/
12730         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12731         SendToProgram("remove\n", &first);
12732         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12733         break;
12734
12735       case BeginningOfGame:
12736       default:
12737         break;
12738
12739       case IcsPlayingWhite:
12740       case IcsPlayingBlack:
12741         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12742             SendToICS(ics_prefix);
12743             SendToICS("takeback 2\n");
12744         } else {
12745             SendToICS(ics_prefix);
12746             SendToICS("takeback 1\n");
12747         }
12748         break;
12749     }
12750 }
12751
12752 void
12753 MoveNowEvent()
12754 {
12755     ChessProgramState *cps;
12756
12757     switch (gameMode) {
12758       case MachinePlaysWhite:
12759         if (!WhiteOnMove(forwardMostMove)) {
12760             DisplayError(_("It is your turn"), 0);
12761             return;
12762         }
12763         cps = &first;
12764         break;
12765       case MachinePlaysBlack:
12766         if (WhiteOnMove(forwardMostMove)) {
12767             DisplayError(_("It is your turn"), 0);
12768             return;
12769         }
12770         cps = &first;
12771         break;
12772       case TwoMachinesPlay:
12773         if (WhiteOnMove(forwardMostMove) ==
12774             (first.twoMachinesColor[0] == 'w')) {
12775             cps = &first;
12776         } else {
12777             cps = &second;
12778         }
12779         break;
12780       case BeginningOfGame:
12781       default:
12782         return;
12783     }
12784     SendToProgram("?\n", cps);
12785 }
12786
12787 void
12788 TruncateGameEvent()
12789 {
12790     EditGameEvent();
12791     if (gameMode != EditGame) return;
12792     TruncateGame();
12793 }
12794
12795 void
12796 TruncateGame()
12797 {
12798     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12799     if (forwardMostMove > currentMove) {
12800         if (gameInfo.resultDetails != NULL) {
12801             free(gameInfo.resultDetails);
12802             gameInfo.resultDetails = NULL;
12803             gameInfo.result = GameUnfinished;
12804         }
12805         forwardMostMove = currentMove;
12806         HistorySet(parseList, backwardMostMove, forwardMostMove,
12807                    currentMove-1);
12808     }
12809 }
12810
12811 void
12812 HintEvent()
12813 {
12814     if (appData.noChessProgram) return;
12815     switch (gameMode) {
12816       case MachinePlaysWhite:
12817         if (WhiteOnMove(forwardMostMove)) {
12818             DisplayError(_("Wait until your turn"), 0);
12819             return;
12820         }
12821         break;
12822       case BeginningOfGame:
12823       case MachinePlaysBlack:
12824         if (!WhiteOnMove(forwardMostMove)) {
12825             DisplayError(_("Wait until your turn"), 0);
12826             return;
12827         }
12828         break;
12829       default:
12830         DisplayError(_("No hint available"), 0);
12831         return;
12832     }
12833     SendToProgram("hint\n", &first);
12834     hintRequested = TRUE;
12835 }
12836
12837 void
12838 BookEvent()
12839 {
12840     if (appData.noChessProgram) return;
12841     switch (gameMode) {
12842       case MachinePlaysWhite:
12843         if (WhiteOnMove(forwardMostMove)) {
12844             DisplayError(_("Wait until your turn"), 0);
12845             return;
12846         }
12847         break;
12848       case BeginningOfGame:
12849       case MachinePlaysBlack:
12850         if (!WhiteOnMove(forwardMostMove)) {
12851             DisplayError(_("Wait until your turn"), 0);
12852             return;
12853         }
12854         break;
12855       case EditPosition:
12856         EditPositionDone(TRUE);
12857         break;
12858       case TwoMachinesPlay:
12859         return;
12860       default:
12861         break;
12862     }
12863     SendToProgram("bk\n", &first);
12864     bookOutput[0] = NULLCHAR;
12865     bookRequested = TRUE;
12866 }
12867
12868 void
12869 AboutGameEvent()
12870 {
12871     char *tags = PGNTags(&gameInfo);
12872     TagsPopUp(tags, CmailMsg());
12873     free(tags);
12874 }
12875
12876 /* end button procedures */
12877
12878 void
12879 PrintPosition(fp, move)
12880      FILE *fp;
12881      int move;
12882 {
12883     int i, j;
12884     
12885     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12886         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12887             char c = PieceToChar(boards[move][i][j]);
12888             fputc(c == 'x' ? '.' : c, fp);
12889             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12890         }
12891     }
12892     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12893       fprintf(fp, "white to play\n");
12894     else
12895       fprintf(fp, "black to play\n");
12896 }
12897
12898 void
12899 PrintOpponents(fp)
12900      FILE *fp;
12901 {
12902     if (gameInfo.white != NULL) {
12903         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12904     } else {
12905         fprintf(fp, "\n");
12906     }
12907 }
12908
12909 /* Find last component of program's own name, using some heuristics */
12910 void
12911 TidyProgramName(prog, host, buf)
12912      char *prog, *host, buf[MSG_SIZ];
12913 {
12914     char *p, *q;
12915     int local = (strcmp(host, "localhost") == 0);
12916     while (!local && (p = strchr(prog, ';')) != NULL) {
12917         p++;
12918         while (*p == ' ') p++;
12919         prog = p;
12920     }
12921     if (*prog == '"' || *prog == '\'') {
12922         q = strchr(prog + 1, *prog);
12923     } else {
12924         q = strchr(prog, ' ');
12925     }
12926     if (q == NULL) q = prog + strlen(prog);
12927     p = q;
12928     while (p >= prog && *p != '/' && *p != '\\') p--;
12929     p++;
12930     if(p == prog && *p == '"') p++;
12931     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12932     memcpy(buf, p, q - p);
12933     buf[q - p] = NULLCHAR;
12934     if (!local) {
12935         strcat(buf, "@");
12936         strcat(buf, host);
12937     }
12938 }
12939
12940 char *
12941 TimeControlTagValue()
12942 {
12943     char buf[MSG_SIZ];
12944     if (!appData.clockMode) {
12945         strcpy(buf, "-");
12946     } else if (movesPerSession > 0) {
12947         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12948     } else if (timeIncrement == 0) {
12949         sprintf(buf, "%ld", timeControl/1000);
12950     } else {
12951         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12952     }
12953     return StrSave(buf);
12954 }
12955
12956 void
12957 SetGameInfo()
12958 {
12959     /* This routine is used only for certain modes */
12960     VariantClass v = gameInfo.variant;
12961     ChessMove r = GameUnfinished;
12962     char *p = NULL;
12963
12964     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12965         r = gameInfo.result; 
12966         p = gameInfo.resultDetails; 
12967         gameInfo.resultDetails = NULL;
12968     }
12969     ClearGameInfo(&gameInfo);
12970     gameInfo.variant = v;
12971
12972     switch (gameMode) {
12973       case MachinePlaysWhite:
12974         gameInfo.event = StrSave( appData.pgnEventHeader );
12975         gameInfo.site = StrSave(HostName());
12976         gameInfo.date = PGNDate();
12977         gameInfo.round = StrSave("-");
12978         gameInfo.white = StrSave(first.tidy);
12979         gameInfo.black = StrSave(UserName());
12980         gameInfo.timeControl = TimeControlTagValue();
12981         break;
12982
12983       case MachinePlaysBlack:
12984         gameInfo.event = StrSave( appData.pgnEventHeader );
12985         gameInfo.site = StrSave(HostName());
12986         gameInfo.date = PGNDate();
12987         gameInfo.round = StrSave("-");
12988         gameInfo.white = StrSave(UserName());
12989         gameInfo.black = StrSave(first.tidy);
12990         gameInfo.timeControl = TimeControlTagValue();
12991         break;
12992
12993       case TwoMachinesPlay:
12994         gameInfo.event = StrSave( appData.pgnEventHeader );
12995         gameInfo.site = StrSave(HostName());
12996         gameInfo.date = PGNDate();
12997         if (matchGame > 0) {
12998             char buf[MSG_SIZ];
12999             sprintf(buf, "%d", matchGame);
13000             gameInfo.round = StrSave(buf);
13001         } else {
13002             gameInfo.round = StrSave("-");
13003         }
13004         if (first.twoMachinesColor[0] == 'w') {
13005             gameInfo.white = StrSave(first.tidy);
13006             gameInfo.black = StrSave(second.tidy);
13007         } else {
13008             gameInfo.white = StrSave(second.tidy);
13009             gameInfo.black = StrSave(first.tidy);
13010         }
13011         gameInfo.timeControl = TimeControlTagValue();
13012         break;
13013
13014       case EditGame:
13015         gameInfo.event = StrSave("Edited game");
13016         gameInfo.site = StrSave(HostName());
13017         gameInfo.date = PGNDate();
13018         gameInfo.round = StrSave("-");
13019         gameInfo.white = StrSave("-");
13020         gameInfo.black = StrSave("-");
13021         gameInfo.result = r;
13022         gameInfo.resultDetails = p;
13023         break;
13024
13025       case EditPosition:
13026         gameInfo.event = StrSave("Edited position");
13027         gameInfo.site = StrSave(HostName());
13028         gameInfo.date = PGNDate();
13029         gameInfo.round = StrSave("-");
13030         gameInfo.white = StrSave("-");
13031         gameInfo.black = StrSave("-");
13032         break;
13033
13034       case IcsPlayingWhite:
13035       case IcsPlayingBlack:
13036       case IcsObserving:
13037       case IcsExamining:
13038         break;
13039
13040       case PlayFromGameFile:
13041         gameInfo.event = StrSave("Game from non-PGN file");
13042         gameInfo.site = StrSave(HostName());
13043         gameInfo.date = PGNDate();
13044         gameInfo.round = StrSave("-");
13045         gameInfo.white = StrSave("?");
13046         gameInfo.black = StrSave("?");
13047         break;
13048
13049       default:
13050         break;
13051     }
13052 }
13053
13054 void
13055 ReplaceComment(index, text)
13056      int index;
13057      char *text;
13058 {
13059     int len;
13060
13061     while (*text == '\n') text++;
13062     len = strlen(text);
13063     while (len > 0 && text[len - 1] == '\n') len--;
13064
13065     if (commentList[index] != NULL)
13066       free(commentList[index]);
13067
13068     if (len == 0) {
13069         commentList[index] = NULL;
13070         return;
13071     }
13072   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13073       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13074       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13075     commentList[index] = (char *) malloc(len + 2);
13076     strncpy(commentList[index], text, len);
13077     commentList[index][len] = '\n';
13078     commentList[index][len + 1] = NULLCHAR;
13079   } else { 
13080     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13081     char *p;
13082     commentList[index] = (char *) malloc(len + 6);
13083     strcpy(commentList[index], "{\n");
13084     strncpy(commentList[index]+2, text, len);
13085     commentList[index][len+2] = NULLCHAR;
13086     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13087     strcat(commentList[index], "\n}\n");
13088   }
13089 }
13090
13091 void
13092 CrushCRs(text)
13093      char *text;
13094 {
13095   char *p = text;
13096   char *q = text;
13097   char ch;
13098
13099   do {
13100     ch = *p++;
13101     if (ch == '\r') continue;
13102     *q++ = ch;
13103   } while (ch != '\0');
13104 }
13105
13106 void
13107 AppendComment(index, text, addBraces)
13108      int index;
13109      char *text;
13110      Boolean addBraces; // [HGM] braces: tells if we should add {}
13111 {
13112     int oldlen, len;
13113     char *old;
13114
13115 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13116     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13117
13118     CrushCRs(text);
13119     while (*text == '\n') text++;
13120     len = strlen(text);
13121     while (len > 0 && text[len - 1] == '\n') len--;
13122
13123     if (len == 0) return;
13124
13125     if (commentList[index] != NULL) {
13126         old = commentList[index];
13127         oldlen = strlen(old);
13128         while(commentList[index][oldlen-1] ==  '\n')
13129           commentList[index][--oldlen] = NULLCHAR;
13130         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13131         strcpy(commentList[index], old);
13132         free(old);
13133         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13134         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13135           if(addBraces) addBraces = FALSE; else { text++; len--; }
13136           while (*text == '\n') { text++; len--; }
13137           commentList[index][--oldlen] = NULLCHAR;
13138       }
13139         if(addBraces) strcat(commentList[index], "\n{\n");
13140         else          strcat(commentList[index], "\n");
13141         strcat(commentList[index], text);
13142         if(addBraces) strcat(commentList[index], "\n}\n");
13143         else          strcat(commentList[index], "\n");
13144     } else {
13145         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13146         if(addBraces)
13147              strcpy(commentList[index], "{\n");
13148         else commentList[index][0] = NULLCHAR;
13149         strcat(commentList[index], text);
13150         strcat(commentList[index], "\n");
13151         if(addBraces) strcat(commentList[index], "}\n");
13152     }
13153 }
13154
13155 static char * FindStr( char * text, char * sub_text )
13156 {
13157     char * result = strstr( text, sub_text );
13158
13159     if( result != NULL ) {
13160         result += strlen( sub_text );
13161     }
13162
13163     return result;
13164 }
13165
13166 /* [AS] Try to extract PV info from PGN comment */
13167 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13168 char *GetInfoFromComment( int index, char * text )
13169 {
13170     char * sep = text;
13171
13172     if( text != NULL && index > 0 ) {
13173         int score = 0;
13174         int depth = 0;
13175         int time = -1, sec = 0, deci;
13176         char * s_eval = FindStr( text, "[%eval " );
13177         char * s_emt = FindStr( text, "[%emt " );
13178
13179         if( s_eval != NULL || s_emt != NULL ) {
13180             /* New style */
13181             char delim;
13182
13183             if( s_eval != NULL ) {
13184                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13185                     return text;
13186                 }
13187
13188                 if( delim != ']' ) {
13189                     return text;
13190                 }
13191             }
13192
13193             if( s_emt != NULL ) {
13194             }
13195                 return text;
13196         }
13197         else {
13198             /* We expect something like: [+|-]nnn.nn/dd */
13199             int score_lo = 0;
13200
13201             if(*text != '{') return text; // [HGM] braces: must be normal comment
13202
13203             sep = strchr( text, '/' );
13204             if( sep == NULL || sep < (text+4) ) {
13205                 return text;
13206             }
13207
13208             time = -1; sec = -1; deci = -1;
13209             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13210                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13211                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13212                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13213                 return text;
13214             }
13215
13216             if( score_lo < 0 || score_lo >= 100 ) {
13217                 return text;
13218             }
13219
13220             if(sec >= 0) time = 600*time + 10*sec; else
13221             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13222
13223             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13224
13225             /* [HGM] PV time: now locate end of PV info */
13226             while( *++sep >= '0' && *sep <= '9'); // strip depth
13227             if(time >= 0)
13228             while( *++sep >= '0' && *sep <= '9'); // strip time
13229             if(sec >= 0)
13230             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13231             if(deci >= 0)
13232             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13233             while(*sep == ' ') sep++;
13234         }
13235
13236         if( depth <= 0 ) {
13237             return text;
13238         }
13239
13240         if( time < 0 ) {
13241             time = -1;
13242         }
13243
13244         pvInfoList[index-1].depth = depth;
13245         pvInfoList[index-1].score = score;
13246         pvInfoList[index-1].time  = 10*time; // centi-sec
13247         if(*sep == '}') *sep = 0; else *--sep = '{';
13248     }
13249     return sep;
13250 }
13251
13252 void
13253 SendToProgram(message, cps)
13254      char *message;
13255      ChessProgramState *cps;
13256 {
13257     int count, outCount, error;
13258     char buf[MSG_SIZ];
13259
13260     if (cps->pr == NULL) return;
13261     Attention(cps);
13262     
13263     if (appData.debugMode) {
13264         TimeMark now;
13265         GetTimeMark(&now);
13266         fprintf(debugFP, "%ld >%-6s: %s", 
13267                 SubtractTimeMarks(&now, &programStartTime),
13268                 cps->which, message);
13269     }
13270     
13271     count = strlen(message);
13272     outCount = OutputToProcess(cps->pr, message, count, &error);
13273     if (outCount < count && !exiting 
13274                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13275         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13276         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13277             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13278                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13279                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13280             } else {
13281                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13282             }
13283             gameInfo.resultDetails = StrSave(buf);
13284         }
13285         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13286     }
13287 }
13288
13289 void
13290 ReceiveFromProgram(isr, closure, message, count, error)
13291      InputSourceRef isr;
13292      VOIDSTAR closure;
13293      char *message;
13294      int count;
13295      int error;
13296 {
13297     char *end_str;
13298     char buf[MSG_SIZ];
13299     ChessProgramState *cps = (ChessProgramState *)closure;
13300
13301     if (isr != cps->isr) return; /* Killed intentionally */
13302     if (count <= 0) {
13303         if (count == 0) {
13304             sprintf(buf,
13305                     _("Error: %s chess program (%s) exited unexpectedly"),
13306                     cps->which, cps->program);
13307         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13308                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13309                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13310                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13311                 } else {
13312                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13313                 }
13314                 gameInfo.resultDetails = StrSave(buf);
13315             }
13316             RemoveInputSource(cps->isr);
13317             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13318         } else {
13319             sprintf(buf,
13320                     _("Error reading from %s chess program (%s)"),
13321                     cps->which, cps->program);
13322             RemoveInputSource(cps->isr);
13323
13324             /* [AS] Program is misbehaving badly... kill it */
13325             if( count == -2 ) {
13326                 DestroyChildProcess( cps->pr, 9 );
13327                 cps->pr = NoProc;
13328             }
13329
13330             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13331         }
13332         return;
13333     }
13334     
13335     if ((end_str = strchr(message, '\r')) != NULL)
13336       *end_str = NULLCHAR;
13337     if ((end_str = strchr(message, '\n')) != NULL)
13338       *end_str = NULLCHAR;
13339     
13340     if (appData.debugMode) {
13341         TimeMark now; int print = 1;
13342         char *quote = ""; char c; int i;
13343
13344         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13345                 char start = message[0];
13346                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13347                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13348                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13349                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13350                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13351                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13352                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13353                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13354                         { quote = "# "; print = (appData.engineComments == 2); }
13355                 message[0] = start; // restore original message
13356         }
13357         if(print) {
13358                 GetTimeMark(&now);
13359                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13360                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13361                         quote,
13362                         message);
13363         }
13364     }
13365
13366     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13367     if (appData.icsEngineAnalyze) {
13368         if (strstr(message, "whisper") != NULL ||
13369              strstr(message, "kibitz") != NULL || 
13370             strstr(message, "tellics") != NULL) return;
13371     }
13372
13373     HandleMachineMove(message, cps);
13374 }
13375
13376
13377 void
13378 SendTimeControl(cps, mps, tc, inc, sd, st)
13379      ChessProgramState *cps;
13380      int mps, inc, sd, st;
13381      long tc;
13382 {
13383     char buf[MSG_SIZ];
13384     int seconds;
13385
13386     if( timeControl_2 > 0 ) {
13387         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13388             tc = timeControl_2;
13389         }
13390     }
13391     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13392     inc /= cps->timeOdds;
13393     st  /= cps->timeOdds;
13394
13395     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13396
13397     if (st > 0) {
13398       /* Set exact time per move, normally using st command */
13399       if (cps->stKludge) {
13400         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13401         seconds = st % 60;
13402         if (seconds == 0) {
13403           sprintf(buf, "level 1 %d\n", st/60);
13404         } else {
13405           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13406         }
13407       } else {
13408         sprintf(buf, "st %d\n", st);
13409       }
13410     } else {
13411       /* Set conventional or incremental time control, using level command */
13412       if (seconds == 0) {
13413         /* Note old gnuchess bug -- minutes:seconds used to not work.
13414            Fixed in later versions, but still avoid :seconds
13415            when seconds is 0. */
13416         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13417       } else {
13418         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13419                 seconds, inc/1000);
13420       }
13421     }
13422     SendToProgram(buf, cps);
13423
13424     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13425     /* Orthogonally, limit search to given depth */
13426     if (sd > 0) {
13427       if (cps->sdKludge) {
13428         sprintf(buf, "depth\n%d\n", sd);
13429       } else {
13430         sprintf(buf, "sd %d\n", sd);
13431       }
13432       SendToProgram(buf, cps);
13433     }
13434
13435     if(cps->nps > 0) { /* [HGM] nps */
13436         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13437         else {
13438                 sprintf(buf, "nps %d\n", cps->nps);
13439               SendToProgram(buf, cps);
13440         }
13441     }
13442 }
13443
13444 ChessProgramState *WhitePlayer()
13445 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13446 {
13447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13448        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13449         return &second;
13450     return &first;
13451 }
13452
13453 void
13454 SendTimeRemaining(cps, machineWhite)
13455      ChessProgramState *cps;
13456      int /*boolean*/ machineWhite;
13457 {
13458     char message[MSG_SIZ];
13459     long time, otime;
13460
13461     /* Note: this routine must be called when the clocks are stopped
13462        or when they have *just* been set or switched; otherwise
13463        it will be off by the time since the current tick started.
13464     */
13465     if (machineWhite) {
13466         time = whiteTimeRemaining / 10;
13467         otime = blackTimeRemaining / 10;
13468     } else {
13469         time = blackTimeRemaining / 10;
13470         otime = whiteTimeRemaining / 10;
13471     }
13472     /* [HGM] translate opponent's time by time-odds factor */
13473     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13474     if (appData.debugMode) {
13475         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13476     }
13477
13478     if (time <= 0) time = 1;
13479     if (otime <= 0) otime = 1;
13480     
13481     sprintf(message, "time %ld\n", time);
13482     SendToProgram(message, cps);
13483
13484     sprintf(message, "otim %ld\n", otime);
13485     SendToProgram(message, cps);
13486 }
13487
13488 int
13489 BoolFeature(p, name, loc, cps)
13490      char **p;
13491      char *name;
13492      int *loc;
13493      ChessProgramState *cps;
13494 {
13495   char buf[MSG_SIZ];
13496   int len = strlen(name);
13497   int val;
13498   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13499     (*p) += len + 1;
13500     sscanf(*p, "%d", &val);
13501     *loc = (val != 0);
13502     while (**p && **p != ' ') (*p)++;
13503     sprintf(buf, "accepted %s\n", name);
13504     SendToProgram(buf, cps);
13505     return TRUE;
13506   }
13507   return FALSE;
13508 }
13509
13510 int
13511 IntFeature(p, name, loc, cps)
13512      char **p;
13513      char *name;
13514      int *loc;
13515      ChessProgramState *cps;
13516 {
13517   char buf[MSG_SIZ];
13518   int len = strlen(name);
13519   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13520     (*p) += len + 1;
13521     sscanf(*p, "%d", loc);
13522     while (**p && **p != ' ') (*p)++;
13523     sprintf(buf, "accepted %s\n", name);
13524     SendToProgram(buf, cps);
13525     return TRUE;
13526   }
13527   return FALSE;
13528 }
13529
13530 int
13531 StringFeature(p, name, loc, cps)
13532      char **p;
13533      char *name;
13534      char loc[];
13535      ChessProgramState *cps;
13536 {
13537   char buf[MSG_SIZ];
13538   int len = strlen(name);
13539   if (strncmp((*p), name, len) == 0
13540       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13541     (*p) += len + 2;
13542     sscanf(*p, "%[^\"]", loc);
13543     while (**p && **p != '\"') (*p)++;
13544     if (**p == '\"') (*p)++;
13545     sprintf(buf, "accepted %s\n", name);
13546     SendToProgram(buf, cps);
13547     return TRUE;
13548   }
13549   return FALSE;
13550 }
13551
13552 int 
13553 ParseOption(Option *opt, ChessProgramState *cps)
13554 // [HGM] options: process the string that defines an engine option, and determine
13555 // name, type, default value, and allowed value range
13556 {
13557         char *p, *q, buf[MSG_SIZ];
13558         int n, min = (-1)<<31, max = 1<<31, def;
13559
13560         if(p = strstr(opt->name, " -spin ")) {
13561             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13562             if(max < min) max = min; // enforce consistency
13563             if(def < min) def = min;
13564             if(def > max) def = max;
13565             opt->value = def;
13566             opt->min = min;
13567             opt->max = max;
13568             opt->type = Spin;
13569         } else if((p = strstr(opt->name, " -slider "))) {
13570             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13571             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13572             if(max < min) max = min; // enforce consistency
13573             if(def < min) def = min;
13574             if(def > max) def = max;
13575             opt->value = def;
13576             opt->min = min;
13577             opt->max = max;
13578             opt->type = Spin; // Slider;
13579         } else if((p = strstr(opt->name, " -string "))) {
13580             opt->textValue = p+9;
13581             opt->type = TextBox;
13582         } else if((p = strstr(opt->name, " -file "))) {
13583             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13584             opt->textValue = p+7;
13585             opt->type = TextBox; // FileName;
13586         } else if((p = strstr(opt->name, " -path "))) {
13587             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13588             opt->textValue = p+7;
13589             opt->type = TextBox; // PathName;
13590         } else if(p = strstr(opt->name, " -check ")) {
13591             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13592             opt->value = (def != 0);
13593             opt->type = CheckBox;
13594         } else if(p = strstr(opt->name, " -combo ")) {
13595             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13596             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13597             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13598             opt->value = n = 0;
13599             while(q = StrStr(q, " /// ")) {
13600                 n++; *q = 0;    // count choices, and null-terminate each of them
13601                 q += 5;
13602                 if(*q == '*') { // remember default, which is marked with * prefix
13603                     q++;
13604                     opt->value = n;
13605                 }
13606                 cps->comboList[cps->comboCnt++] = q;
13607             }
13608             cps->comboList[cps->comboCnt++] = NULL;
13609             opt->max = n + 1;
13610             opt->type = ComboBox;
13611         } else if(p = strstr(opt->name, " -button")) {
13612             opt->type = Button;
13613         } else if(p = strstr(opt->name, " -save")) {
13614             opt->type = SaveButton;
13615         } else return FALSE;
13616         *p = 0; // terminate option name
13617         // now look if the command-line options define a setting for this engine option.
13618         if(cps->optionSettings && cps->optionSettings[0])
13619             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13620         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13621                 sprintf(buf, "option %s", p);
13622                 if(p = strstr(buf, ",")) *p = 0;
13623                 strcat(buf, "\n");
13624                 SendToProgram(buf, cps);
13625         }
13626         return TRUE;
13627 }
13628
13629 void
13630 FeatureDone(cps, val)
13631      ChessProgramState* cps;
13632      int val;
13633 {
13634   DelayedEventCallback cb = GetDelayedEvent();
13635   if ((cb == InitBackEnd3 && cps == &first) ||
13636       (cb == TwoMachinesEventIfReady && cps == &second)) {
13637     CancelDelayedEvent();
13638     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13639   }
13640   cps->initDone = val;
13641 }
13642
13643 /* Parse feature command from engine */
13644 void
13645 ParseFeatures(args, cps)
13646      char* args;
13647      ChessProgramState *cps;  
13648 {
13649   char *p = args;
13650   char *q;
13651   int val;
13652   char buf[MSG_SIZ];
13653
13654   for (;;) {
13655     while (*p == ' ') p++;
13656     if (*p == NULLCHAR) return;
13657
13658     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13659     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13660     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13661     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13662     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13663     if (BoolFeature(&p, "reuse", &val, cps)) {
13664       /* Engine can disable reuse, but can't enable it if user said no */
13665       if (!val) cps->reuse = FALSE;
13666       continue;
13667     }
13668     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13669     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13670       if (gameMode == TwoMachinesPlay) {
13671         DisplayTwoMachinesTitle();
13672       } else {
13673         DisplayTitle("");
13674       }
13675       continue;
13676     }
13677     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13678     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13679     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13680     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13681     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13682     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13683     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13684     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13685     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13686     if (IntFeature(&p, "done", &val, cps)) {
13687       FeatureDone(cps, val);
13688       continue;
13689     }
13690     /* Added by Tord: */
13691     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13692     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13693     /* End of additions by Tord */
13694
13695     /* [HGM] added features: */
13696     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13697     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13698     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13699     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13700     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13701     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13702     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13703         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13704             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13705             SendToProgram(buf, cps);
13706             continue;
13707         }
13708         if(cps->nrOptions >= MAX_OPTIONS) {
13709             cps->nrOptions--;
13710             sprintf(buf, "%s engine has too many options\n", cps->which);
13711             DisplayError(buf, 0);
13712         }
13713         continue;
13714     }
13715     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13716     /* End of additions by HGM */
13717
13718     /* unknown feature: complain and skip */
13719     q = p;
13720     while (*q && *q != '=') q++;
13721     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13722     SendToProgram(buf, cps);
13723     p = q;
13724     if (*p == '=') {
13725       p++;
13726       if (*p == '\"') {
13727         p++;
13728         while (*p && *p != '\"') p++;
13729         if (*p == '\"') p++;
13730       } else {
13731         while (*p && *p != ' ') p++;
13732       }
13733     }
13734   }
13735
13736 }
13737
13738 void
13739 PeriodicUpdatesEvent(newState)
13740      int newState;
13741 {
13742     if (newState == appData.periodicUpdates)
13743       return;
13744
13745     appData.periodicUpdates=newState;
13746
13747     /* Display type changes, so update it now */
13748 //    DisplayAnalysis();
13749
13750     /* Get the ball rolling again... */
13751     if (newState) {
13752         AnalysisPeriodicEvent(1);
13753         StartAnalysisClock();
13754     }
13755 }
13756
13757 void
13758 PonderNextMoveEvent(newState)
13759      int newState;
13760 {
13761     if (newState == appData.ponderNextMove) return;
13762     if (gameMode == EditPosition) EditPositionDone(TRUE);
13763     if (newState) {
13764         SendToProgram("hard\n", &first);
13765         if (gameMode == TwoMachinesPlay) {
13766             SendToProgram("hard\n", &second);
13767         }
13768     } else {
13769         SendToProgram("easy\n", &first);
13770         thinkOutput[0] = NULLCHAR;
13771         if (gameMode == TwoMachinesPlay) {
13772             SendToProgram("easy\n", &second);
13773         }
13774     }
13775     appData.ponderNextMove = newState;
13776 }
13777
13778 void
13779 NewSettingEvent(option, command, value)
13780      char *command;
13781      int option, value;
13782 {
13783     char buf[MSG_SIZ];
13784
13785     if (gameMode == EditPosition) EditPositionDone(TRUE);
13786     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13787     SendToProgram(buf, &first);
13788     if (gameMode == TwoMachinesPlay) {
13789         SendToProgram(buf, &second);
13790     }
13791 }
13792
13793 void
13794 ShowThinkingEvent()
13795 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13796 {
13797     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13798     int newState = appData.showThinking
13799         // [HGM] thinking: other features now need thinking output as well
13800         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13801     
13802     if (oldState == newState) return;
13803     oldState = newState;
13804     if (gameMode == EditPosition) EditPositionDone(TRUE);
13805     if (oldState) {
13806         SendToProgram("post\n", &first);
13807         if (gameMode == TwoMachinesPlay) {
13808             SendToProgram("post\n", &second);
13809         }
13810     } else {
13811         SendToProgram("nopost\n", &first);
13812         thinkOutput[0] = NULLCHAR;
13813         if (gameMode == TwoMachinesPlay) {
13814             SendToProgram("nopost\n", &second);
13815         }
13816     }
13817 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13818 }
13819
13820 void
13821 AskQuestionEvent(title, question, replyPrefix, which)
13822      char *title; char *question; char *replyPrefix; char *which;
13823 {
13824   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13825   if (pr == NoProc) return;
13826   AskQuestion(title, question, replyPrefix, pr);
13827 }
13828
13829 void
13830 DisplayMove(moveNumber)
13831      int moveNumber;
13832 {
13833     char message[MSG_SIZ];
13834     char res[MSG_SIZ];
13835     char cpThinkOutput[MSG_SIZ];
13836
13837     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13838     
13839     if (moveNumber == forwardMostMove - 1 || 
13840         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13841
13842         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13843
13844         if (strchr(cpThinkOutput, '\n')) {
13845             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13846         }
13847     } else {
13848         *cpThinkOutput = NULLCHAR;
13849     }
13850
13851     /* [AS] Hide thinking from human user */
13852     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13853         *cpThinkOutput = NULLCHAR;
13854         if( thinkOutput[0] != NULLCHAR ) {
13855             int i;
13856
13857             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13858                 cpThinkOutput[i] = '.';
13859             }
13860             cpThinkOutput[i] = NULLCHAR;
13861             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13862         }
13863     }
13864
13865     if (moveNumber == forwardMostMove - 1 &&
13866         gameInfo.resultDetails != NULL) {
13867         if (gameInfo.resultDetails[0] == NULLCHAR) {
13868             sprintf(res, " %s", PGNResult(gameInfo.result));
13869         } else {
13870             sprintf(res, " {%s} %s",
13871                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13872         }
13873     } else {
13874         res[0] = NULLCHAR;
13875     }
13876
13877     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13878         DisplayMessage(res, cpThinkOutput);
13879     } else {
13880         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13881                 WhiteOnMove(moveNumber) ? " " : ".. ",
13882                 parseList[moveNumber], res);
13883         DisplayMessage(message, cpThinkOutput);
13884     }
13885 }
13886
13887 void
13888 DisplayComment(moveNumber, text)
13889      int moveNumber;
13890      char *text;
13891 {
13892     char title[MSG_SIZ];
13893     char buf[8000]; // comment can be long!
13894     int score, depth;
13895     
13896     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13897       strcpy(title, "Comment");
13898     } else {
13899       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13900               WhiteOnMove(moveNumber) ? " " : ".. ",
13901               parseList[moveNumber]);
13902     }
13903     // [HGM] PV info: display PV info together with (or as) comment
13904     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13905       if(text == NULL) text = "";                                           
13906       score = pvInfoList[moveNumber].score;
13907       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13908               depth, (pvInfoList[moveNumber].time+50)/100, text);
13909       text = buf;
13910     }
13911     if (text != NULL && (appData.autoDisplayComment || commentUp))
13912         CommentPopUp(title, text);
13913 }
13914
13915 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13916  * might be busy thinking or pondering.  It can be omitted if your
13917  * gnuchess is configured to stop thinking immediately on any user
13918  * input.  However, that gnuchess feature depends on the FIONREAD
13919  * ioctl, which does not work properly on some flavors of Unix.
13920  */
13921 void
13922 Attention(cps)
13923      ChessProgramState *cps;
13924 {
13925 #if ATTENTION
13926     if (!cps->useSigint) return;
13927     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13928     switch (gameMode) {
13929       case MachinePlaysWhite:
13930       case MachinePlaysBlack:
13931       case TwoMachinesPlay:
13932       case IcsPlayingWhite:
13933       case IcsPlayingBlack:
13934       case AnalyzeMode:
13935       case AnalyzeFile:
13936         /* Skip if we know it isn't thinking */
13937         if (!cps->maybeThinking) return;
13938         if (appData.debugMode)
13939           fprintf(debugFP, "Interrupting %s\n", cps->which);
13940         InterruptChildProcess(cps->pr);
13941         cps->maybeThinking = FALSE;
13942         break;
13943       default:
13944         break;
13945     }
13946 #endif /*ATTENTION*/
13947 }
13948
13949 int
13950 CheckFlags()
13951 {
13952     if (whiteTimeRemaining <= 0) {
13953         if (!whiteFlag) {
13954             whiteFlag = TRUE;
13955             if (appData.icsActive) {
13956                 if (appData.autoCallFlag &&
13957                     gameMode == IcsPlayingBlack && !blackFlag) {
13958                   SendToICS(ics_prefix);
13959                   SendToICS("flag\n");
13960                 }
13961             } else {
13962                 if (blackFlag) {
13963                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13964                 } else {
13965                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13966                     if (appData.autoCallFlag) {
13967                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13968                         return TRUE;
13969                     }
13970                 }
13971             }
13972         }
13973     }
13974     if (blackTimeRemaining <= 0) {
13975         if (!blackFlag) {
13976             blackFlag = TRUE;
13977             if (appData.icsActive) {
13978                 if (appData.autoCallFlag &&
13979                     gameMode == IcsPlayingWhite && !whiteFlag) {
13980                   SendToICS(ics_prefix);
13981                   SendToICS("flag\n");
13982                 }
13983             } else {
13984                 if (whiteFlag) {
13985                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13986                 } else {
13987                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13988                     if (appData.autoCallFlag) {
13989                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13990                         return TRUE;
13991                     }
13992                 }
13993             }
13994         }
13995     }
13996     return FALSE;
13997 }
13998
13999 void
14000 CheckTimeControl()
14001 {
14002     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14003         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14004
14005     /*
14006      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14007      */
14008     if ( !WhiteOnMove(forwardMostMove) )
14009         /* White made time control */
14010         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14011         /* [HGM] time odds: correct new time quota for time odds! */
14012                                             / WhitePlayer()->timeOdds;
14013       else
14014         /* Black made time control */
14015         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14016                                             / WhitePlayer()->other->timeOdds;
14017 }
14018
14019 void
14020 DisplayBothClocks()
14021 {
14022     int wom = gameMode == EditPosition ?
14023       !blackPlaysFirst : WhiteOnMove(currentMove);
14024     DisplayWhiteClock(whiteTimeRemaining, wom);
14025     DisplayBlackClock(blackTimeRemaining, !wom);
14026 }
14027
14028
14029 /* Timekeeping seems to be a portability nightmare.  I think everyone
14030    has ftime(), but I'm really not sure, so I'm including some ifdefs
14031    to use other calls if you don't.  Clocks will be less accurate if
14032    you have neither ftime nor gettimeofday.
14033 */
14034
14035 /* VS 2008 requires the #include outside of the function */
14036 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14037 #include <sys/timeb.h>
14038 #endif
14039
14040 /* Get the current time as a TimeMark */
14041 void
14042 GetTimeMark(tm)
14043      TimeMark *tm;
14044 {
14045 #if HAVE_GETTIMEOFDAY
14046
14047     struct timeval timeVal;
14048     struct timezone timeZone;
14049
14050     gettimeofday(&timeVal, &timeZone);
14051     tm->sec = (long) timeVal.tv_sec; 
14052     tm->ms = (int) (timeVal.tv_usec / 1000L);
14053
14054 #else /*!HAVE_GETTIMEOFDAY*/
14055 #if HAVE_FTIME
14056
14057 // include <sys/timeb.h> / moved to just above start of function
14058     struct timeb timeB;
14059
14060     ftime(&timeB);
14061     tm->sec = (long) timeB.time;
14062     tm->ms = (int) timeB.millitm;
14063
14064 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14065     tm->sec = (long) time(NULL);
14066     tm->ms = 0;
14067 #endif
14068 #endif
14069 }
14070
14071 /* Return the difference in milliseconds between two
14072    time marks.  We assume the difference will fit in a long!
14073 */
14074 long
14075 SubtractTimeMarks(tm2, tm1)
14076      TimeMark *tm2, *tm1;
14077 {
14078     return 1000L*(tm2->sec - tm1->sec) +
14079            (long) (tm2->ms - tm1->ms);
14080 }
14081
14082
14083 /*
14084  * Code to manage the game clocks.
14085  *
14086  * In tournament play, black starts the clock and then white makes a move.
14087  * We give the human user a slight advantage if he is playing white---the
14088  * clocks don't run until he makes his first move, so it takes zero time.
14089  * Also, we don't account for network lag, so we could get out of sync
14090  * with GNU Chess's clock -- but then, referees are always right.  
14091  */
14092
14093 static TimeMark tickStartTM;
14094 static long intendedTickLength;
14095
14096 long
14097 NextTickLength(timeRemaining)
14098      long timeRemaining;
14099 {
14100     long nominalTickLength, nextTickLength;
14101
14102     if (timeRemaining > 0L && timeRemaining <= 10000L)
14103       nominalTickLength = 100L;
14104     else
14105       nominalTickLength = 1000L;
14106     nextTickLength = timeRemaining % nominalTickLength;
14107     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14108
14109     return nextTickLength;
14110 }
14111
14112 /* Adjust clock one minute up or down */
14113 void
14114 AdjustClock(Boolean which, int dir)
14115 {
14116     if(which) blackTimeRemaining += 60000*dir;
14117     else      whiteTimeRemaining += 60000*dir;
14118     DisplayBothClocks();
14119 }
14120
14121 /* Stop clocks and reset to a fresh time control */
14122 void
14123 ResetClocks() 
14124 {
14125     (void) StopClockTimer();
14126     if (appData.icsActive) {
14127         whiteTimeRemaining = blackTimeRemaining = 0;
14128     } else if (searchTime) {
14129         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14130         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14131     } else { /* [HGM] correct new time quote for time odds */
14132         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14133         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14134     }
14135     if (whiteFlag || blackFlag) {
14136         DisplayTitle("");
14137         whiteFlag = blackFlag = FALSE;
14138     }
14139     DisplayBothClocks();
14140 }
14141
14142 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14143
14144 /* Decrement running clock by amount of time that has passed */
14145 void
14146 DecrementClocks()
14147 {
14148     long timeRemaining;
14149     long lastTickLength, fudge;
14150     TimeMark now;
14151
14152     if (!appData.clockMode) return;
14153     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14154         
14155     GetTimeMark(&now);
14156
14157     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14158
14159     /* Fudge if we woke up a little too soon */
14160     fudge = intendedTickLength - lastTickLength;
14161     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14162
14163     if (WhiteOnMove(forwardMostMove)) {
14164         if(whiteNPS >= 0) lastTickLength = 0;
14165         timeRemaining = whiteTimeRemaining -= lastTickLength;
14166         DisplayWhiteClock(whiteTimeRemaining - fudge,
14167                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14168     } else {
14169         if(blackNPS >= 0) lastTickLength = 0;
14170         timeRemaining = blackTimeRemaining -= lastTickLength;
14171         DisplayBlackClock(blackTimeRemaining - fudge,
14172                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14173     }
14174
14175     if (CheckFlags()) return;
14176         
14177     tickStartTM = now;
14178     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14179     StartClockTimer(intendedTickLength);
14180
14181     /* if the time remaining has fallen below the alarm threshold, sound the
14182      * alarm. if the alarm has sounded and (due to a takeback or time control
14183      * with increment) the time remaining has increased to a level above the
14184      * threshold, reset the alarm so it can sound again. 
14185      */
14186     
14187     if (appData.icsActive && appData.icsAlarm) {
14188
14189         /* make sure we are dealing with the user's clock */
14190         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14191                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14192            )) return;
14193
14194         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14195             alarmSounded = FALSE;
14196         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14197             PlayAlarmSound();
14198             alarmSounded = TRUE;
14199         }
14200     }
14201 }
14202
14203
14204 /* A player has just moved, so stop the previously running
14205    clock and (if in clock mode) start the other one.
14206    We redisplay both clocks in case we're in ICS mode, because
14207    ICS gives us an update to both clocks after every move.
14208    Note that this routine is called *after* forwardMostMove
14209    is updated, so the last fractional tick must be subtracted
14210    from the color that is *not* on move now.
14211 */
14212 void
14213 SwitchClocks(int newMoveNr)
14214 {
14215     long lastTickLength;
14216     TimeMark now;
14217     int flagged = FALSE;
14218
14219     GetTimeMark(&now);
14220
14221     if (StopClockTimer() && appData.clockMode) {
14222         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14223         if (!WhiteOnMove(forwardMostMove)) {
14224             if(blackNPS >= 0) lastTickLength = 0;
14225             blackTimeRemaining -= lastTickLength;
14226            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14227 //         if(pvInfoList[forwardMostMove-1].time == -1)
14228                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14229                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14230         } else {
14231            if(whiteNPS >= 0) lastTickLength = 0;
14232            whiteTimeRemaining -= lastTickLength;
14233            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14234 //         if(pvInfoList[forwardMostMove-1].time == -1)
14235                  pvInfoList[forwardMostMove-1].time = 
14236                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14237         }
14238         flagged = CheckFlags();
14239     }
14240     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14241     CheckTimeControl();
14242
14243     if (flagged || !appData.clockMode) return;
14244
14245     switch (gameMode) {
14246       case MachinePlaysBlack:
14247       case MachinePlaysWhite:
14248       case BeginningOfGame:
14249         if (pausing) return;
14250         break;
14251
14252       case EditGame:
14253       case PlayFromGameFile:
14254       case IcsExamining:
14255         return;
14256
14257       default:
14258         break;
14259     }
14260
14261     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14262         if(WhiteOnMove(forwardMostMove))
14263              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14264         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14265     }
14266
14267     tickStartTM = now;
14268     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14269       whiteTimeRemaining : blackTimeRemaining);
14270     StartClockTimer(intendedTickLength);
14271 }
14272         
14273
14274 /* Stop both clocks */
14275 void
14276 StopClocks()
14277 {       
14278     long lastTickLength;
14279     TimeMark now;
14280
14281     if (!StopClockTimer()) return;
14282     if (!appData.clockMode) return;
14283
14284     GetTimeMark(&now);
14285
14286     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14287     if (WhiteOnMove(forwardMostMove)) {
14288         if(whiteNPS >= 0) lastTickLength = 0;
14289         whiteTimeRemaining -= lastTickLength;
14290         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14291     } else {
14292         if(blackNPS >= 0) lastTickLength = 0;
14293         blackTimeRemaining -= lastTickLength;
14294         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14295     }
14296     CheckFlags();
14297 }
14298         
14299 /* Start clock of player on move.  Time may have been reset, so
14300    if clock is already running, stop and restart it. */
14301 void
14302 StartClocks()
14303 {
14304     (void) StopClockTimer(); /* in case it was running already */
14305     DisplayBothClocks();
14306     if (CheckFlags()) return;
14307
14308     if (!appData.clockMode) return;
14309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14310
14311     GetTimeMark(&tickStartTM);
14312     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14313       whiteTimeRemaining : blackTimeRemaining);
14314
14315    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14316     whiteNPS = blackNPS = -1; 
14317     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14318        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14319         whiteNPS = first.nps;
14320     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14321        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14322         blackNPS = first.nps;
14323     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14324         whiteNPS = second.nps;
14325     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14326         blackNPS = second.nps;
14327     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14328
14329     StartClockTimer(intendedTickLength);
14330 }
14331
14332 char *
14333 TimeString(ms)
14334      long ms;
14335 {
14336     long second, minute, hour, day;
14337     char *sign = "";
14338     static char buf[32];
14339     
14340     if (ms > 0 && ms <= 9900) {
14341       /* convert milliseconds to tenths, rounding up */
14342       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14343
14344       sprintf(buf, " %03.1f ", tenths/10.0);
14345       return buf;
14346     }
14347
14348     /* convert milliseconds to seconds, rounding up */
14349     /* use floating point to avoid strangeness of integer division
14350        with negative dividends on many machines */
14351     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14352
14353     if (second < 0) {
14354         sign = "-";
14355         second = -second;
14356     }
14357     
14358     day = second / (60 * 60 * 24);
14359     second = second % (60 * 60 * 24);
14360     hour = second / (60 * 60);
14361     second = second % (60 * 60);
14362     minute = second / 60;
14363     second = second % 60;
14364     
14365     if (day > 0)
14366       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14367               sign, day, hour, minute, second);
14368     else if (hour > 0)
14369       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14370     else
14371       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14372     
14373     return buf;
14374 }
14375
14376
14377 /*
14378  * This is necessary because some C libraries aren't ANSI C compliant yet.
14379  */
14380 char *
14381 StrStr(string, match)
14382      char *string, *match;
14383 {
14384     int i, length;
14385     
14386     length = strlen(match);
14387     
14388     for (i = strlen(string) - length; i >= 0; i--, string++)
14389       if (!strncmp(match, string, length))
14390         return string;
14391     
14392     return NULL;
14393 }
14394
14395 char *
14396 StrCaseStr(string, match)
14397      char *string, *match;
14398 {
14399     int i, j, length;
14400     
14401     length = strlen(match);
14402     
14403     for (i = strlen(string) - length; i >= 0; i--, string++) {
14404         for (j = 0; j < length; j++) {
14405             if (ToLower(match[j]) != ToLower(string[j]))
14406               break;
14407         }
14408         if (j == length) return string;
14409     }
14410
14411     return NULL;
14412 }
14413
14414 #ifndef _amigados
14415 int
14416 StrCaseCmp(s1, s2)
14417      char *s1, *s2;
14418 {
14419     char c1, c2;
14420     
14421     for (;;) {
14422         c1 = ToLower(*s1++);
14423         c2 = ToLower(*s2++);
14424         if (c1 > c2) return 1;
14425         if (c1 < c2) return -1;
14426         if (c1 == NULLCHAR) return 0;
14427     }
14428 }
14429
14430
14431 int
14432 ToLower(c)
14433      int c;
14434 {
14435     return isupper(c) ? tolower(c) : c;
14436 }
14437
14438
14439 int
14440 ToUpper(c)
14441      int c;
14442 {
14443     return islower(c) ? toupper(c) : c;
14444 }
14445 #endif /* !_amigados    */
14446
14447 char *
14448 StrSave(s)
14449      char *s;
14450 {
14451     char *ret;
14452
14453     if ((ret = (char *) malloc(strlen(s) + 1))) {
14454         strcpy(ret, s);
14455     }
14456     return ret;
14457 }
14458
14459 char *
14460 StrSavePtr(s, savePtr)
14461      char *s, **savePtr;
14462 {
14463     if (*savePtr) {
14464         free(*savePtr);
14465     }
14466     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14467         strcpy(*savePtr, s);
14468     }
14469     return(*savePtr);
14470 }
14471
14472 char *
14473 PGNDate()
14474 {
14475     time_t clock;
14476     struct tm *tm;
14477     char buf[MSG_SIZ];
14478
14479     clock = time((time_t *)NULL);
14480     tm = localtime(&clock);
14481     sprintf(buf, "%04d.%02d.%02d",
14482             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14483     return StrSave(buf);
14484 }
14485
14486
14487 char *
14488 PositionToFEN(move, overrideCastling)
14489      int move;
14490      char *overrideCastling;
14491 {
14492     int i, j, fromX, fromY, toX, toY;
14493     int whiteToPlay;
14494     char buf[128];
14495     char *p, *q;
14496     int emptycount;
14497     ChessSquare piece;
14498
14499     whiteToPlay = (gameMode == EditPosition) ?
14500       !blackPlaysFirst : (move % 2 == 0);
14501     p = buf;
14502
14503     /* Piece placement data */
14504     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14505         emptycount = 0;
14506         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14507             if (boards[move][i][j] == EmptySquare) {
14508                 emptycount++;
14509             } else { ChessSquare piece = boards[move][i][j];
14510                 if (emptycount > 0) {
14511                     if(emptycount<10) /* [HGM] can be >= 10 */
14512                         *p++ = '0' + emptycount;
14513                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14514                     emptycount = 0;
14515                 }
14516                 if(PieceToChar(piece) == '+') {
14517                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14518                     *p++ = '+';
14519                     piece = (ChessSquare)(DEMOTED piece);
14520                 } 
14521                 *p++ = PieceToChar(piece);
14522                 if(p[-1] == '~') {
14523                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14524                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14525                     *p++ = '~';
14526                 }
14527             }
14528         }
14529         if (emptycount > 0) {
14530             if(emptycount<10) /* [HGM] can be >= 10 */
14531                 *p++ = '0' + emptycount;
14532             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14533             emptycount = 0;
14534         }
14535         *p++ = '/';
14536     }
14537     *(p - 1) = ' ';
14538
14539     /* [HGM] print Crazyhouse or Shogi holdings */
14540     if( gameInfo.holdingsWidth ) {
14541         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14542         q = p;
14543         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14544             piece = boards[move][i][BOARD_WIDTH-1];
14545             if( piece != EmptySquare )
14546               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14547                   *p++ = PieceToChar(piece);
14548         }
14549         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14550             piece = boards[move][BOARD_HEIGHT-i-1][0];
14551             if( piece != EmptySquare )
14552               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14553                   *p++ = PieceToChar(piece);
14554         }
14555
14556         if( q == p ) *p++ = '-';
14557         *p++ = ']';
14558         *p++ = ' ';
14559     }
14560
14561     /* Active color */
14562     *p++ = whiteToPlay ? 'w' : 'b';
14563     *p++ = ' ';
14564
14565   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14566     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14567   } else {
14568   if(nrCastlingRights) {
14569      q = p;
14570      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14571        /* [HGM] write directly from rights */
14572            if(boards[move][CASTLING][2] != NoRights &&
14573               boards[move][CASTLING][0] != NoRights   )
14574                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14575            if(boards[move][CASTLING][2] != NoRights &&
14576               boards[move][CASTLING][1] != NoRights   )
14577                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14578            if(boards[move][CASTLING][5] != NoRights &&
14579               boards[move][CASTLING][3] != NoRights   )
14580                 *p++ = boards[move][CASTLING][3] + AAA;
14581            if(boards[move][CASTLING][5] != NoRights &&
14582               boards[move][CASTLING][4] != NoRights   )
14583                 *p++ = boards[move][CASTLING][4] + AAA;
14584      } else {
14585
14586         /* [HGM] write true castling rights */
14587         if( nrCastlingRights == 6 ) {
14588             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14589                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14590             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14591                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14592             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14593                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14594             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14595                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14596         }
14597      }
14598      if (q == p) *p++ = '-'; /* No castling rights */
14599      *p++ = ' ';
14600   }
14601
14602   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14603      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14604     /* En passant target square */
14605     if (move > backwardMostMove) {
14606         fromX = moveList[move - 1][0] - AAA;
14607         fromY = moveList[move - 1][1] - ONE;
14608         toX = moveList[move - 1][2] - AAA;
14609         toY = moveList[move - 1][3] - ONE;
14610         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14611             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14612             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14613             fromX == toX) {
14614             /* 2-square pawn move just happened */
14615             *p++ = toX + AAA;
14616             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14617         } else {
14618             *p++ = '-';
14619         }
14620     } else if(move == backwardMostMove) {
14621         // [HGM] perhaps we should always do it like this, and forget the above?
14622         if((signed char)boards[move][EP_STATUS] >= 0) {
14623             *p++ = boards[move][EP_STATUS] + AAA;
14624             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14625         } else {
14626             *p++ = '-';
14627         }
14628     } else {
14629         *p++ = '-';
14630     }
14631     *p++ = ' ';
14632   }
14633   }
14634
14635     /* [HGM] find reversible plies */
14636     {   int i = 0, j=move;
14637
14638         if (appData.debugMode) { int k;
14639             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14640             for(k=backwardMostMove; k<=forwardMostMove; k++)
14641                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14642
14643         }
14644
14645         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14646         if( j == backwardMostMove ) i += initialRulePlies;
14647         sprintf(p, "%d ", i);
14648         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14649     }
14650     /* Fullmove number */
14651     sprintf(p, "%d", (move / 2) + 1);
14652     
14653     return StrSave(buf);
14654 }
14655
14656 Boolean
14657 ParseFEN(board, blackPlaysFirst, fen)
14658     Board board;
14659      int *blackPlaysFirst;
14660      char *fen;
14661 {
14662     int i, j;
14663     char *p;
14664     int emptycount;
14665     ChessSquare piece;
14666
14667     p = fen;
14668
14669     /* [HGM] by default clear Crazyhouse holdings, if present */
14670     if(gameInfo.holdingsWidth) {
14671        for(i=0; i<BOARD_HEIGHT; i++) {
14672            board[i][0]             = EmptySquare; /* black holdings */
14673            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14674            board[i][1]             = (ChessSquare) 0; /* black counts */
14675            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14676        }
14677     }
14678
14679     /* Piece placement data */
14680     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14681         j = 0;
14682         for (;;) {
14683             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14684                 if (*p == '/') p++;
14685                 emptycount = gameInfo.boardWidth - j;
14686                 while (emptycount--)
14687                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14688                 break;
14689 #if(BOARD_FILES >= 10)
14690             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14691                 p++; emptycount=10;
14692                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14693                 while (emptycount--)
14694                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14695 #endif
14696             } else if (isdigit(*p)) {
14697                 emptycount = *p++ - '0';
14698                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14699                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14700                 while (emptycount--)
14701                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14702             } else if (*p == '+' || isalpha(*p)) {
14703                 if (j >= gameInfo.boardWidth) return FALSE;
14704                 if(*p=='+') {
14705                     piece = CharToPiece(*++p);
14706                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14707                     piece = (ChessSquare) (PROMOTED piece ); p++;
14708                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14709                 } else piece = CharToPiece(*p++);
14710
14711                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14712                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14713                     piece = (ChessSquare) (PROMOTED piece);
14714                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14715                     p++;
14716                 }
14717                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14718             } else {
14719                 return FALSE;
14720             }
14721         }
14722     }
14723     while (*p == '/' || *p == ' ') p++;
14724
14725     /* [HGM] look for Crazyhouse holdings here */
14726     while(*p==' ') p++;
14727     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14728         if(*p == '[') p++;
14729         if(*p == '-' ) *p++; /* empty holdings */ else {
14730             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14731             /* if we would allow FEN reading to set board size, we would   */
14732             /* have to add holdings and shift the board read so far here   */
14733             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14734                 *p++;
14735                 if((int) piece >= (int) BlackPawn ) {
14736                     i = (int)piece - (int)BlackPawn;
14737                     i = PieceToNumber((ChessSquare)i);
14738                     if( i >= gameInfo.holdingsSize ) return FALSE;
14739                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14740                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14741                 } else {
14742                     i = (int)piece - (int)WhitePawn;
14743                     i = PieceToNumber((ChessSquare)i);
14744                     if( i >= gameInfo.holdingsSize ) return FALSE;
14745                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14746                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14747                 }
14748             }
14749         }
14750         if(*p == ']') *p++;
14751     }
14752
14753     while(*p == ' ') p++;
14754
14755     /* Active color */
14756     switch (*p++) {
14757       case 'w':
14758         *blackPlaysFirst = FALSE;
14759         break;
14760       case 'b': 
14761         *blackPlaysFirst = TRUE;
14762         break;
14763       default:
14764         return FALSE;
14765     }
14766
14767     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14768     /* return the extra info in global variiables             */
14769
14770     /* set defaults in case FEN is incomplete */
14771     board[EP_STATUS] = EP_UNKNOWN;
14772     for(i=0; i<nrCastlingRights; i++ ) {
14773         board[CASTLING][i] =
14774             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14775     }   /* assume possible unless obviously impossible */
14776     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14777     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14778     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14779                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14780     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14781     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14782     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14783                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14784     FENrulePlies = 0;
14785
14786     while(*p==' ') p++;
14787     if(nrCastlingRights) {
14788       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14789           /* castling indicator present, so default becomes no castlings */
14790           for(i=0; i<nrCastlingRights; i++ ) {
14791                  board[CASTLING][i] = NoRights;
14792           }
14793       }
14794       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14795              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14796              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14797              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14798         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14799
14800         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14801             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14802             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14803         }
14804         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14805             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14806         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14807                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14808         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14809                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14810         switch(c) {
14811           case'K':
14812               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14813               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14814               board[CASTLING][2] = whiteKingFile;
14815               break;
14816           case'Q':
14817               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14818               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14819               board[CASTLING][2] = whiteKingFile;
14820               break;
14821           case'k':
14822               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14823               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14824               board[CASTLING][5] = blackKingFile;
14825               break;
14826           case'q':
14827               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14828               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14829               board[CASTLING][5] = blackKingFile;
14830           case '-':
14831               break;
14832           default: /* FRC castlings */
14833               if(c >= 'a') { /* black rights */
14834                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14835                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14836                   if(i == BOARD_RGHT) break;
14837                   board[CASTLING][5] = i;
14838                   c -= AAA;
14839                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14840                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14841                   if(c > i)
14842                       board[CASTLING][3] = c;
14843                   else
14844                       board[CASTLING][4] = c;
14845               } else { /* white rights */
14846                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14847                     if(board[0][i] == WhiteKing) break;
14848                   if(i == BOARD_RGHT) break;
14849                   board[CASTLING][2] = i;
14850                   c -= AAA - 'a' + 'A';
14851                   if(board[0][c] >= WhiteKing) break;
14852                   if(c > i)
14853                       board[CASTLING][0] = c;
14854                   else
14855                       board[CASTLING][1] = c;
14856               }
14857         }
14858       }
14859       for(i=0; i<nrCastlingRights; i++)
14860         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14861     if (appData.debugMode) {
14862         fprintf(debugFP, "FEN castling rights:");
14863         for(i=0; i<nrCastlingRights; i++)
14864         fprintf(debugFP, " %d", board[CASTLING][i]);
14865         fprintf(debugFP, "\n");
14866     }
14867
14868       while(*p==' ') p++;
14869     }
14870
14871     /* read e.p. field in games that know e.p. capture */
14872     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14873        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14874       if(*p=='-') {
14875         p++; board[EP_STATUS] = EP_NONE;
14876       } else {
14877          char c = *p++ - AAA;
14878
14879          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14880          if(*p >= '0' && *p <='9') *p++;
14881          board[EP_STATUS] = c;
14882       }
14883     }
14884
14885
14886     if(sscanf(p, "%d", &i) == 1) {
14887         FENrulePlies = i; /* 50-move ply counter */
14888         /* (The move number is still ignored)    */
14889     }
14890
14891     return TRUE;
14892 }
14893       
14894 void
14895 EditPositionPasteFEN(char *fen)
14896 {
14897   if (fen != NULL) {
14898     Board initial_position;
14899
14900     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14901       DisplayError(_("Bad FEN position in clipboard"), 0);
14902       return ;
14903     } else {
14904       int savedBlackPlaysFirst = blackPlaysFirst;
14905       EditPositionEvent();
14906       blackPlaysFirst = savedBlackPlaysFirst;
14907       CopyBoard(boards[0], initial_position);
14908       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14909       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14910       DisplayBothClocks();
14911       DrawPosition(FALSE, boards[currentMove]);
14912     }
14913   }
14914 }
14915
14916 static char cseq[12] = "\\   ";
14917
14918 Boolean set_cont_sequence(char *new_seq)
14919 {
14920     int len;
14921     Boolean ret;
14922
14923     // handle bad attempts to set the sequence
14924         if (!new_seq)
14925                 return 0; // acceptable error - no debug
14926
14927     len = strlen(new_seq);
14928     ret = (len > 0) && (len < sizeof(cseq));
14929     if (ret)
14930         strcpy(cseq, new_seq);
14931     else if (appData.debugMode)
14932         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14933     return ret;
14934 }
14935
14936 /*
14937     reformat a source message so words don't cross the width boundary.  internal
14938     newlines are not removed.  returns the wrapped size (no null character unless
14939     included in source message).  If dest is NULL, only calculate the size required
14940     for the dest buffer.  lp argument indicats line position upon entry, and it's
14941     passed back upon exit.
14942 */
14943 int wrap(char *dest, char *src, int count, int width, int *lp)
14944 {
14945     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14946
14947     cseq_len = strlen(cseq);
14948     old_line = line = *lp;
14949     ansi = len = clen = 0;
14950
14951     for (i=0; i < count; i++)
14952     {
14953         if (src[i] == '\033')
14954             ansi = 1;
14955
14956         // if we hit the width, back up
14957         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14958         {
14959             // store i & len in case the word is too long
14960             old_i = i, old_len = len;
14961
14962             // find the end of the last word
14963             while (i && src[i] != ' ' && src[i] != '\n')
14964             {
14965                 i--;
14966                 len--;
14967             }
14968
14969             // word too long?  restore i & len before splitting it
14970             if ((old_i-i+clen) >= width)
14971             {
14972                 i = old_i;
14973                 len = old_len;
14974             }
14975
14976             // extra space?
14977             if (i && src[i-1] == ' ')
14978                 len--;
14979
14980             if (src[i] != ' ' && src[i] != '\n')
14981             {
14982                 i--;
14983                 if (len)
14984                     len--;
14985             }
14986
14987             // now append the newline and continuation sequence
14988             if (dest)
14989                 dest[len] = '\n';
14990             len++;
14991             if (dest)
14992                 strncpy(dest+len, cseq, cseq_len);
14993             len += cseq_len;
14994             line = cseq_len;
14995             clen = cseq_len;
14996             continue;
14997         }
14998
14999         if (dest)
15000             dest[len] = src[i];
15001         len++;
15002         if (!ansi)
15003             line++;
15004         if (src[i] == '\n')
15005             line = 0;
15006         if (src[i] == 'm')
15007             ansi = 0;
15008     }
15009     if (dest && appData.debugMode)
15010     {
15011         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15012             count, width, line, len, *lp);
15013         show_bytes(debugFP, src, count);
15014         fprintf(debugFP, "\ndest: ");
15015         show_bytes(debugFP, dest, len);
15016         fprintf(debugFP, "\n");
15017     }
15018     *lp = dest ? line : old_line;
15019
15020     return len;
15021 }
15022
15023 // [HGM] vari: routines for shelving variations
15024
15025 void 
15026 PushTail(int firstMove, int lastMove)
15027 {
15028         int i, j, nrMoves = lastMove - firstMove;
15029
15030         if(appData.icsActive) { // only in local mode
15031                 forwardMostMove = currentMove; // mimic old ICS behavior
15032                 return;
15033         }
15034         if(storedGames >= MAX_VARIATIONS-1) return;
15035
15036         // push current tail of game on stack
15037         savedResult[storedGames] = gameInfo.result;
15038         savedDetails[storedGames] = gameInfo.resultDetails;
15039         gameInfo.resultDetails = NULL;
15040         savedFirst[storedGames] = firstMove;
15041         savedLast [storedGames] = lastMove;
15042         savedFramePtr[storedGames] = framePtr;
15043         framePtr -= nrMoves; // reserve space for the boards
15044         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15045             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15046             for(j=0; j<MOVE_LEN; j++)
15047                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15048             for(j=0; j<2*MOVE_LEN; j++)
15049                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15050             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15051             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15052             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15053             pvInfoList[firstMove+i-1].depth = 0;
15054             commentList[framePtr+i] = commentList[firstMove+i];
15055             commentList[firstMove+i] = NULL;
15056         }
15057
15058         storedGames++;
15059         forwardMostMove = firstMove; // truncate game so we can start variation
15060         if(storedGames == 1) GreyRevert(FALSE);
15061 }
15062
15063 Boolean
15064 PopTail(Boolean annotate)
15065 {
15066         int i, j, nrMoves;
15067         char buf[8000], moveBuf[20];
15068
15069         if(appData.icsActive) return FALSE; // only in local mode
15070         if(!storedGames) return FALSE; // sanity
15071         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15072
15073         storedGames--;
15074         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15075         nrMoves = savedLast[storedGames] - currentMove;
15076         if(annotate) {
15077                 int cnt = 10;
15078                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15079                 else strcpy(buf, "(");
15080                 for(i=currentMove; i<forwardMostMove; i++) {
15081                         if(WhiteOnMove(i))
15082                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15083                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15084                         strcat(buf, moveBuf);
15085                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15086                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15087                 }
15088                 strcat(buf, ")");
15089         }
15090         for(i=1; i<=nrMoves; i++) { // copy last variation back
15091             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15092             for(j=0; j<MOVE_LEN; j++)
15093                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15094             for(j=0; j<2*MOVE_LEN; j++)
15095                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15096             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15097             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15098             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15099             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15100             commentList[currentMove+i] = commentList[framePtr+i];
15101             commentList[framePtr+i] = NULL;
15102         }
15103         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15104         framePtr = savedFramePtr[storedGames];
15105         gameInfo.result = savedResult[storedGames];
15106         if(gameInfo.resultDetails != NULL) {
15107             free(gameInfo.resultDetails);
15108       }
15109         gameInfo.resultDetails = savedDetails[storedGames];
15110         forwardMostMove = currentMove + nrMoves;
15111         if(storedGames == 0) GreyRevert(TRUE);
15112         return TRUE;
15113 }
15114
15115 void 
15116 CleanupTail()
15117 {       // remove all shelved variations
15118         int i;
15119         for(i=0; i<storedGames; i++) {
15120             if(savedDetails[i])
15121                 free(savedDetails[i]);
15122             savedDetails[i] = NULL;
15123         }
15124         for(i=framePtr; i<MAX_MOVES; i++) {
15125                 if(commentList[i]) free(commentList[i]);
15126                 commentList[i] = NULL;
15127         }
15128         framePtr = MAX_MOVES-1;
15129         storedGames = 0;
15130 }
15131
15132 void
15133 LoadVariation(int index, char *text)
15134 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15135         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15136         int level = 0, move;
15137
15138         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15139         // first find outermost bracketing variation
15140         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15141             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15142                 if(*p == '{') wait = '}'; else
15143                 if(*p == '[') wait = ']'; else
15144                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15145                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15146             }
15147             if(*p == wait) wait = NULLCHAR; // closing ]} found
15148             p++;
15149         }
15150         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15151         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15152         end[1] = NULLCHAR; // clip off comment beyond variation
15153         ToNrEvent(currentMove-1);
15154         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15155         // kludge: use ParsePV() to append variation to game
15156         move = currentMove;
15157         ParsePV(start, TRUE);
15158         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15159         ClearPremoveHighlights();
15160         CommentPopDown();
15161         ToNrEvent(currentMove+1);
15162 }