Group Chat Boxes with console in stead of board window
[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 Boolean twoBoards = 0;
250 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
251 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
252 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
253 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
254 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
255 int opponentKibitzes;
256 int lastSavedGame; /* [HGM] save: ID of game */
257 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
258 extern int chatCount;
259 int chattingPartner;
260 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
261
262 /* States for ics_getting_history */
263 #define H_FALSE 0
264 #define H_REQUESTED 1
265 #define H_GOT_REQ_HEADER 2
266 #define H_GOT_UNREQ_HEADER 3
267 #define H_GETTING_MOVES 4
268 #define H_GOT_UNWANTED_HEADER 5
269
270 /* whosays values for GameEnds */
271 #define GE_ICS 0
272 #define GE_ENGINE 1
273 #define GE_PLAYER 2
274 #define GE_FILE 3
275 #define GE_XBOARD 4
276 #define GE_ENGINE1 5
277 #define GE_ENGINE2 6
278
279 /* Maximum number of games in a cmail message */
280 #define CMAIL_MAX_GAMES 20
281
282 /* Different types of move when calling RegisterMove */
283 #define CMAIL_MOVE   0
284 #define CMAIL_RESIGN 1
285 #define CMAIL_DRAW   2
286 #define CMAIL_ACCEPT 3
287
288 /* Different types of result to remember for each game */
289 #define CMAIL_NOT_RESULT 0
290 #define CMAIL_OLD_RESULT 1
291 #define CMAIL_NEW_RESULT 2
292
293 /* Telnet protocol constants */
294 #define TN_WILL 0373
295 #define TN_WONT 0374
296 #define TN_DO   0375
297 #define TN_DONT 0376
298 #define TN_IAC  0377
299 #define TN_ECHO 0001
300 #define TN_SGA  0003
301 #define TN_PORT 23
302
303 /* [AS] */
304 static char * safeStrCpy( char * dst, const char * src, size_t count )
305 {
306     assert( dst != NULL );
307     assert( src != NULL );
308     assert( count > 0 );
309
310     strncpy( dst, src, count );
311     dst[ count-1 ] = '\0';
312     return dst;
313 }
314
315 /* Some compiler can't cast u64 to double
316  * This function do the job for us:
317
318  * We use the highest bit for cast, this only
319  * works if the highest bit is not
320  * in use (This should not happen)
321  *
322  * We used this for all compiler
323  */
324 double
325 u64ToDouble(u64 value)
326 {
327   double r;
328   u64 tmp = value & u64Const(0x7fffffffffffffff);
329   r = (double)(s64)tmp;
330   if (value & u64Const(0x8000000000000000))
331        r +=  9.2233720368547758080e18; /* 2^63 */
332  return r;
333 }
334
335 /* Fake up flags for now, as we aren't keeping track of castling
336    availability yet. [HGM] Change of logic: the flag now only
337    indicates the type of castlings allowed by the rule of the game.
338    The actual rights themselves are maintained in the array
339    castlingRights, as part of the game history, and are not probed
340    by this function.
341  */
342 int
343 PosFlags(index)
344 {
345   int flags = F_ALL_CASTLE_OK;
346   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
347   switch (gameInfo.variant) {
348   case VariantSuicide:
349     flags &= ~F_ALL_CASTLE_OK;
350   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
351     flags |= F_IGNORE_CHECK;
352   case VariantLosers:
353     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
354     break;
355   case VariantAtomic:
356     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
357     break;
358   case VariantKriegspiel:
359     flags |= F_KRIEGSPIEL_CAPTURE;
360     break;
361   case VariantCapaRandom: 
362   case VariantFischeRandom:
363     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
364   case VariantNoCastle:
365   case VariantShatranj:
366   case VariantCourier:
367   case VariantMakruk:
368     flags &= ~F_ALL_CASTLE_OK;
369     break;
370   default:
371     break;
372   }
373   return flags;
374 }
375
376 FILE *gameFileFP, *debugFP;
377
378 /* 
379     [AS] Note: sometimes, the sscanf() function is used to parse the input
380     into a fixed-size buffer. Because of this, we must be prepared to
381     receive strings as long as the size of the input buffer, which is currently
382     set to 4K for Windows and 8K for the rest.
383     So, we must either allocate sufficiently large buffers here, or
384     reduce the size of the input buffer in the input reading part.
385 */
386
387 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
388 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
389 char thinkOutput1[MSG_SIZ*10];
390
391 ChessProgramState first, second;
392
393 /* premove variables */
394 int premoveToX = 0;
395 int premoveToY = 0;
396 int premoveFromX = 0;
397 int premoveFromY = 0;
398 int premovePromoChar = 0;
399 int gotPremove = 0;
400 Boolean alarmSounded;
401 /* end premove variables */
402
403 char *ics_prefix = "$";
404 int ics_type = ICS_GENERIC;
405
406 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
407 int pauseExamForwardMostMove = 0;
408 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
409 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
410 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
411 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
412 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
413 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
414 int whiteFlag = FALSE, blackFlag = FALSE;
415 int userOfferedDraw = FALSE;
416 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
417 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
418 int cmailMoveType[CMAIL_MAX_GAMES];
419 long ics_clock_paused = 0;
420 ProcRef icsPR = NoProc, cmailPR = NoProc;
421 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
422 GameMode gameMode = BeginningOfGame;
423 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
424 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
425 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
426 int hiddenThinkOutputState = 0; /* [AS] */
427 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
428 int adjudicateLossPlies = 6;
429 char white_holding[64], black_holding[64];
430 TimeMark lastNodeCountTime;
431 long lastNodeCount=0;
432 int have_sent_ICS_logon = 0;
433 int movesPerSession;
434 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
435 long timeControl_2; /* [AS] Allow separate time controls */
436 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
437 long timeRemaining[2][MAX_MOVES];
438 int matchGame = 0;
439 TimeMark programStartTime;
440 char ics_handle[MSG_SIZ];
441 int have_set_title = 0;
442
443 /* animateTraining preserves the state of appData.animate
444  * when Training mode is activated. This allows the
445  * response to be animated when appData.animate == TRUE and
446  * appData.animateDragging == TRUE.
447  */
448 Boolean animateTraining;
449
450 GameInfo gameInfo;
451
452 AppData appData;
453
454 Board boards[MAX_MOVES];
455 /* [HGM] Following 7 needed for accurate legality tests: */
456 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
457 signed char  initialRights[BOARD_FILES];
458 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
459 int   initialRulePlies, FENrulePlies;
460 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
461 int loadFlag = 0; 
462 int shuffleOpenings;
463 int mute; // mute all sounds
464
465 // [HGM] vari: next 12 to save and restore variations
466 #define MAX_VARIATIONS 10
467 int framePtr = MAX_MOVES-1; // points to free stack entry
468 int storedGames = 0;
469 int savedFirst[MAX_VARIATIONS];
470 int savedLast[MAX_VARIATIONS];
471 int savedFramePtr[MAX_VARIATIONS];
472 char *savedDetails[MAX_VARIATIONS];
473 ChessMove savedResult[MAX_VARIATIONS];
474
475 void PushTail P((int firstMove, int lastMove));
476 Boolean PopTail P((Boolean annotate));
477 void CleanupTail P((void));
478
479 ChessSquare  FIDEArray[2][BOARD_FILES] = {
480     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
481         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
482     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
483         BlackKing, BlackBishop, BlackKnight, BlackRook }
484 };
485
486 ChessSquare twoKingsArray[2][BOARD_FILES] = {
487     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
488         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
489     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
490         BlackKing, BlackKing, BlackKnight, BlackRook }
491 };
492
493 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
494     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
495         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
496     { BlackRook, BlackMan, BlackBishop, BlackQueen,
497         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
498 };
499
500 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
501     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
504         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
505 };
506
507 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
508     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
509         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
511         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
512 };
513
514 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
515     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
516         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackMan, BlackFerz,
518         BlackKing, BlackMan, BlackKnight, BlackRook }
519 };
520
521
522 #if (BOARD_FILES>=10)
523 ChessSquare ShogiArray[2][BOARD_FILES] = {
524     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
525         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
526     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
527         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
528 };
529
530 ChessSquare XiangqiArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
532         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
534         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
535 };
536
537 ChessSquare CapablancaArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
539         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
540     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
541         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
542 };
543
544 ChessSquare GreatArray[2][BOARD_FILES] = {
545     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
546         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
547     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
548         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
549 };
550
551 ChessSquare JanusArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
553         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
554     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
555         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
556 };
557
558 #ifdef GOTHIC
559 ChessSquare GothicArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
561         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
563         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
564 };
565 #else // !GOTHIC
566 #define GothicArray CapablancaArray
567 #endif // !GOTHIC
568
569 #ifdef FALCON
570 ChessSquare FalconArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
572         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
574         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
575 };
576 #else // !FALCON
577 #define FalconArray CapablancaArray
578 #endif // !FALCON
579
580 #else // !(BOARD_FILES>=10)
581 #define XiangqiPosition FIDEArray
582 #define CapablancaArray FIDEArray
583 #define GothicArray FIDEArray
584 #define GreatArray FIDEArray
585 #endif // !(BOARD_FILES>=10)
586
587 #if (BOARD_FILES>=12)
588 ChessSquare CourierArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
590         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
592         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
593 };
594 #else // !(BOARD_FILES>=12)
595 #define CourierArray CapablancaArray
596 #endif // !(BOARD_FILES>=12)
597
598
599 Board initialPosition;
600
601
602 /* Convert str to a rating. Checks for special cases of "----",
603
604    "++++", etc. Also strips ()'s */
605 int
606 string_to_rating(str)
607   char *str;
608 {
609   while(*str && !isdigit(*str)) ++str;
610   if (!*str)
611     return 0;   /* One of the special "no rating" cases */
612   else
613     return atoi(str);
614 }
615
616 void
617 ClearProgramStats()
618 {
619     /* Init programStats */
620     programStats.movelist[0] = 0;
621     programStats.depth = 0;
622     programStats.nr_moves = 0;
623     programStats.moves_left = 0;
624     programStats.nodes = 0;
625     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
626     programStats.score = 0;
627     programStats.got_only_move = 0;
628     programStats.got_fail = 0;
629     programStats.line_is_book = 0;
630 }
631
632 void
633 InitBackEnd1()
634 {
635     int matched, min, sec;
636
637     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
638
639     GetTimeMark(&programStartTime);
640     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
641
642     ClearProgramStats();
643     programStats.ok_to_send = 1;
644     programStats.seen_stat = 0;
645
646     /*
647      * Initialize game list
648      */
649     ListNew(&gameList);
650
651
652     /*
653      * Internet chess server status
654      */
655     if (appData.icsActive) {
656         appData.matchMode = FALSE;
657         appData.matchGames = 0;
658 #if ZIPPY       
659         appData.noChessProgram = !appData.zippyPlay;
660 #else
661         appData.zippyPlay = FALSE;
662         appData.zippyTalk = FALSE;
663         appData.noChessProgram = TRUE;
664 #endif
665         if (*appData.icsHelper != NULLCHAR) {
666             appData.useTelnet = TRUE;
667             appData.telnetProgram = appData.icsHelper;
668         }
669     } else {
670         appData.zippyTalk = appData.zippyPlay = FALSE;
671     }
672
673     /* [AS] Initialize pv info list [HGM] and game state */
674     {
675         int i, j;
676
677         for( i=0; i<=framePtr; i++ ) {
678             pvInfoList[i].depth = -1;
679             boards[i][EP_STATUS] = EP_NONE;
680             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
681         }
682     }
683
684     /*
685      * Parse timeControl resource
686      */
687     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
688                           appData.movesPerSession)) {
689         char buf[MSG_SIZ];
690         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
691         DisplayFatalError(buf, 0, 2);
692     }
693
694     /*
695      * Parse searchTime resource
696      */
697     if (*appData.searchTime != NULLCHAR) {
698         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
699         if (matched == 1) {
700             searchTime = min * 60;
701         } else if (matched == 2) {
702             searchTime = min * 60 + sec;
703         } else {
704             char buf[MSG_SIZ];
705             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
706             DisplayFatalError(buf, 0, 2);
707         }
708     }
709
710     /* [AS] Adjudication threshold */
711     adjudicateLossThreshold = appData.adjudicateLossThreshold;
712     
713     first.which = "first";
714     second.which = "second";
715     first.maybeThinking = second.maybeThinking = FALSE;
716     first.pr = second.pr = NoProc;
717     first.isr = second.isr = NULL;
718     first.sendTime = second.sendTime = 2;
719     first.sendDrawOffers = 1;
720     if (appData.firstPlaysBlack) {
721         first.twoMachinesColor = "black\n";
722         second.twoMachinesColor = "white\n";
723     } else {
724         first.twoMachinesColor = "white\n";
725         second.twoMachinesColor = "black\n";
726     }
727     first.program = appData.firstChessProgram;
728     second.program = appData.secondChessProgram;
729     first.host = appData.firstHost;
730     second.host = appData.secondHost;
731     first.dir = appData.firstDirectory;
732     second.dir = appData.secondDirectory;
733     first.other = &second;
734     second.other = &first;
735     first.initString = appData.initString;
736     second.initString = appData.secondInitString;
737     first.computerString = appData.firstComputerString;
738     second.computerString = appData.secondComputerString;
739     first.useSigint = second.useSigint = TRUE;
740     first.useSigterm = second.useSigterm = TRUE;
741     first.reuse = appData.reuseFirst;
742     second.reuse = appData.reuseSecond;
743     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
744     second.nps = appData.secondNPS;
745     first.useSetboard = second.useSetboard = FALSE;
746     first.useSAN = second.useSAN = FALSE;
747     first.usePing = second.usePing = FALSE;
748     first.lastPing = second.lastPing = 0;
749     first.lastPong = second.lastPong = 0;
750     first.usePlayother = second.usePlayother = FALSE;
751     first.useColors = second.useColors = TRUE;
752     first.useUsermove = second.useUsermove = FALSE;
753     first.sendICS = second.sendICS = FALSE;
754     first.sendName = second.sendName = appData.icsActive;
755     first.sdKludge = second.sdKludge = FALSE;
756     first.stKludge = second.stKludge = FALSE;
757     TidyProgramName(first.program, first.host, first.tidy);
758     TidyProgramName(second.program, second.host, second.tidy);
759     first.matchWins = second.matchWins = 0;
760     strcpy(first.variants, appData.variant);
761     strcpy(second.variants, appData.variant);
762     first.analysisSupport = second.analysisSupport = 2; /* detect */
763     first.analyzing = second.analyzing = FALSE;
764     first.initDone = second.initDone = FALSE;
765
766     /* New features added by Tord: */
767     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
768     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
769     /* End of new features added by Tord. */
770     first.fenOverride  = appData.fenOverride1;
771     second.fenOverride = appData.fenOverride2;
772
773     /* [HGM] time odds: set factor for each machine */
774     first.timeOdds  = appData.firstTimeOdds;
775     second.timeOdds = appData.secondTimeOdds;
776     { float norm = 1;
777         if(appData.timeOddsMode) {
778             norm = first.timeOdds;
779             if(norm > second.timeOdds) norm = second.timeOdds;
780         }
781         first.timeOdds /= norm;
782         second.timeOdds /= norm;
783     }
784
785     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
786     first.accumulateTC = appData.firstAccumulateTC;
787     second.accumulateTC = appData.secondAccumulateTC;
788     first.maxNrOfSessions = second.maxNrOfSessions = 1;
789
790     /* [HGM] debug */
791     first.debug = second.debug = FALSE;
792     first.supportsNPS = second.supportsNPS = UNKNOWN;
793
794     /* [HGM] options */
795     first.optionSettings  = appData.firstOptions;
796     second.optionSettings = appData.secondOptions;
797
798     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
799     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
800     first.isUCI = appData.firstIsUCI; /* [AS] */
801     second.isUCI = appData.secondIsUCI; /* [AS] */
802     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
803     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
804
805     if (appData.firstProtocolVersion > PROTOVER ||
806         appData.firstProtocolVersion < 1) {
807       char buf[MSG_SIZ];
808       sprintf(buf, _("protocol version %d not supported"),
809               appData.firstProtocolVersion);
810       DisplayFatalError(buf, 0, 2);
811     } else {
812       first.protocolVersion = appData.firstProtocolVersion;
813     }
814
815     if (appData.secondProtocolVersion > PROTOVER ||
816         appData.secondProtocolVersion < 1) {
817       char buf[MSG_SIZ];
818       sprintf(buf, _("protocol version %d not supported"),
819               appData.secondProtocolVersion);
820       DisplayFatalError(buf, 0, 2);
821     } else {
822       second.protocolVersion = appData.secondProtocolVersion;
823     }
824
825     if (appData.icsActive) {
826         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
827 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
828     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
829         appData.clockMode = FALSE;
830         first.sendTime = second.sendTime = 0;
831     }
832     
833 #if ZIPPY
834     /* Override some settings from environment variables, for backward
835        compatibility.  Unfortunately it's not feasible to have the env
836        vars just set defaults, at least in xboard.  Ugh.
837     */
838     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
839       ZippyInit();
840     }
841 #endif
842     
843     if (appData.noChessProgram) {
844         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
845         sprintf(programVersion, "%s", PACKAGE_STRING);
846     } else {
847       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
848       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
849       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850     }
851
852     if (!appData.icsActive) {
853       char buf[MSG_SIZ];
854       /* Check for variants that are supported only in ICS mode,
855          or not at all.  Some that are accepted here nevertheless
856          have bugs; see comments below.
857       */
858       VariantClass variant = StringToVariant(appData.variant);
859       switch (variant) {
860       case VariantBughouse:     /* need four players and two boards */
861       case VariantKriegspiel:   /* need to hide pieces and move details */
862       /* case VariantFischeRandom: (Fabien: moved below) */
863         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
864         DisplayFatalError(buf, 0, 2);
865         return;
866
867       case VariantUnknown:
868       case VariantLoadable:
869       case Variant29:
870       case Variant30:
871       case Variant31:
872       case Variant32:
873       case Variant33:
874       case Variant34:
875       case Variant35:
876       case Variant36:
877       default:
878         sprintf(buf, _("Unknown variant name %s"), appData.variant);
879         DisplayFatalError(buf, 0, 2);
880         return;
881
882       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
883       case VariantFairy:      /* [HGM] TestLegality definitely off! */
884       case VariantGothic:     /* [HGM] should work */
885       case VariantCapablanca: /* [HGM] should work */
886       case VariantCourier:    /* [HGM] initial forced moves not implemented */
887       case VariantShogi:      /* [HGM] drops not tested for legality */
888       case VariantKnightmate: /* [HGM] should work */
889       case VariantCylinder:   /* [HGM] untested */
890       case VariantFalcon:     /* [HGM] untested */
891       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
892                                  offboard interposition not understood */
893       case VariantNormal:     /* definitely works! */
894       case VariantWildCastle: /* pieces not automatically shuffled */
895       case VariantNoCastle:   /* pieces not automatically shuffled */
896       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
897       case VariantLosers:     /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantSuicide:    /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantGiveaway:   /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantTwoKings:   /* should work */
904       case VariantAtomic:     /* should work except for win condition */
905       case Variant3Check:     /* should work except for win condition */
906       case VariantShatranj:   /* should work except for all win conditions */
907       case VariantMakruk:     /* should work except for daw countdown */
908       case VariantBerolina:   /* might work if TestLegality is off */
909       case VariantCapaRandom: /* should work */
910       case VariantJanus:      /* should work */
911       case VariantSuper:      /* experimental */
912       case VariantGreat:      /* experimental, requires legality testing to be off */
913         break;
914       }
915     }
916
917     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
918     InitEngineUCI( installDir, &second );
919 }
920
921 int NextIntegerFromString( char ** str, long * value )
922 {
923     int result = -1;
924     char * s = *str;
925
926     while( *s == ' ' || *s == '\t' ) {
927         s++;
928     }
929
930     *value = 0;
931
932     if( *s >= '0' && *s <= '9' ) {
933         while( *s >= '0' && *s <= '9' ) {
934             *value = *value * 10 + (*s - '0');
935             s++;
936         }
937
938         result = 0;
939     }
940
941     *str = s;
942
943     return result;
944 }
945
946 int NextTimeControlFromString( char ** str, long * value )
947 {
948     long temp;
949     int result = NextIntegerFromString( str, &temp );
950
951     if( result == 0 ) {
952         *value = temp * 60; /* Minutes */
953         if( **str == ':' ) {
954             (*str)++;
955             result = NextIntegerFromString( str, &temp );
956             *value += temp; /* Seconds */
957         }
958     }
959
960     return result;
961 }
962
963 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
964 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
965     int result = -1; long temp, temp2;
966
967     if(**str != '+') return -1; // old params remain in force!
968     (*str)++;
969     if( NextTimeControlFromString( str, &temp ) ) return -1;
970
971     if(**str != '/') {
972         /* time only: incremental or sudden-death time control */
973         if(**str == '+') { /* increment follows; read it */
974             (*str)++;
975             if(result = NextIntegerFromString( str, &temp2)) return -1;
976             *inc = temp2 * 1000;
977         } else *inc = 0;
978         *moves = 0; *tc = temp * 1000; 
979         return 0;
980     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
981
982     (*str)++; /* classical time control */
983     result = NextTimeControlFromString( str, &temp2);
984     if(result == 0) {
985         *moves = temp/60;
986         *tc    = temp2 * 1000;
987         *inc   = 0;
988     }
989     return result;
990 }
991
992 int GetTimeQuota(int movenr)
993 {   /* [HGM] get time to add from the multi-session time-control string */
994     int moves=1; /* kludge to force reading of first session */
995     long time, increment;
996     char *s = fullTimeControlString;
997
998     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
999     do {
1000         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1001         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1002         if(movenr == -1) return time;    /* last move before new session     */
1003         if(!moves) return increment;     /* current session is incremental   */
1004         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1005     } while(movenr >= -1);               /* try again for next session       */
1006
1007     return 0; // no new time quota on this move
1008 }
1009
1010 int
1011 ParseTimeControl(tc, ti, mps)
1012      char *tc;
1013      int ti;
1014      int mps;
1015 {
1016   long tc1;
1017   long tc2;
1018   char buf[MSG_SIZ];
1019   
1020   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1021   if(ti > 0) {
1022     if(mps)
1023       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1024     else sprintf(buf, "+%s+%d", tc, ti);
1025   } else {
1026     if(mps)
1027              sprintf(buf, "+%d/%s", mps, tc);
1028     else sprintf(buf, "+%s", tc);
1029   }
1030   fullTimeControlString = StrSave(buf);
1031   
1032   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1033     return FALSE;
1034   }
1035   
1036   if( *tc == '/' ) {
1037     /* Parse second time control */
1038     tc++;
1039     
1040     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1041       return FALSE;
1042     }
1043     
1044     if( tc2 == 0 ) {
1045       return FALSE;
1046     }
1047     
1048     timeControl_2 = tc2 * 1000;
1049   }
1050   else {
1051     timeControl_2 = 0;
1052   }
1053   
1054   if( tc1 == 0 ) {
1055     return FALSE;
1056   }
1057   
1058   timeControl = tc1 * 1000;
1059   
1060   if (ti >= 0) {
1061     timeIncrement = ti * 1000;  /* convert to ms */
1062     movesPerSession = 0;
1063   } else {
1064     timeIncrement = 0;
1065     movesPerSession = mps;
1066   }
1067   return TRUE;
1068 }
1069
1070 void
1071 InitBackEnd2()
1072 {
1073     if (appData.debugMode) {
1074         fprintf(debugFP, "%s\n", programVersion);
1075     }
1076
1077     set_cont_sequence(appData.wrapContSeq);
1078     if (appData.matchGames > 0) {
1079         appData.matchMode = TRUE;
1080     } else if (appData.matchMode) {
1081         appData.matchGames = 1;
1082     }
1083     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1084         appData.matchGames = appData.sameColorGames;
1085     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1086         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1087         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1088     }
1089     Reset(TRUE, FALSE);
1090     if (appData.noChessProgram || first.protocolVersion == 1) {
1091       InitBackEnd3();
1092     } else {
1093       /* kludge: allow timeout for initial "feature" commands */
1094       FreezeUI();
1095       DisplayMessage("", _("Starting chess program"));
1096       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1097     }
1098 }
1099
1100 void
1101 InitBackEnd3 P((void))
1102 {
1103     GameMode initialMode;
1104     char buf[MSG_SIZ];
1105     int err;
1106
1107     InitChessProgram(&first, startedFromSetupPosition);
1108
1109
1110     if (appData.icsActive) {
1111 #ifdef WIN32
1112         /* [DM] Make a console window if needed [HGM] merged ifs */
1113         ConsoleCreate(); 
1114 #endif
1115         err = establish();
1116         if (err != 0) {
1117             if (*appData.icsCommPort != NULLCHAR) {
1118                 sprintf(buf, _("Could not open comm port %s"),  
1119                         appData.icsCommPort);
1120             } else {
1121                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1122                         appData.icsHost, appData.icsPort);
1123             }
1124             DisplayFatalError(buf, err, 1);
1125             return;
1126         }
1127         SetICSMode();
1128         telnetISR =
1129           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1130         fromUserISR =
1131           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1132         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1133             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1134     } else if (appData.noChessProgram) {
1135         SetNCPMode();
1136     } else {
1137         SetGNUMode();
1138     }
1139
1140     if (*appData.cmailGameName != NULLCHAR) {
1141         SetCmailMode();
1142         OpenLoopback(&cmailPR);
1143         cmailISR =
1144           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1145     }
1146     
1147     ThawUI();
1148     DisplayMessage("", "");
1149     if (StrCaseCmp(appData.initialMode, "") == 0) {
1150       initialMode = BeginningOfGame;
1151     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1152       initialMode = TwoMachinesPlay;
1153     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1154       initialMode = AnalyzeFile; 
1155     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1156       initialMode = AnalyzeMode;
1157     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1158       initialMode = MachinePlaysWhite;
1159     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1160       initialMode = MachinePlaysBlack;
1161     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1162       initialMode = EditGame;
1163     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1164       initialMode = EditPosition;
1165     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1166       initialMode = Training;
1167     } else {
1168       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1169       DisplayFatalError(buf, 0, 2);
1170       return;
1171     }
1172
1173     if (appData.matchMode) {
1174         /* Set up machine vs. machine match */
1175         if (appData.noChessProgram) {
1176             DisplayFatalError(_("Can't have a match with no chess programs"),
1177                               0, 2);
1178             return;
1179         }
1180         matchMode = TRUE;
1181         matchGame = 1;
1182         if (*appData.loadGameFile != NULLCHAR) {
1183             int index = appData.loadGameIndex; // [HGM] autoinc
1184             if(index<0) lastIndex = index = 1;
1185             if (!LoadGameFromFile(appData.loadGameFile,
1186                                   index,
1187                                   appData.loadGameFile, FALSE)) {
1188                 DisplayFatalError(_("Bad game file"), 0, 1);
1189                 return;
1190             }
1191         } else if (*appData.loadPositionFile != NULLCHAR) {
1192             int index = appData.loadPositionIndex; // [HGM] autoinc
1193             if(index<0) lastIndex = index = 1;
1194             if (!LoadPositionFromFile(appData.loadPositionFile,
1195                                       index,
1196                                       appData.loadPositionFile)) {
1197                 DisplayFatalError(_("Bad position file"), 0, 1);
1198                 return;
1199             }
1200         }
1201         TwoMachinesEvent();
1202     } else if (*appData.cmailGameName != NULLCHAR) {
1203         /* Set up cmail mode */
1204         ReloadCmailMsgEvent(TRUE);
1205     } else {
1206         /* Set up other modes */
1207         if (initialMode == AnalyzeFile) {
1208           if (*appData.loadGameFile == NULLCHAR) {
1209             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1210             return;
1211           }
1212         }
1213         if (*appData.loadGameFile != NULLCHAR) {
1214             (void) LoadGameFromFile(appData.loadGameFile,
1215                                     appData.loadGameIndex,
1216                                     appData.loadGameFile, TRUE);
1217         } else if (*appData.loadPositionFile != NULLCHAR) {
1218             (void) LoadPositionFromFile(appData.loadPositionFile,
1219                                         appData.loadPositionIndex,
1220                                         appData.loadPositionFile);
1221             /* [HGM] try to make self-starting even after FEN load */
1222             /* to allow automatic setup of fairy variants with wtm */
1223             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1224                 gameMode = BeginningOfGame;
1225                 setboardSpoiledMachineBlack = 1;
1226             }
1227             /* [HGM] loadPos: make that every new game uses the setup */
1228             /* from file as long as we do not switch variant          */
1229             if(!blackPlaysFirst) {
1230                 startedFromPositionFile = TRUE;
1231                 CopyBoard(filePosition, boards[0]);
1232             }
1233         }
1234         if (initialMode == AnalyzeMode) {
1235           if (appData.noChessProgram) {
1236             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1237             return;
1238           }
1239           if (appData.icsActive) {
1240             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1241             return;
1242           }
1243           AnalyzeModeEvent();
1244         } else if (initialMode == AnalyzeFile) {
1245           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1246           ShowThinkingEvent();
1247           AnalyzeFileEvent();
1248           AnalysisPeriodicEvent(1);
1249         } else if (initialMode == MachinePlaysWhite) {
1250           if (appData.noChessProgram) {
1251             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1252                               0, 2);
1253             return;
1254           }
1255           if (appData.icsActive) {
1256             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1257                               0, 2);
1258             return;
1259           }
1260           MachineWhiteEvent();
1261         } else if (initialMode == MachinePlaysBlack) {
1262           if (appData.noChessProgram) {
1263             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1264                               0, 2);
1265             return;
1266           }
1267           if (appData.icsActive) {
1268             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1269                               0, 2);
1270             return;
1271           }
1272           MachineBlackEvent();
1273         } else if (initialMode == TwoMachinesPlay) {
1274           if (appData.noChessProgram) {
1275             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1276                               0, 2);
1277             return;
1278           }
1279           if (appData.icsActive) {
1280             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1281                               0, 2);
1282             return;
1283           }
1284           TwoMachinesEvent();
1285         } else if (initialMode == EditGame) {
1286           EditGameEvent();
1287         } else if (initialMode == EditPosition) {
1288           EditPositionEvent();
1289         } else if (initialMode == Training) {
1290           if (*appData.loadGameFile == NULLCHAR) {
1291             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1292             return;
1293           }
1294           TrainingEvent();
1295         }
1296     }
1297 }
1298
1299 /*
1300  * Establish will establish a contact to a remote host.port.
1301  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1302  *  used to talk to the host.
1303  * Returns 0 if okay, error code if not.
1304  */
1305 int
1306 establish()
1307 {
1308     char buf[MSG_SIZ];
1309
1310     if (*appData.icsCommPort != NULLCHAR) {
1311         /* Talk to the host through a serial comm port */
1312         return OpenCommPort(appData.icsCommPort, &icsPR);
1313
1314     } else if (*appData.gateway != NULLCHAR) {
1315         if (*appData.remoteShell == NULLCHAR) {
1316             /* Use the rcmd protocol to run telnet program on a gateway host */
1317             snprintf(buf, sizeof(buf), "%s %s %s",
1318                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1319             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1320
1321         } else {
1322             /* Use the rsh program to run telnet program on a gateway host */
1323             if (*appData.remoteUser == NULLCHAR) {
1324                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1325                         appData.gateway, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             } else {
1328                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1329                         appData.remoteShell, appData.gateway, 
1330                         appData.remoteUser, appData.telnetProgram,
1331                         appData.icsHost, appData.icsPort);
1332             }
1333             return StartChildProcess(buf, "", &icsPR);
1334
1335         }
1336     } else if (appData.useTelnet) {
1337         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1338
1339     } else {
1340         /* TCP socket interface differs somewhat between
1341            Unix and NT; handle details in the front end.
1342            */
1343         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1344     }
1345 }
1346
1347 void
1348 show_bytes(fp, buf, count)
1349      FILE *fp;
1350      char *buf;
1351      int count;
1352 {
1353     while (count--) {
1354         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1355             fprintf(fp, "\\%03o", *buf & 0xff);
1356         } else {
1357             putc(*buf, fp);
1358         }
1359         buf++;
1360     }
1361     fflush(fp);
1362 }
1363
1364 /* Returns an errno value */
1365 int
1366 OutputMaybeTelnet(pr, message, count, outError)
1367      ProcRef pr;
1368      char *message;
1369      int count;
1370      int *outError;
1371 {
1372     char buf[8192], *p, *q, *buflim;
1373     int left, newcount, outcount;
1374
1375     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1376         *appData.gateway != NULLCHAR) {
1377         if (appData.debugMode) {
1378             fprintf(debugFP, ">ICS: ");
1379             show_bytes(debugFP, message, count);
1380             fprintf(debugFP, "\n");
1381         }
1382         return OutputToProcess(pr, message, count, outError);
1383     }
1384
1385     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1386     p = message;
1387     q = buf;
1388     left = count;
1389     newcount = 0;
1390     while (left) {
1391         if (q >= buflim) {
1392             if (appData.debugMode) {
1393                 fprintf(debugFP, ">ICS: ");
1394                 show_bytes(debugFP, buf, newcount);
1395                 fprintf(debugFP, "\n");
1396             }
1397             outcount = OutputToProcess(pr, buf, newcount, outError);
1398             if (outcount < newcount) return -1; /* to be sure */
1399             q = buf;
1400             newcount = 0;
1401         }
1402         if (*p == '\n') {
1403             *q++ = '\r';
1404             newcount++;
1405         } else if (((unsigned char) *p) == TN_IAC) {
1406             *q++ = (char) TN_IAC;
1407             newcount ++;
1408         }
1409         *q++ = *p++;
1410         newcount++;
1411         left--;
1412     }
1413     if (appData.debugMode) {
1414         fprintf(debugFP, ">ICS: ");
1415         show_bytes(debugFP, buf, newcount);
1416         fprintf(debugFP, "\n");
1417     }
1418     outcount = OutputToProcess(pr, buf, newcount, outError);
1419     if (outcount < newcount) return -1; /* to be sure */
1420     return count;
1421 }
1422
1423 void
1424 read_from_player(isr, closure, message, count, error)
1425      InputSourceRef isr;
1426      VOIDSTAR closure;
1427      char *message;
1428      int count;
1429      int error;
1430 {
1431     int outError, outCount;
1432     static int gotEof = 0;
1433
1434     /* Pass data read from player on to ICS */
1435     if (count > 0) {
1436         gotEof = 0;
1437         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1438         if (outCount < count) {
1439             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1440         }
1441     } else if (count < 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1444     } else if (gotEof++ > 0) {
1445         RemoveInputSource(isr);
1446         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447     }
1448 }
1449
1450 void
1451 KeepAlive()
1452 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1453     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1454     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1455     SendToICS("date\n");
1456     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1457 }
1458
1459 /* added routine for printf style output to ics */
1460 void ics_printf(char *format, ...)
1461 {
1462     char buffer[MSG_SIZ];
1463     va_list args;
1464
1465     va_start(args, format);
1466     vsnprintf(buffer, sizeof(buffer), format, args);
1467     buffer[sizeof(buffer)-1] = '\0';
1468     SendToICS(buffer);
1469     va_end(args);
1470 }
1471
1472 void
1473 SendToICS(s)
1474      char *s;
1475 {
1476     int count, outCount, outError;
1477
1478     if (icsPR == NULL) return;
1479
1480     count = strlen(s);
1481     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1482     if (outCount < count) {
1483         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1484     }
1485 }
1486
1487 /* This is used for sending logon scripts to the ICS. Sending
1488    without a delay causes problems when using timestamp on ICC
1489    (at least on my machine). */
1490 void
1491 SendToICSDelayed(s,msdelay)
1492      char *s;
1493      long msdelay;
1494 {
1495     int count, outCount, outError;
1496
1497     if (icsPR == NULL) return;
1498
1499     count = strlen(s);
1500     if (appData.debugMode) {
1501         fprintf(debugFP, ">ICS: ");
1502         show_bytes(debugFP, s, count);
1503         fprintf(debugFP, "\n");
1504     }
1505     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1506                                       msdelay);
1507     if (outCount < count) {
1508         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1509     }
1510 }
1511
1512
1513 /* Remove all highlighting escape sequences in s
1514    Also deletes any suffix starting with '(' 
1515    */
1516 char *
1517 StripHighlightAndTitle(s)
1518      char *s;
1519 {
1520     static char retbuf[MSG_SIZ];
1521     char *p = retbuf;
1522
1523     while (*s != NULLCHAR) {
1524         while (*s == '\033') {
1525             while (*s != NULLCHAR && !isalpha(*s)) s++;
1526             if (*s != NULLCHAR) s++;
1527         }
1528         while (*s != NULLCHAR && *s != '\033') {
1529             if (*s == '(' || *s == '[') {
1530                 *p = NULLCHAR;
1531                 return retbuf;
1532             }
1533             *p++ = *s++;
1534         }
1535     }
1536     *p = NULLCHAR;
1537     return retbuf;
1538 }
1539
1540 /* Remove all highlighting escape sequences in s */
1541 char *
1542 StripHighlight(s)
1543      char *s;
1544 {
1545     static char retbuf[MSG_SIZ];
1546     char *p = retbuf;
1547
1548     while (*s != NULLCHAR) {
1549         while (*s == '\033') {
1550             while (*s != NULLCHAR && !isalpha(*s)) s++;
1551             if (*s != NULLCHAR) s++;
1552         }
1553         while (*s != NULLCHAR && *s != '\033') {
1554             *p++ = *s++;
1555         }
1556     }
1557     *p = NULLCHAR;
1558     return retbuf;
1559 }
1560
1561 char *variantNames[] = VARIANT_NAMES;
1562 char *
1563 VariantName(v)
1564      VariantClass v;
1565 {
1566     return variantNames[v];
1567 }
1568
1569
1570 /* Identify a variant from the strings the chess servers use or the
1571    PGN Variant tag names we use. */
1572 VariantClass
1573 StringToVariant(e)
1574      char *e;
1575 {
1576     char *p;
1577     int wnum = -1;
1578     VariantClass v = VariantNormal;
1579     int i, found = FALSE;
1580     char buf[MSG_SIZ];
1581
1582     if (!e) return v;
1583
1584     /* [HGM] skip over optional board-size prefixes */
1585     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1586         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1587         while( *e++ != '_');
1588     }
1589
1590     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1591         v = VariantNormal;
1592         found = TRUE;
1593     } else
1594     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1595       if (StrCaseStr(e, variantNames[i])) {
1596         v = (VariantClass) i;
1597         found = TRUE;
1598         break;
1599       }
1600     }
1601
1602     if (!found) {
1603       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1604           || StrCaseStr(e, "wild/fr") 
1605           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1606         v = VariantFischeRandom;
1607       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1608                  (i = 1, p = StrCaseStr(e, "w"))) {
1609         p += i;
1610         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1611         if (isdigit(*p)) {
1612           wnum = atoi(p);
1613         } else {
1614           wnum = -1;
1615         }
1616         switch (wnum) {
1617         case 0: /* FICS only, actually */
1618         case 1:
1619           /* Castling legal even if K starts on d-file */
1620           v = VariantWildCastle;
1621           break;
1622         case 2:
1623         case 3:
1624         case 4:
1625           /* Castling illegal even if K & R happen to start in
1626              normal positions. */
1627           v = VariantNoCastle;
1628           break;
1629         case 5:
1630         case 7:
1631         case 8:
1632         case 10:
1633         case 11:
1634         case 12:
1635         case 13:
1636         case 14:
1637         case 15:
1638         case 18:
1639         case 19:
1640           /* Castling legal iff K & R start in normal positions */
1641           v = VariantNormal;
1642           break;
1643         case 6:
1644         case 20:
1645         case 21:
1646           /* Special wilds for position setup; unclear what to do here */
1647           v = VariantLoadable;
1648           break;
1649         case 9:
1650           /* Bizarre ICC game */
1651           v = VariantTwoKings;
1652           break;
1653         case 16:
1654           v = VariantKriegspiel;
1655           break;
1656         case 17:
1657           v = VariantLosers;
1658           break;
1659         case 22:
1660           v = VariantFischeRandom;
1661           break;
1662         case 23:
1663           v = VariantCrazyhouse;
1664           break;
1665         case 24:
1666           v = VariantBughouse;
1667           break;
1668         case 25:
1669           v = Variant3Check;
1670           break;
1671         case 26:
1672           /* Not quite the same as FICS suicide! */
1673           v = VariantGiveaway;
1674           break;
1675         case 27:
1676           v = VariantAtomic;
1677           break;
1678         case 28:
1679           v = VariantShatranj;
1680           break;
1681
1682         /* Temporary names for future ICC types.  The name *will* change in 
1683            the next xboard/WinBoard release after ICC defines it. */
1684         case 29:
1685           v = Variant29;
1686           break;
1687         case 30:
1688           v = Variant30;
1689           break;
1690         case 31:
1691           v = Variant31;
1692           break;
1693         case 32:
1694           v = Variant32;
1695           break;
1696         case 33:
1697           v = Variant33;
1698           break;
1699         case 34:
1700           v = Variant34;
1701           break;
1702         case 35:
1703           v = Variant35;
1704           break;
1705         case 36:
1706           v = Variant36;
1707           break;
1708         case 37:
1709           v = VariantShogi;
1710           break;
1711         case 38:
1712           v = VariantXiangqi;
1713           break;
1714         case 39:
1715           v = VariantCourier;
1716           break;
1717         case 40:
1718           v = VariantGothic;
1719           break;
1720         case 41:
1721           v = VariantCapablanca;
1722           break;
1723         case 42:
1724           v = VariantKnightmate;
1725           break;
1726         case 43:
1727           v = VariantFairy;
1728           break;
1729         case 44:
1730           v = VariantCylinder;
1731           break;
1732         case 45:
1733           v = VariantFalcon;
1734           break;
1735         case 46:
1736           v = VariantCapaRandom;
1737           break;
1738         case 47:
1739           v = VariantBerolina;
1740           break;
1741         case 48:
1742           v = VariantJanus;
1743           break;
1744         case 49:
1745           v = VariantSuper;
1746           break;
1747         case 50:
1748           v = VariantGreat;
1749           break;
1750         case -1:
1751           /* Found "wild" or "w" in the string but no number;
1752              must assume it's normal chess. */
1753           v = VariantNormal;
1754           break;
1755         default:
1756           sprintf(buf, _("Unknown wild type %d"), wnum);
1757           DisplayError(buf, 0);
1758           v = VariantUnknown;
1759           break;
1760         }
1761       }
1762     }
1763     if (appData.debugMode) {
1764       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1765               e, wnum, VariantName(v));
1766     }
1767     return v;
1768 }
1769
1770 static int leftover_start = 0, leftover_len = 0;
1771 char star_match[STAR_MATCH_N][MSG_SIZ];
1772
1773 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1774    advance *index beyond it, and set leftover_start to the new value of
1775    *index; else return FALSE.  If pattern contains the character '*', it
1776    matches any sequence of characters not containing '\r', '\n', or the
1777    character following the '*' (if any), and the matched sequence(s) are
1778    copied into star_match.
1779    */
1780 int
1781 looking_at(buf, index, pattern)
1782      char *buf;
1783      int *index;
1784      char *pattern;
1785 {
1786     char *bufp = &buf[*index], *patternp = pattern;
1787     int star_count = 0;
1788     char *matchp = star_match[0];
1789     
1790     for (;;) {
1791         if (*patternp == NULLCHAR) {
1792             *index = leftover_start = bufp - buf;
1793             *matchp = NULLCHAR;
1794             return TRUE;
1795         }
1796         if (*bufp == NULLCHAR) return FALSE;
1797         if (*patternp == '*') {
1798             if (*bufp == *(patternp + 1)) {
1799                 *matchp = NULLCHAR;
1800                 matchp = star_match[++star_count];
1801                 patternp += 2;
1802                 bufp++;
1803                 continue;
1804             } else if (*bufp == '\n' || *bufp == '\r') {
1805                 patternp++;
1806                 if (*patternp == NULLCHAR)
1807                   continue;
1808                 else
1809                   return FALSE;
1810             } else {
1811                 *matchp++ = *bufp++;
1812                 continue;
1813             }
1814         }
1815         if (*patternp != *bufp) return FALSE;
1816         patternp++;
1817         bufp++;
1818     }
1819 }
1820
1821 void
1822 SendToPlayer(data, length)
1823      char *data;
1824      int length;
1825 {
1826     int error, outCount;
1827     outCount = OutputToProcess(NoProc, data, length, &error);
1828     if (outCount < length) {
1829         DisplayFatalError(_("Error writing to display"), error, 1);
1830     }
1831 }
1832
1833 void
1834 PackHolding(packed, holding)
1835      char packed[];
1836      char *holding;
1837 {
1838     char *p = holding;
1839     char *q = packed;
1840     int runlength = 0;
1841     int curr = 9999;
1842     do {
1843         if (*p == curr) {
1844             runlength++;
1845         } else {
1846             switch (runlength) {
1847               case 0:
1848                 break;
1849               case 1:
1850                 *q++ = curr;
1851                 break;
1852               case 2:
1853                 *q++ = curr;
1854                 *q++ = curr;
1855                 break;
1856               default:
1857                 sprintf(q, "%d", runlength);
1858                 while (*q) q++;
1859                 *q++ = curr;
1860                 break;
1861             }
1862             runlength = 1;
1863             curr = *p;
1864         }
1865     } while (*p++);
1866     *q = NULLCHAR;
1867 }
1868
1869 /* Telnet protocol requests from the front end */
1870 void
1871 TelnetRequest(ddww, option)
1872      unsigned char ddww, option;
1873 {
1874     unsigned char msg[3];
1875     int outCount, outError;
1876
1877     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1878
1879     if (appData.debugMode) {
1880         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1881         switch (ddww) {
1882           case TN_DO:
1883             ddwwStr = "DO";
1884             break;
1885           case TN_DONT:
1886             ddwwStr = "DONT";
1887             break;
1888           case TN_WILL:
1889             ddwwStr = "WILL";
1890             break;
1891           case TN_WONT:
1892             ddwwStr = "WONT";
1893             break;
1894           default:
1895             ddwwStr = buf1;
1896             sprintf(buf1, "%d", ddww);
1897             break;
1898         }
1899         switch (option) {
1900           case TN_ECHO:
1901             optionStr = "ECHO";
1902             break;
1903           default:
1904             optionStr = buf2;
1905             sprintf(buf2, "%d", option);
1906             break;
1907         }
1908         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1909     }
1910     msg[0] = TN_IAC;
1911     msg[1] = ddww;
1912     msg[2] = option;
1913     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1914     if (outCount < 3) {
1915         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916     }
1917 }
1918
1919 void
1920 DoEcho()
1921 {
1922     if (!appData.icsActive) return;
1923     TelnetRequest(TN_DO, TN_ECHO);
1924 }
1925
1926 void
1927 DontEcho()
1928 {
1929     if (!appData.icsActive) return;
1930     TelnetRequest(TN_DONT, TN_ECHO);
1931 }
1932
1933 void
1934 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1935 {
1936     /* put the holdings sent to us by the server on the board holdings area */
1937     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1938     char p;
1939     ChessSquare piece;
1940
1941     if(gameInfo.holdingsWidth < 2)  return;
1942     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1943         return; // prevent overwriting by pre-board holdings
1944
1945     if( (int)lowestPiece >= BlackPawn ) {
1946         holdingsColumn = 0;
1947         countsColumn = 1;
1948         holdingsStartRow = BOARD_HEIGHT-1;
1949         direction = -1;
1950     } else {
1951         holdingsColumn = BOARD_WIDTH-1;
1952         countsColumn = BOARD_WIDTH-2;
1953         holdingsStartRow = 0;
1954         direction = 1;
1955     }
1956
1957     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1958         board[i][holdingsColumn] = EmptySquare;
1959         board[i][countsColumn]   = (ChessSquare) 0;
1960     }
1961     while( (p=*holdings++) != NULLCHAR ) {
1962         piece = CharToPiece( ToUpper(p) );
1963         if(piece == EmptySquare) continue;
1964         /*j = (int) piece - (int) WhitePawn;*/
1965         j = PieceToNumber(piece);
1966         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1967         if(j < 0) continue;               /* should not happen */
1968         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1969         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1970         board[holdingsStartRow+j*direction][countsColumn]++;
1971     }
1972 }
1973
1974
1975 void
1976 VariantSwitch(Board board, VariantClass newVariant)
1977 {
1978    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1979    Board oldBoard;
1980
1981    startedFromPositionFile = FALSE;
1982    if(gameInfo.variant == newVariant) return;
1983
1984    /* [HGM] This routine is called each time an assignment is made to
1985     * gameInfo.variant during a game, to make sure the board sizes
1986     * are set to match the new variant. If that means adding or deleting
1987     * holdings, we shift the playing board accordingly
1988     * This kludge is needed because in ICS observe mode, we get boards
1989     * of an ongoing game without knowing the variant, and learn about the
1990     * latter only later. This can be because of the move list we requested,
1991     * in which case the game history is refilled from the beginning anyway,
1992     * but also when receiving holdings of a crazyhouse game. In the latter
1993     * case we want to add those holdings to the already received position.
1994     */
1995
1996    
1997    if (appData.debugMode) {
1998      fprintf(debugFP, "Switch board from %s to %s\n",
1999              VariantName(gameInfo.variant), VariantName(newVariant));
2000      setbuf(debugFP, NULL);
2001    }
2002    shuffleOpenings = 0;       /* [HGM] shuffle */
2003    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2004    switch(newVariant) 
2005      {
2006      case VariantShogi:
2007        newWidth = 9;  newHeight = 9;
2008        gameInfo.holdingsSize = 7;
2009      case VariantBughouse:
2010      case VariantCrazyhouse:
2011        newHoldingsWidth = 2; break;
2012      case VariantGreat:
2013        newWidth = 10;
2014      case VariantSuper:
2015        newHoldingsWidth = 2;
2016        gameInfo.holdingsSize = 8;
2017        break;
2018      case VariantGothic:
2019      case VariantCapablanca:
2020      case VariantCapaRandom:
2021        newWidth = 10;
2022      default:
2023        newHoldingsWidth = gameInfo.holdingsSize = 0;
2024      };
2025    
2026    if(newWidth  != gameInfo.boardWidth  ||
2027       newHeight != gameInfo.boardHeight ||
2028       newHoldingsWidth != gameInfo.holdingsWidth ) {
2029      
2030      /* shift position to new playing area, if needed */
2031      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2032        for(i=0; i<BOARD_HEIGHT; i++) 
2033          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2034            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2035              board[i][j];
2036        for(i=0; i<newHeight; i++) {
2037          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2038          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2039        }
2040      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2041        for(i=0; i<BOARD_HEIGHT; i++)
2042          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2043            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2044              board[i][j];
2045      }
2046      gameInfo.boardWidth  = newWidth;
2047      gameInfo.boardHeight = newHeight;
2048      gameInfo.holdingsWidth = newHoldingsWidth;
2049      gameInfo.variant = newVariant;
2050      InitDrawingSizes(-2, 0);
2051    } else gameInfo.variant = newVariant;
2052    CopyBoard(oldBoard, board);   // remember correctly formatted board
2053      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2054    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2055 }
2056
2057 static int loggedOn = FALSE;
2058
2059 /*-- Game start info cache: --*/
2060 int gs_gamenum;
2061 char gs_kind[MSG_SIZ];
2062 static char player1Name[128] = "";
2063 static char player2Name[128] = "";
2064 static char cont_seq[] = "\n\\   ";
2065 static int player1Rating = -1;
2066 static int player2Rating = -1;
2067 /*----------------------------*/
2068
2069 ColorClass curColor = ColorNormal;
2070 int suppressKibitz = 0;
2071
2072 // [HGM] seekgraph
2073 Boolean soughtPending = FALSE;
2074 Boolean seekGraphUp;
2075 #define MAX_SEEK_ADS 200
2076 #define SQUARE 0x80
2077 char *seekAdList[MAX_SEEK_ADS];
2078 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2079 float tcList[MAX_SEEK_ADS];
2080 char colorList[MAX_SEEK_ADS];
2081 int nrOfSeekAds = 0;
2082 int minRating = 1010, maxRating = 2800;
2083 int hMargin = 10, vMargin = 20, h, w;
2084 extern int squareSize, lineGap;
2085
2086 void
2087 PlotSeekAd(int i)
2088 {
2089         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2090         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2091         if(r < minRating+100 && r >=0 ) r = minRating+100;
2092         if(r > maxRating) r = maxRating;
2093         if(tc < 1.) tc = 1.;
2094         if(tc > 95.) tc = 95.;
2095         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2096         y = ((double)r - minRating)/(maxRating - minRating)
2097             * (h-vMargin-squareSize/8-1) + vMargin;
2098         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2099         if(strstr(seekAdList[i], " u ")) color = 1;
2100         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2101            !strstr(seekAdList[i], "bullet") &&
2102            !strstr(seekAdList[i], "blitz") &&
2103            !strstr(seekAdList[i], "standard") ) color = 2;
2104         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2105         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2106 }
2107
2108 void
2109 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2110 {
2111         char buf[MSG_SIZ], *ext = "";
2112         VariantClass v = StringToVariant(type);
2113         if(strstr(type, "wild")) {
2114             ext = type + 4; // append wild number
2115             if(v == VariantFischeRandom) type = "chess960"; else
2116             if(v == VariantLoadable) type = "setup"; else
2117             type = VariantName(v);
2118         }
2119         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2120         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2121             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2122             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2123             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2124             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2125             seekNrList[nrOfSeekAds] = nr;
2126             zList[nrOfSeekAds] = 0;
2127             seekAdList[nrOfSeekAds++] = StrSave(buf);
2128             if(plot) PlotSeekAd(nrOfSeekAds-1);
2129         }
2130 }
2131
2132 void
2133 EraseSeekDot(int i)
2134 {
2135     int x = xList[i], y = yList[i], d=squareSize/4, k;
2136     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2137     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2138     // now replot every dot that overlapped
2139     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2140         int xx = xList[k], yy = yList[k];
2141         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2142             DrawSeekDot(xx, yy, colorList[k]);
2143     }
2144 }
2145
2146 void
2147 RemoveSeekAd(int nr)
2148 {
2149         int i;
2150         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2151             EraseSeekDot(i);
2152             if(seekAdList[i]) free(seekAdList[i]);
2153             seekAdList[i] = seekAdList[--nrOfSeekAds];
2154             seekNrList[i] = seekNrList[nrOfSeekAds];
2155             ratingList[i] = ratingList[nrOfSeekAds];
2156             colorList[i]  = colorList[nrOfSeekAds];
2157             tcList[i] = tcList[nrOfSeekAds];
2158             xList[i]  = xList[nrOfSeekAds];
2159             yList[i]  = yList[nrOfSeekAds];
2160             zList[i]  = zList[nrOfSeekAds];
2161             seekAdList[nrOfSeekAds] = NULL;
2162             break;
2163         }
2164 }
2165
2166 Boolean
2167 MatchSoughtLine(char *line)
2168 {
2169     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2170     int nr, base, inc, u=0; char dummy;
2171
2172     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2174        (u=1) &&
2175        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2176         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2177         // match: compact and save the line
2178         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2179         return TRUE;
2180     }
2181     return FALSE;
2182 }
2183
2184 int
2185 DrawSeekGraph()
2186 {
2187     if(!seekGraphUp) return FALSE;
2188     int i;
2189     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2190     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2191
2192     DrawSeekBackground(0, 0, w, h);
2193     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2194     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2195     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2196         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2197         yy = h-1-yy;
2198         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2199         if(i%500 == 0) {
2200             char buf[MSG_SIZ];
2201             sprintf(buf, "%d", i);
2202             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2203         }
2204     }
2205     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2206     for(i=1; i<100; i+=(i<10?1:5)) {
2207         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2208         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2209         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2210             char buf[MSG_SIZ];
2211             sprintf(buf, "%d", i);
2212             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2213         }
2214     }
2215     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2216     return TRUE;
2217 }
2218
2219 int SeekGraphClick(ClickType click, int x, int y, int moving)
2220 {
2221     static int lastDown = 0, displayed = 0, lastSecond;
2222     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2223         if(click == Release || moving) return FALSE;
2224         nrOfSeekAds = 0;
2225         soughtPending = TRUE;
2226         SendToICS(ics_prefix);
2227         SendToICS("sought\n"); // should this be "sought all"?
2228     } else { // issue challenge based on clicked ad
2229         int dist = 10000; int i, closest = 0, second = 0;
2230         for(i=0; i<nrOfSeekAds; i++) {
2231             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2232             if(d < dist) { dist = d; closest = i; }
2233             second += (d - zList[i] < 120); // count in-range ads
2234             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2235         }
2236         if(dist < 120) {
2237             char buf[MSG_SIZ];
2238             second = (second > 1);
2239             if(displayed != closest || second != lastSecond) {
2240                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2241                 lastSecond = second; displayed = closest;
2242             }
2243             if(click == Press) {
2244                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2245                 lastDown = closest;
2246                 return TRUE;
2247             } // on press 'hit', only show info
2248             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2249             sprintf(buf, "play %d\n", seekNrList[closest]);
2250             SendToICS(ics_prefix);
2251             SendToICS(buf);
2252             return TRUE; // let incoming board of started game pop down the graph
2253         } else if(click == Release) { // release 'miss' is ignored
2254             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2255             if(moving == 2) { // right up-click
2256                 nrOfSeekAds = 0; // refresh graph
2257                 soughtPending = TRUE;
2258                 SendToICS(ics_prefix);
2259                 SendToICS("sought\n"); // should this be "sought all"?
2260             }
2261             return TRUE;
2262         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2263         // press miss or release hit 'pop down' seek graph
2264         seekGraphUp = FALSE;
2265         DrawPosition(TRUE, NULL);
2266     }
2267     return TRUE;
2268 }
2269
2270 void
2271 read_from_ics(isr, closure, data, count, error)
2272      InputSourceRef isr;
2273      VOIDSTAR closure;
2274      char *data;
2275      int count;
2276      int error;
2277 {
2278 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2279 #define STARTED_NONE 0
2280 #define STARTED_MOVES 1
2281 #define STARTED_BOARD 2
2282 #define STARTED_OBSERVE 3
2283 #define STARTED_HOLDINGS 4
2284 #define STARTED_CHATTER 5
2285 #define STARTED_COMMENT 6
2286 #define STARTED_MOVES_NOHIDE 7
2287     
2288     static int started = STARTED_NONE;
2289     static char parse[20000];
2290     static int parse_pos = 0;
2291     static char buf[BUF_SIZE + 1];
2292     static int firstTime = TRUE, intfSet = FALSE;
2293     static ColorClass prevColor = ColorNormal;
2294     static int savingComment = FALSE;
2295     static int cmatch = 0; // continuation sequence match
2296     char *bp;
2297     char str[500];
2298     int i, oldi;
2299     int buf_len;
2300     int next_out;
2301     int tkind;
2302     int backup;    /* [DM] For zippy color lines */
2303     char *p;
2304     char talker[MSG_SIZ]; // [HGM] chat
2305     int channel;
2306
2307     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2308
2309     if (appData.debugMode) {
2310       if (!error) {
2311         fprintf(debugFP, "<ICS: ");
2312         show_bytes(debugFP, data, count);
2313         fprintf(debugFP, "\n");
2314       }
2315     }
2316
2317     if (appData.debugMode) { int f = forwardMostMove;
2318         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2319                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2320                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2321     }
2322     if (count > 0) {
2323         /* If last read ended with a partial line that we couldn't parse,
2324            prepend it to the new read and try again. */
2325         if (leftover_len > 0) {
2326             for (i=0; i<leftover_len; i++)
2327               buf[i] = buf[leftover_start + i];
2328         }
2329
2330     /* copy new characters into the buffer */
2331     bp = buf + leftover_len;
2332     buf_len=leftover_len;
2333     for (i=0; i<count; i++)
2334     {
2335         // ignore these
2336         if (data[i] == '\r')
2337             continue;
2338
2339         // join lines split by ICS?
2340         if (!appData.noJoin)
2341         {
2342             /*
2343                 Joining just consists of finding matches against the
2344                 continuation sequence, and discarding that sequence
2345                 if found instead of copying it.  So, until a match
2346                 fails, there's nothing to do since it might be the
2347                 complete sequence, and thus, something we don't want
2348                 copied.
2349             */
2350             if (data[i] == cont_seq[cmatch])
2351             {
2352                 cmatch++;
2353                 if (cmatch == strlen(cont_seq))
2354                 {
2355                     cmatch = 0; // complete match.  just reset the counter
2356
2357                     /*
2358                         it's possible for the ICS to not include the space
2359                         at the end of the last word, making our [correct]
2360                         join operation fuse two separate words.  the server
2361                         does this when the space occurs at the width setting.
2362                     */
2363                     if (!buf_len || buf[buf_len-1] != ' ')
2364                     {
2365                         *bp++ = ' ';
2366                         buf_len++;
2367                     }
2368                 }
2369                 continue;
2370             }
2371             else if (cmatch)
2372             {
2373                 /*
2374                     match failed, so we have to copy what matched before
2375                     falling through and copying this character.  In reality,
2376                     this will only ever be just the newline character, but
2377                     it doesn't hurt to be precise.
2378                 */
2379                 strncpy(bp, cont_seq, cmatch);
2380                 bp += cmatch;
2381                 buf_len += cmatch;
2382                 cmatch = 0;
2383             }
2384         }
2385
2386         // copy this char
2387         *bp++ = data[i];
2388         buf_len++;
2389     }
2390
2391         buf[buf_len] = NULLCHAR;
2392 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2393         next_out = 0;
2394         leftover_start = 0;
2395         
2396         i = 0;
2397         while (i < buf_len) {
2398             /* Deal with part of the TELNET option negotiation
2399                protocol.  We refuse to do anything beyond the
2400                defaults, except that we allow the WILL ECHO option,
2401                which ICS uses to turn off password echoing when we are
2402                directly connected to it.  We reject this option
2403                if localLineEditing mode is on (always on in xboard)
2404                and we are talking to port 23, which might be a real
2405                telnet server that will try to keep WILL ECHO on permanently.
2406              */
2407             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2408                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2409                 unsigned char option;
2410                 oldi = i;
2411                 switch ((unsigned char) buf[++i]) {
2412                   case TN_WILL:
2413                     if (appData.debugMode)
2414                       fprintf(debugFP, "\n<WILL ");
2415                     switch (option = (unsigned char) buf[++i]) {
2416                       case TN_ECHO:
2417                         if (appData.debugMode)
2418                           fprintf(debugFP, "ECHO ");
2419                         /* Reply only if this is a change, according
2420                            to the protocol rules. */
2421                         if (remoteEchoOption) break;
2422                         if (appData.localLineEditing &&
2423                             atoi(appData.icsPort) == TN_PORT) {
2424                             TelnetRequest(TN_DONT, TN_ECHO);
2425                         } else {
2426                             EchoOff();
2427                             TelnetRequest(TN_DO, TN_ECHO);
2428                             remoteEchoOption = TRUE;
2429                         }
2430                         break;
2431                       default:
2432                         if (appData.debugMode)
2433                           fprintf(debugFP, "%d ", option);
2434                         /* Whatever this is, we don't want it. */
2435                         TelnetRequest(TN_DONT, option);
2436                         break;
2437                     }
2438                     break;
2439                   case TN_WONT:
2440                     if (appData.debugMode)
2441                       fprintf(debugFP, "\n<WONT ");
2442                     switch (option = (unsigned char) buf[++i]) {
2443                       case TN_ECHO:
2444                         if (appData.debugMode)
2445                           fprintf(debugFP, "ECHO ");
2446                         /* Reply only if this is a change, according
2447                            to the protocol rules. */
2448                         if (!remoteEchoOption) break;
2449                         EchoOn();
2450                         TelnetRequest(TN_DONT, TN_ECHO);
2451                         remoteEchoOption = FALSE;
2452                         break;
2453                       default:
2454                         if (appData.debugMode)
2455                           fprintf(debugFP, "%d ", (unsigned char) option);
2456                         /* Whatever this is, it must already be turned
2457                            off, because we never agree to turn on
2458                            anything non-default, so according to the
2459                            protocol rules, we don't reply. */
2460                         break;
2461                     }
2462                     break;
2463                   case TN_DO:
2464                     if (appData.debugMode)
2465                       fprintf(debugFP, "\n<DO ");
2466                     switch (option = (unsigned char) buf[++i]) {
2467                       default:
2468                         /* Whatever this is, we refuse to do it. */
2469                         if (appData.debugMode)
2470                           fprintf(debugFP, "%d ", option);
2471                         TelnetRequest(TN_WONT, option);
2472                         break;
2473                     }
2474                     break;
2475                   case TN_DONT:
2476                     if (appData.debugMode)
2477                       fprintf(debugFP, "\n<DONT ");
2478                     switch (option = (unsigned char) buf[++i]) {
2479                       default:
2480                         if (appData.debugMode)
2481                           fprintf(debugFP, "%d ", option);
2482                         /* Whatever this is, we are already not doing
2483                            it, because we never agree to do anything
2484                            non-default, so according to the protocol
2485                            rules, we don't reply. */
2486                         break;
2487                     }
2488                     break;
2489                   case TN_IAC:
2490                     if (appData.debugMode)
2491                       fprintf(debugFP, "\n<IAC ");
2492                     /* Doubled IAC; pass it through */
2493                     i--;
2494                     break;
2495                   default:
2496                     if (appData.debugMode)
2497                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2498                     /* Drop all other telnet commands on the floor */
2499                     break;
2500                 }
2501                 if (oldi > next_out)
2502                   SendToPlayer(&buf[next_out], oldi - next_out);
2503                 if (++i > next_out)
2504                   next_out = i;
2505                 continue;
2506             }
2507                 
2508             /* OK, this at least will *usually* work */
2509             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2510                 loggedOn = TRUE;
2511             }
2512             
2513             if (loggedOn && !intfSet) {
2514                 if (ics_type == ICS_ICC) {
2515                   sprintf(str,
2516                           "/set-quietly interface %s\n/set-quietly style 12\n",
2517                           programVersion);
2518                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2519                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2520                 } else if (ics_type == ICS_CHESSNET) {
2521                   sprintf(str, "/style 12\n");
2522                 } else {
2523                   strcpy(str, "alias $ @\n$set interface ");
2524                   strcat(str, programVersion);
2525                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2526                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2527                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2528 #ifdef WIN32
2529                   strcat(str, "$iset nohighlight 1\n");
2530 #endif
2531                   strcat(str, "$iset lock 1\n$style 12\n");
2532                 }
2533                 SendToICS(str);
2534                 NotifyFrontendLogin();
2535                 intfSet = TRUE;
2536             }
2537
2538             if (started == STARTED_COMMENT) {
2539                 /* Accumulate characters in comment */
2540                 parse[parse_pos++] = buf[i];
2541                 if (buf[i] == '\n') {
2542                     parse[parse_pos] = NULLCHAR;
2543                     if(chattingPartner>=0) {
2544                         char mess[MSG_SIZ];
2545                         sprintf(mess, "%s%s", talker, parse);
2546                         OutputChatMessage(chattingPartner, mess);
2547                         chattingPartner = -1;
2548                         next_out = i+1; // [HGM] suppress printing in ICS window
2549                     } else
2550                     if(!suppressKibitz) // [HGM] kibitz
2551                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2552                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2553                         int nrDigit = 0, nrAlph = 0, j;
2554                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2555                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2556                         parse[parse_pos] = NULLCHAR;
2557                         // try to be smart: if it does not look like search info, it should go to
2558                         // ICS interaction window after all, not to engine-output window.
2559                         for(j=0; j<parse_pos; j++) { // count letters and digits
2560                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2561                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2562                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2563                         }
2564                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2565                             int depth=0; float score;
2566                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2567                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2568                                 pvInfoList[forwardMostMove-1].depth = depth;
2569                                 pvInfoList[forwardMostMove-1].score = 100*score;
2570                             }
2571                             OutputKibitz(suppressKibitz, parse);
2572                         } else {
2573                             char tmp[MSG_SIZ];
2574                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2575                             SendToPlayer(tmp, strlen(tmp));
2576                         }
2577                         next_out = i+1; // [HGM] suppress printing in ICS window
2578                     }
2579                     started = STARTED_NONE;
2580                 } else {
2581                     /* Don't match patterns against characters in comment */
2582                     i++;
2583                     continue;
2584                 }
2585             }
2586             if (started == STARTED_CHATTER) {
2587                 if (buf[i] != '\n') {
2588                     /* Don't match patterns against characters in chatter */
2589                     i++;
2590                     continue;
2591                 }
2592                 started = STARTED_NONE;
2593                 if(suppressKibitz) next_out = i+1;
2594             }
2595
2596             /* Kludge to deal with rcmd protocol */
2597             if (firstTime && looking_at(buf, &i, "\001*")) {
2598                 DisplayFatalError(&buf[1], 0, 1);
2599                 continue;
2600             } else {
2601                 firstTime = FALSE;
2602             }
2603
2604             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2605                 ics_type = ICS_ICC;
2606                 ics_prefix = "/";
2607                 if (appData.debugMode)
2608                   fprintf(debugFP, "ics_type %d\n", ics_type);
2609                 continue;
2610             }
2611             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2612                 ics_type = ICS_FICS;
2613                 ics_prefix = "$";
2614                 if (appData.debugMode)
2615                   fprintf(debugFP, "ics_type %d\n", ics_type);
2616                 continue;
2617             }
2618             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2619                 ics_type = ICS_CHESSNET;
2620                 ics_prefix = "/";
2621                 if (appData.debugMode)
2622                   fprintf(debugFP, "ics_type %d\n", ics_type);
2623                 continue;
2624             }
2625
2626             if (!loggedOn &&
2627                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2628                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2629                  looking_at(buf, &i, "will be \"*\""))) {
2630               strcpy(ics_handle, star_match[0]);
2631               continue;
2632             }
2633
2634             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2635               char buf[MSG_SIZ];
2636               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2637               DisplayIcsInteractionTitle(buf);
2638               have_set_title = TRUE;
2639             }
2640
2641             /* skip finger notes */
2642             if (started == STARTED_NONE &&
2643                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2644                  (buf[i] == '1' && buf[i+1] == '0')) &&
2645                 buf[i+2] == ':' && buf[i+3] == ' ') {
2646               started = STARTED_CHATTER;
2647               i += 3;
2648               continue;
2649             }
2650
2651             oldi = i;
2652             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2653             if(appData.seekGraph) {
2654                 if(soughtPending && MatchSoughtLine(buf+i)) {
2655                     i = strstr(buf+i, "rated") - buf;
2656                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2657                     next_out = leftover_start = i;
2658                     started = STARTED_CHATTER;
2659                     suppressKibitz = TRUE;
2660                     continue;
2661                 }
2662                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2663                         && looking_at(buf, &i, "* ads displayed")) {
2664                     soughtPending = FALSE;
2665                     seekGraphUp = TRUE;
2666                     DrawSeekGraph();
2667                     continue;
2668                 }
2669                 if(appData.autoRefresh) {
2670                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2671                         int s = (ics_type == ICS_ICC); // ICC format differs
2672                         if(seekGraphUp)
2673                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2674                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2675                         looking_at(buf, &i, "*% "); // eat prompt
2676                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2677                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2678                         next_out = i; // suppress
2679                         continue;
2680                     }
2681                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2682                         char *p = star_match[0];
2683                         while(*p) {
2684                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2685                             while(*p && *p++ != ' '); // next
2686                         }
2687                         looking_at(buf, &i, "*% "); // eat prompt
2688                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2689                         next_out = i;
2690                         continue;
2691                     }
2692                 }
2693             }
2694
2695             /* skip formula vars */
2696             if (started == STARTED_NONE &&
2697                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2698               started = STARTED_CHATTER;
2699               i += 3;
2700               continue;
2701             }
2702
2703             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2704             if (appData.autoKibitz && started == STARTED_NONE && 
2705                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2706                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2707                 if(looking_at(buf, &i, "* kibitzes: ") &&
2708                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2709                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2710                         suppressKibitz = TRUE;
2711                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2712                         next_out = i;
2713                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2714                                 && (gameMode == IcsPlayingWhite)) ||
2715                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2716                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2717                             started = STARTED_CHATTER; // own kibitz we simply discard
2718                         else {
2719                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2720                             parse_pos = 0; parse[0] = NULLCHAR;
2721                             savingComment = TRUE;
2722                             suppressKibitz = gameMode != IcsObserving ? 2 :
2723                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2724                         } 
2725                         continue;
2726                 } else
2727                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2728                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2729                          && atoi(star_match[0])) {
2730                     // suppress the acknowledgements of our own autoKibitz
2731                     char *p;
2732                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2733                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2734                     SendToPlayer(star_match[0], strlen(star_match[0]));
2735                     if(looking_at(buf, &i, "*% ")) // eat prompt
2736                         suppressKibitz = FALSE;
2737                     next_out = i;
2738                     continue;
2739                 }
2740             } // [HGM] kibitz: end of patch
2741
2742             // [HGM] chat: intercept tells by users for which we have an open chat window
2743             channel = -1;
2744             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2745                                            looking_at(buf, &i, "* whispers:") ||
2746                                            looking_at(buf, &i, "* shouts:") ||
2747                                            looking_at(buf, &i, "* c-shouts:") ||
2748                                            looking_at(buf, &i, "--> * ") ||
2749                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2750                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2751                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2752                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2753                 int p;
2754                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2755                 chattingPartner = -1;
2756
2757                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2758                 for(p=0; p<MAX_CHAT; p++) {
2759                     if(channel == atoi(chatPartner[p])) {
2760                     talker[0] = '['; strcat(talker, "] ");
2761                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2762                     chattingPartner = p; break;
2763                     }
2764                 } else
2765                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2766                 for(p=0; p<MAX_CHAT; p++) {
2767                     if(!strcmp("whispers", chatPartner[p])) {
2768                         talker[0] = '['; strcat(talker, "] ");
2769                         chattingPartner = p; break;
2770                     }
2771                 } else
2772                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2773                 for(p=0; p<MAX_CHAT; p++) {
2774                     if(!strcmp("shouts", chatPartner[p])) {
2775                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2776                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2777                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2778                         chattingPartner = p; break;
2779                     }
2780                 }
2781                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2782                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2783                     talker[0] = 0; Colorize(ColorTell, FALSE);
2784                     chattingPartner = p; break;
2785                 }
2786                 if(chattingPartner<0) i = oldi; else {
2787                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2788                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2789                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2790                     started = STARTED_COMMENT;
2791                     parse_pos = 0; parse[0] = NULLCHAR;
2792                     savingComment = 3 + chattingPartner; // counts as TRUE
2793                     suppressKibitz = TRUE;
2794                     continue;
2795                 }
2796             } // [HGM] chat: end of patch
2797
2798             if (appData.zippyTalk || appData.zippyPlay) {
2799                 /* [DM] Backup address for color zippy lines */
2800                 backup = i;
2801 #if ZIPPY
2802        #ifdef WIN32
2803                if (loggedOn == TRUE)
2804                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2805                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2806        #else
2807                 if (ZippyControl(buf, &i) ||
2808                     ZippyConverse(buf, &i) ||
2809                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2810                       loggedOn = TRUE;
2811                       if (!appData.colorize) continue;
2812                 }
2813        #endif
2814 #endif
2815             } // [DM] 'else { ' deleted
2816                 if (
2817                     /* Regular tells and says */
2818                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2819                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2820                     looking_at(buf, &i, "* says: ") ||
2821                     /* Don't color "message" or "messages" output */
2822                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2823                     looking_at(buf, &i, "*. * at *:*: ") ||
2824                     looking_at(buf, &i, "--* (*:*): ") ||
2825                     /* Message notifications (same color as tells) */
2826                     looking_at(buf, &i, "* has left a message ") ||
2827                     looking_at(buf, &i, "* just sent you a message:\n") ||
2828                     /* Whispers and kibitzes */
2829                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2830                     looking_at(buf, &i, "* kibitzes: ") ||
2831                     /* Channel tells */
2832                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2833
2834                   if (tkind == 1 && strchr(star_match[0], ':')) {
2835                       /* Avoid "tells you:" spoofs in channels */
2836                      tkind = 3;
2837                   }
2838                   if (star_match[0][0] == NULLCHAR ||
2839                       strchr(star_match[0], ' ') ||
2840                       (tkind == 3 && strchr(star_match[1], ' '))) {
2841                     /* Reject bogus matches */
2842                     i = oldi;
2843                   } else {
2844                     if (appData.colorize) {
2845                       if (oldi > next_out) {
2846                         SendToPlayer(&buf[next_out], oldi - next_out);
2847                         next_out = oldi;
2848                       }
2849                       switch (tkind) {
2850                       case 1:
2851                         Colorize(ColorTell, FALSE);
2852                         curColor = ColorTell;
2853                         break;
2854                       case 2:
2855                         Colorize(ColorKibitz, FALSE);
2856                         curColor = ColorKibitz;
2857                         break;
2858                       case 3:
2859                         p = strrchr(star_match[1], '(');
2860                         if (p == NULL) {
2861                           p = star_match[1];
2862                         } else {
2863                           p++;
2864                         }
2865                         if (atoi(p) == 1) {
2866                           Colorize(ColorChannel1, FALSE);
2867                           curColor = ColorChannel1;
2868                         } else {
2869                           Colorize(ColorChannel, FALSE);
2870                           curColor = ColorChannel;
2871                         }
2872                         break;
2873                       case 5:
2874                         curColor = ColorNormal;
2875                         break;
2876                       }
2877                     }
2878                     if (started == STARTED_NONE && appData.autoComment &&
2879                         (gameMode == IcsObserving ||
2880                          gameMode == IcsPlayingWhite ||
2881                          gameMode == IcsPlayingBlack)) {
2882                       parse_pos = i - oldi;
2883                       memcpy(parse, &buf[oldi], parse_pos);
2884                       parse[parse_pos] = NULLCHAR;
2885                       started = STARTED_COMMENT;
2886                       savingComment = TRUE;
2887                     } else {
2888                       started = STARTED_CHATTER;
2889                       savingComment = FALSE;
2890                     }
2891                     loggedOn = TRUE;
2892                     continue;
2893                   }
2894                 }
2895
2896                 if (looking_at(buf, &i, "* s-shouts: ") ||
2897                     looking_at(buf, &i, "* c-shouts: ")) {
2898                     if (appData.colorize) {
2899                         if (oldi > next_out) {
2900                             SendToPlayer(&buf[next_out], oldi - next_out);
2901                             next_out = oldi;
2902                         }
2903                         Colorize(ColorSShout, FALSE);
2904                         curColor = ColorSShout;
2905                     }
2906                     loggedOn = TRUE;
2907                     started = STARTED_CHATTER;
2908                     continue;
2909                 }
2910
2911                 if (looking_at(buf, &i, "--->")) {
2912                     loggedOn = TRUE;
2913                     continue;
2914                 }
2915
2916                 if (looking_at(buf, &i, "* shouts: ") ||
2917                     looking_at(buf, &i, "--> ")) {
2918                     if (appData.colorize) {
2919                         if (oldi > next_out) {
2920                             SendToPlayer(&buf[next_out], oldi - next_out);
2921                             next_out = oldi;
2922                         }
2923                         Colorize(ColorShout, FALSE);
2924                         curColor = ColorShout;
2925                     }
2926                     loggedOn = TRUE;
2927                     started = STARTED_CHATTER;
2928                     continue;
2929                 }
2930
2931                 if (looking_at( buf, &i, "Challenge:")) {
2932                     if (appData.colorize) {
2933                         if (oldi > next_out) {
2934                             SendToPlayer(&buf[next_out], oldi - next_out);
2935                             next_out = oldi;
2936                         }
2937                         Colorize(ColorChallenge, FALSE);
2938                         curColor = ColorChallenge;
2939                     }
2940                     loggedOn = TRUE;
2941                     continue;
2942                 }
2943
2944                 if (looking_at(buf, &i, "* offers you") ||
2945                     looking_at(buf, &i, "* offers to be") ||
2946                     looking_at(buf, &i, "* would like to") ||
2947                     looking_at(buf, &i, "* requests to") ||
2948                     looking_at(buf, &i, "Your opponent offers") ||
2949                     looking_at(buf, &i, "Your opponent requests")) {
2950
2951                     if (appData.colorize) {
2952                         if (oldi > next_out) {
2953                             SendToPlayer(&buf[next_out], oldi - next_out);
2954                             next_out = oldi;
2955                         }
2956                         Colorize(ColorRequest, FALSE);
2957                         curColor = ColorRequest;
2958                     }
2959                     continue;
2960                 }
2961
2962                 if (looking_at(buf, &i, "* (*) seeking")) {
2963                     if (appData.colorize) {
2964                         if (oldi > next_out) {
2965                             SendToPlayer(&buf[next_out], oldi - next_out);
2966                             next_out = oldi;
2967                         }
2968                         Colorize(ColorSeek, FALSE);
2969                         curColor = ColorSeek;
2970                     }
2971                     continue;
2972             }
2973
2974             if (looking_at(buf, &i, "\\   ")) {
2975                 if (prevColor != ColorNormal) {
2976                     if (oldi > next_out) {
2977                         SendToPlayer(&buf[next_out], oldi - next_out);
2978                         next_out = oldi;
2979                     }
2980                     Colorize(prevColor, TRUE);
2981                     curColor = prevColor;
2982                 }
2983                 if (savingComment) {
2984                     parse_pos = i - oldi;
2985                     memcpy(parse, &buf[oldi], parse_pos);
2986                     parse[parse_pos] = NULLCHAR;
2987                     started = STARTED_COMMENT;
2988                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2989                         chattingPartner = savingComment - 3; // kludge to remember the box
2990                 } else {
2991                     started = STARTED_CHATTER;
2992                 }
2993                 continue;
2994             }
2995
2996             if (looking_at(buf, &i, "Black Strength :") ||
2997                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2998                 looking_at(buf, &i, "<10>") ||
2999                 looking_at(buf, &i, "#@#")) {
3000                 /* Wrong board style */
3001                 loggedOn = TRUE;
3002                 SendToICS(ics_prefix);
3003                 SendToICS("set style 12\n");
3004                 SendToICS(ics_prefix);
3005                 SendToICS("refresh\n");
3006                 continue;
3007             }
3008             
3009             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3010                 ICSInitScript();
3011                 have_sent_ICS_logon = 1;
3012                 continue;
3013             }
3014               
3015             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3016                 (looking_at(buf, &i, "\n<12> ") ||
3017                  looking_at(buf, &i, "<12> "))) {
3018                 loggedOn = TRUE;
3019                 if (oldi > next_out) {
3020                     SendToPlayer(&buf[next_out], oldi - next_out);
3021                 }
3022                 next_out = i;
3023                 started = STARTED_BOARD;
3024                 parse_pos = 0;
3025                 continue;
3026             }
3027
3028             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3029                 looking_at(buf, &i, "<b1> ")) {
3030                 if (oldi > next_out) {
3031                     SendToPlayer(&buf[next_out], oldi - next_out);
3032                 }
3033                 next_out = i;
3034                 started = STARTED_HOLDINGS;
3035                 parse_pos = 0;
3036                 continue;
3037             }
3038
3039             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3040                 loggedOn = TRUE;
3041                 /* Header for a move list -- first line */
3042
3043                 switch (ics_getting_history) {
3044                   case H_FALSE:
3045                     switch (gameMode) {
3046                       case IcsIdle:
3047                       case BeginningOfGame:
3048                         /* User typed "moves" or "oldmoves" while we
3049                            were idle.  Pretend we asked for these
3050                            moves and soak them up so user can step
3051                            through them and/or save them.
3052                            */
3053                         Reset(FALSE, TRUE);
3054                         gameMode = IcsObserving;
3055                         ModeHighlight();
3056                         ics_gamenum = -1;
3057                         ics_getting_history = H_GOT_UNREQ_HEADER;
3058                         break;
3059                       case EditGame: /*?*/
3060                       case EditPosition: /*?*/
3061                         /* Should above feature work in these modes too? */
3062                         /* For now it doesn't */
3063                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3064                         break;
3065                       default:
3066                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3067                         break;
3068                     }
3069                     break;
3070                   case H_REQUESTED:
3071                     /* Is this the right one? */
3072                     if (gameInfo.white && gameInfo.black &&
3073                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3074                         strcmp(gameInfo.black, star_match[2]) == 0) {
3075                         /* All is well */
3076                         ics_getting_history = H_GOT_REQ_HEADER;
3077                     }
3078                     break;
3079                   case H_GOT_REQ_HEADER:
3080                   case H_GOT_UNREQ_HEADER:
3081                   case H_GOT_UNWANTED_HEADER:
3082                   case H_GETTING_MOVES:
3083                     /* Should not happen */
3084                     DisplayError(_("Error gathering move list: two headers"), 0);
3085                     ics_getting_history = H_FALSE;
3086                     break;
3087                 }
3088
3089                 /* Save player ratings into gameInfo if needed */
3090                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3091                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3092                     (gameInfo.whiteRating == -1 ||
3093                      gameInfo.blackRating == -1)) {
3094
3095                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3096                     gameInfo.blackRating = string_to_rating(star_match[3]);
3097                     if (appData.debugMode)
3098                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3099                               gameInfo.whiteRating, gameInfo.blackRating);
3100                 }
3101                 continue;
3102             }
3103
3104             if (looking_at(buf, &i,
3105               "* * match, initial time: * minute*, increment: * second")) {
3106                 /* Header for a move list -- second line */
3107                 /* Initial board will follow if this is a wild game */
3108                 if (gameInfo.event != NULL) free(gameInfo.event);
3109                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3110                 gameInfo.event = StrSave(str);
3111                 /* [HGM] we switched variant. Translate boards if needed. */
3112                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Move  ")) {
3117                 /* Beginning of a move list */
3118                 switch (ics_getting_history) {
3119                   case H_FALSE:
3120                     /* Normally should not happen */
3121                     /* Maybe user hit reset while we were parsing */
3122                     break;
3123                   case H_REQUESTED:
3124                     /* Happens if we are ignoring a move list that is not
3125                      * the one we just requested.  Common if the user
3126                      * tries to observe two games without turning off
3127                      * getMoveList */
3128                     break;
3129                   case H_GETTING_MOVES:
3130                     /* Should not happen */
3131                     DisplayError(_("Error gathering move list: nested"), 0);
3132                     ics_getting_history = H_FALSE;
3133                     break;
3134                   case H_GOT_REQ_HEADER:
3135                     ics_getting_history = H_GETTING_MOVES;
3136                     started = STARTED_MOVES;
3137                     parse_pos = 0;
3138                     if (oldi > next_out) {
3139                         SendToPlayer(&buf[next_out], oldi - next_out);
3140                     }
3141                     break;
3142                   case H_GOT_UNREQ_HEADER:
3143                     ics_getting_history = H_GETTING_MOVES;
3144                     started = STARTED_MOVES_NOHIDE;
3145                     parse_pos = 0;
3146                     break;
3147                   case H_GOT_UNWANTED_HEADER:
3148                     ics_getting_history = H_FALSE;
3149                     break;
3150                 }
3151                 continue;
3152             }                           
3153             
3154             if (looking_at(buf, &i, "% ") ||
3155                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3156                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3157                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3158                     soughtPending = FALSE;
3159                     seekGraphUp = TRUE;
3160                     DrawSeekGraph();
3161                 }
3162                 if(suppressKibitz) next_out = i;
3163                 savingComment = FALSE;
3164                 suppressKibitz = 0;
3165                 switch (started) {
3166                   case STARTED_MOVES:
3167                   case STARTED_MOVES_NOHIDE:
3168                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3169                     parse[parse_pos + i - oldi] = NULLCHAR;
3170                     ParseGameHistory(parse);
3171 #if ZIPPY
3172                     if (appData.zippyPlay && first.initDone) {
3173                         FeedMovesToProgram(&first, forwardMostMove);
3174                         if (gameMode == IcsPlayingWhite) {
3175                             if (WhiteOnMove(forwardMostMove)) {
3176                                 if (first.sendTime) {
3177                                   if (first.useColors) {
3178                                     SendToProgram("black\n", &first); 
3179                                   }
3180                                   SendTimeRemaining(&first, TRUE);
3181                                 }
3182                                 if (first.useColors) {
3183                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3184                                 }
3185                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3186                                 first.maybeThinking = TRUE;
3187                             } else {
3188                                 if (first.usePlayother) {
3189                                   if (first.sendTime) {
3190                                     SendTimeRemaining(&first, TRUE);
3191                                   }
3192                                   SendToProgram("playother\n", &first);
3193                                   firstMove = FALSE;
3194                                 } else {
3195                                   firstMove = TRUE;
3196                                 }
3197                             }
3198                         } else if (gameMode == IcsPlayingBlack) {
3199                             if (!WhiteOnMove(forwardMostMove)) {
3200                                 if (first.sendTime) {
3201                                   if (first.useColors) {
3202                                     SendToProgram("white\n", &first);
3203                                   }
3204                                   SendTimeRemaining(&first, FALSE);
3205                                 }
3206                                 if (first.useColors) {
3207                                   SendToProgram("black\n", &first);
3208                                 }
3209                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3210                                 first.maybeThinking = TRUE;
3211                             } else {
3212                                 if (first.usePlayother) {
3213                                   if (first.sendTime) {
3214                                     SendTimeRemaining(&first, FALSE);
3215                                   }
3216                                   SendToProgram("playother\n", &first);
3217                                   firstMove = FALSE;
3218                                 } else {
3219                                   firstMove = TRUE;
3220                                 }
3221                             }
3222                         }                       
3223                     }
3224 #endif
3225                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3226                         /* Moves came from oldmoves or moves command
3227                            while we weren't doing anything else.
3228                            */
3229                         currentMove = forwardMostMove;
3230                         ClearHighlights();/*!!could figure this out*/
3231                         flipView = appData.flipView;
3232                         DrawPosition(TRUE, boards[currentMove]);
3233                         DisplayBothClocks();
3234                         sprintf(str, "%s vs. %s",
3235                                 gameInfo.white, gameInfo.black);
3236                         DisplayTitle(str);
3237                         gameMode = IcsIdle;
3238                     } else {
3239                         /* Moves were history of an active game */
3240                         if (gameInfo.resultDetails != NULL) {
3241                             free(gameInfo.resultDetails);
3242                             gameInfo.resultDetails = NULL;
3243                         }
3244                     }
3245                     HistorySet(parseList, backwardMostMove,
3246                                forwardMostMove, currentMove-1);
3247                     DisplayMove(currentMove - 1);
3248                     if (started == STARTED_MOVES) next_out = i;
3249                     started = STARTED_NONE;
3250                     ics_getting_history = H_FALSE;
3251                     break;
3252
3253                   case STARTED_OBSERVE:
3254                     started = STARTED_NONE;
3255                     SendToICS(ics_prefix);
3256                     SendToICS("refresh\n");
3257                     break;
3258
3259                   default:
3260                     break;
3261                 }
3262                 if(bookHit) { // [HGM] book: simulate book reply
3263                     static char bookMove[MSG_SIZ]; // a bit generous?
3264
3265                     programStats.nodes = programStats.depth = programStats.time = 
3266                     programStats.score = programStats.got_only_move = 0;
3267                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3268
3269                     strcpy(bookMove, "move ");
3270                     strcat(bookMove, bookHit);
3271                     HandleMachineMove(bookMove, &first);
3272                 }
3273                 continue;
3274             }
3275             
3276             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3277                  started == STARTED_HOLDINGS ||
3278                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3279                 /* Accumulate characters in move list or board */
3280                 parse[parse_pos++] = buf[i];
3281             }
3282             
3283             /* Start of game messages.  Mostly we detect start of game
3284                when the first board image arrives.  On some versions
3285                of the ICS, though, we need to do a "refresh" after starting
3286                to observe in order to get the current board right away. */
3287             if (looking_at(buf, &i, "Adding game * to observation list")) {
3288                 started = STARTED_OBSERVE;
3289                 continue;
3290             }
3291
3292             /* Handle auto-observe */
3293             if (appData.autoObserve &&
3294                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3295                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3296                 char *player;
3297                 /* Choose the player that was highlighted, if any. */
3298                 if (star_match[0][0] == '\033' ||
3299                     star_match[1][0] != '\033') {
3300                     player = star_match[0];
3301                 } else {
3302                     player = star_match[2];
3303                 }
3304                 sprintf(str, "%sobserve %s\n",
3305                         ics_prefix, StripHighlightAndTitle(player));
3306                 SendToICS(str);
3307
3308                 /* Save ratings from notify string */
3309                 strcpy(player1Name, star_match[0]);
3310                 player1Rating = string_to_rating(star_match[1]);
3311                 strcpy(player2Name, star_match[2]);
3312                 player2Rating = string_to_rating(star_match[3]);
3313
3314                 if (appData.debugMode)
3315                   fprintf(debugFP, 
3316                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3317                           player1Name, player1Rating,
3318                           player2Name, player2Rating);
3319
3320                 continue;
3321             }
3322
3323             /* Deal with automatic examine mode after a game,
3324                and with IcsObserving -> IcsExamining transition */
3325             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3326                 looking_at(buf, &i, "has made you an examiner of game *")) {
3327
3328                 int gamenum = atoi(star_match[0]);
3329                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3330                     gamenum == ics_gamenum) {
3331                     /* We were already playing or observing this game;
3332                        no need to refetch history */
3333                     gameMode = IcsExamining;
3334                     if (pausing) {
3335                         pauseExamForwardMostMove = forwardMostMove;
3336                     } else if (currentMove < forwardMostMove) {
3337                         ForwardInner(forwardMostMove);
3338                     }
3339                 } else {
3340                     /* I don't think this case really can happen */
3341                     SendToICS(ics_prefix);
3342                     SendToICS("refresh\n");
3343                 }
3344                 continue;
3345             }    
3346             
3347             /* Error messages */
3348 //          if (ics_user_moved) {
3349             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3350                 if (looking_at(buf, &i, "Illegal move") ||
3351                     looking_at(buf, &i, "Not a legal move") ||
3352                     looking_at(buf, &i, "Your king is in check") ||
3353                     looking_at(buf, &i, "It isn't your turn") ||
3354                     looking_at(buf, &i, "It is not your move")) {
3355                     /* Illegal move */
3356                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3357                         currentMove = forwardMostMove-1;
3358                         DisplayMove(currentMove - 1); /* before DMError */
3359                         DrawPosition(FALSE, boards[currentMove]);
3360                         SwitchClocks(forwardMostMove-1); // [HGM] race
3361                         DisplayBothClocks();
3362                     }
3363                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3364                     ics_user_moved = 0;
3365                     continue;
3366                 }
3367             }
3368
3369             if (looking_at(buf, &i, "still have time") ||
3370                 looking_at(buf, &i, "not out of time") ||
3371                 looking_at(buf, &i, "either player is out of time") ||
3372                 looking_at(buf, &i, "has timeseal; checking")) {
3373                 /* We must have called his flag a little too soon */
3374                 whiteFlag = blackFlag = FALSE;
3375                 continue;
3376             }
3377
3378             if (looking_at(buf, &i, "added * seconds to") ||
3379                 looking_at(buf, &i, "seconds were added to")) {
3380                 /* Update the clocks */
3381                 SendToICS(ics_prefix);
3382                 SendToICS("refresh\n");
3383                 continue;
3384             }
3385
3386             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3387                 ics_clock_paused = TRUE;
3388                 StopClocks();
3389                 continue;
3390             }
3391
3392             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3393                 ics_clock_paused = FALSE;
3394                 StartClocks();
3395                 continue;
3396             }
3397
3398             /* Grab player ratings from the Creating: message.
3399                Note we have to check for the special case when
3400                the ICS inserts things like [white] or [black]. */
3401             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3402                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3403                 /* star_matches:
3404                    0    player 1 name (not necessarily white)
3405                    1    player 1 rating
3406                    2    empty, white, or black (IGNORED)
3407                    3    player 2 name (not necessarily black)
3408                    4    player 2 rating
3409                    
3410                    The names/ratings are sorted out when the game
3411                    actually starts (below).
3412                 */
3413                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3414                 player1Rating = string_to_rating(star_match[1]);
3415                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3416                 player2Rating = string_to_rating(star_match[4]);
3417
3418                 if (appData.debugMode)
3419                   fprintf(debugFP, 
3420                           "Ratings from 'Creating:' %s %d, %s %d\n",
3421                           player1Name, player1Rating,
3422                           player2Name, player2Rating);
3423
3424                 continue;
3425             }
3426             
3427             /* Improved generic start/end-of-game messages */
3428             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3429                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3430                 /* If tkind == 0: */
3431                 /* star_match[0] is the game number */
3432                 /*           [1] is the white player's name */
3433                 /*           [2] is the black player's name */
3434                 /* For end-of-game: */
3435                 /*           [3] is the reason for the game end */
3436                 /*           [4] is a PGN end game-token, preceded by " " */
3437                 /* For start-of-game: */
3438                 /*           [3] begins with "Creating" or "Continuing" */
3439                 /*           [4] is " *" or empty (don't care). */
3440                 int gamenum = atoi(star_match[0]);
3441                 char *whitename, *blackname, *why, *endtoken;
3442                 ChessMove endtype = (ChessMove) 0;
3443
3444                 if (tkind == 0) {
3445                   whitename = star_match[1];
3446                   blackname = star_match[2];
3447                   why = star_match[3];
3448                   endtoken = star_match[4];
3449                 } else {
3450                   whitename = star_match[1];
3451                   blackname = star_match[3];
3452                   why = star_match[5];
3453                   endtoken = star_match[6];
3454                 }
3455
3456                 /* Game start messages */
3457                 if (strncmp(why, "Creating ", 9) == 0 ||
3458                     strncmp(why, "Continuing ", 11) == 0) {
3459                     gs_gamenum = gamenum;
3460                     strcpy(gs_kind, strchr(why, ' ') + 1);
3461                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3462 #if ZIPPY
3463                     if (appData.zippyPlay) {
3464                         ZippyGameStart(whitename, blackname);
3465                     }
3466 #endif /*ZIPPY*/
3467                     continue;
3468                 }
3469
3470                 /* Game end messages */
3471                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3472                     ics_gamenum != gamenum) {
3473                     continue;
3474                 }
3475                 while (endtoken[0] == ' ') endtoken++;
3476                 switch (endtoken[0]) {
3477                   case '*':
3478                   default:
3479                     endtype = GameUnfinished;
3480                     break;
3481                   case '0':
3482                     endtype = BlackWins;
3483                     break;
3484                   case '1':
3485                     if (endtoken[1] == '/')
3486                       endtype = GameIsDrawn;
3487                     else
3488                       endtype = WhiteWins;
3489                     break;
3490                 }
3491                 GameEnds(endtype, why, GE_ICS);
3492 #if ZIPPY
3493                 if (appData.zippyPlay && first.initDone) {
3494                     ZippyGameEnd(endtype, why);
3495                     if (first.pr == NULL) {
3496                       /* Start the next process early so that we'll
3497                          be ready for the next challenge */
3498                       StartChessProgram(&first);
3499                     }
3500                     /* Send "new" early, in case this command takes
3501                        a long time to finish, so that we'll be ready
3502                        for the next challenge. */
3503                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3504                     Reset(TRUE, TRUE);
3505                 }
3506 #endif /*ZIPPY*/
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i, "Removing game * from observation") ||
3511                 looking_at(buf, &i, "no longer observing game *") ||
3512                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3513                 if (gameMode == IcsObserving &&
3514                     atoi(star_match[0]) == ics_gamenum)
3515                   {
3516                       /* icsEngineAnalyze */
3517                       if (appData.icsEngineAnalyze) {
3518                             ExitAnalyzeMode();
3519                             ModeHighlight();
3520                       }
3521                       StopClocks();
3522                       gameMode = IcsIdle;
3523                       ics_gamenum = -1;
3524                       ics_user_moved = FALSE;
3525                   }
3526                 continue;
3527             }
3528
3529             if (looking_at(buf, &i, "no longer examining game *")) {
3530                 if (gameMode == IcsExamining &&
3531                     atoi(star_match[0]) == ics_gamenum)
3532                   {
3533                       gameMode = IcsIdle;
3534                       ics_gamenum = -1;
3535                       ics_user_moved = FALSE;
3536                   }
3537                 continue;
3538             }
3539
3540             /* Advance leftover_start past any newlines we find,
3541                so only partial lines can get reparsed */
3542             if (looking_at(buf, &i, "\n")) {
3543                 prevColor = curColor;
3544                 if (curColor != ColorNormal) {
3545                     if (oldi > next_out) {
3546                         SendToPlayer(&buf[next_out], oldi - next_out);
3547                         next_out = oldi;
3548                     }
3549                     Colorize(ColorNormal, FALSE);
3550                     curColor = ColorNormal;
3551                 }
3552                 if (started == STARTED_BOARD) {
3553                     started = STARTED_NONE;
3554                     parse[parse_pos] = NULLCHAR;
3555                     ParseBoard12(parse);
3556                     ics_user_moved = 0;
3557
3558                     /* Send premove here */
3559                     if (appData.premove) {
3560                       char str[MSG_SIZ];
3561                       if (currentMove == 0 &&
3562                           gameMode == IcsPlayingWhite &&
3563                           appData.premoveWhite) {
3564                         sprintf(str, "%s\n", appData.premoveWhiteText);
3565                         if (appData.debugMode)
3566                           fprintf(debugFP, "Sending premove:\n");
3567                         SendToICS(str);
3568                       } else if (currentMove == 1 &&
3569                                  gameMode == IcsPlayingBlack &&
3570                                  appData.premoveBlack) {
3571                         sprintf(str, "%s\n", appData.premoveBlackText);
3572                         if (appData.debugMode)
3573                           fprintf(debugFP, "Sending premove:\n");
3574                         SendToICS(str);
3575                       } else if (gotPremove) {
3576                         gotPremove = 0;
3577                         ClearPremoveHighlights();
3578                         if (appData.debugMode)
3579                           fprintf(debugFP, "Sending premove:\n");
3580                           UserMoveEvent(premoveFromX, premoveFromY, 
3581                                         premoveToX, premoveToY, 
3582                                         premovePromoChar);
3583                       }
3584                     }
3585
3586                     /* Usually suppress following prompt */
3587                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3588                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3589                         if (looking_at(buf, &i, "*% ")) {
3590                             savingComment = FALSE;
3591                             suppressKibitz = 0;
3592                         }
3593                     }
3594                     next_out = i;
3595                 } else if (started == STARTED_HOLDINGS) {
3596                     int gamenum;
3597                     char new_piece[MSG_SIZ];
3598                     started = STARTED_NONE;
3599                     parse[parse_pos] = NULLCHAR;
3600                     if (appData.debugMode)
3601                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3602                                                         parse, currentMove);
3603                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3604                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3605                         if (gameInfo.variant == VariantNormal) {
3606                           /* [HGM] We seem to switch variant during a game!
3607                            * Presumably no holdings were displayed, so we have
3608                            * to move the position two files to the right to
3609                            * create room for them!
3610                            */
3611                           VariantClass newVariant;
3612                           switch(gameInfo.boardWidth) { // base guess on board width
3613                                 case 9:  newVariant = VariantShogi; break;
3614                                 case 10: newVariant = VariantGreat; break;
3615                                 default: newVariant = VariantCrazyhouse; break;
3616                           }
3617                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3618                           /* Get a move list just to see the header, which
3619                              will tell us whether this is really bug or zh */
3620                           if (ics_getting_history == H_FALSE) {
3621                             ics_getting_history = H_REQUESTED;
3622                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3623                             SendToICS(str);
3624                           }
3625                         }
3626                         new_piece[0] = NULLCHAR;
3627                         sscanf(parse, "game %d white [%s black [%s <- %s",
3628                                &gamenum, white_holding, black_holding,
3629                                new_piece);
3630                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3631                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3632                         /* [HGM] copy holdings to board holdings area */
3633                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3634                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3635                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3636 #if ZIPPY
3637                         if (appData.zippyPlay && first.initDone) {
3638                             ZippyHoldings(white_holding, black_holding,
3639                                           new_piece);
3640                         }
3641 #endif /*ZIPPY*/
3642                         if (tinyLayout || smallLayout) {
3643                             char wh[16], bh[16];
3644                             PackHolding(wh, white_holding);
3645                             PackHolding(bh, black_holding);
3646                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3647                                     gameInfo.white, gameInfo.black);
3648                         } else {
3649                             sprintf(str, "%s [%s] vs. %s [%s]",
3650                                     gameInfo.white, white_holding,
3651                                     gameInfo.black, black_holding);
3652                         }
3653                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3654                         DrawPosition(FALSE, boards[currentMove]);
3655                         DisplayTitle(str);
3656                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3657                         sscanf(parse, "game %d white [%s black [%s <- %s",
3658                                &gamenum, white_holding, black_holding,
3659                                new_piece);
3660                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3661                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3662                         /* [HGM] copy holdings to partner-board holdings area */
3663                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3664                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3665                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3666                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3667                         if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own
3668                       }
3669                     }
3670                     /* Suppress following prompt */
3671                     if (looking_at(buf, &i, "*% ")) {
3672                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3673                         savingComment = FALSE;
3674                         suppressKibitz = 0;
3675                     }
3676                     next_out = i;
3677                 }
3678                 continue;
3679             }
3680
3681             i++;                /* skip unparsed character and loop back */
3682         }
3683         
3684         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3685 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3686 //          SendToPlayer(&buf[next_out], i - next_out);
3687             started != STARTED_HOLDINGS && leftover_start > next_out) {
3688             SendToPlayer(&buf[next_out], leftover_start - next_out);
3689             next_out = i;
3690         }
3691         
3692         leftover_len = buf_len - leftover_start;
3693         /* if buffer ends with something we couldn't parse,
3694            reparse it after appending the next read */
3695         
3696     } else if (count == 0) {
3697         RemoveInputSource(isr);
3698         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3699     } else {
3700         DisplayFatalError(_("Error reading from ICS"), error, 1);
3701     }
3702 }
3703
3704
3705 /* Board style 12 looks like this:
3706    
3707    <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
3708    
3709  * The "<12> " is stripped before it gets to this routine.  The two
3710  * trailing 0's (flip state and clock ticking) are later addition, and
3711  * some chess servers may not have them, or may have only the first.
3712  * Additional trailing fields may be added in the future.  
3713  */
3714
3715 #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"
3716
3717 #define RELATION_OBSERVING_PLAYED    0
3718 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3719 #define RELATION_PLAYING_MYMOVE      1
3720 #define RELATION_PLAYING_NOTMYMOVE  -1
3721 #define RELATION_EXAMINING           2
3722 #define RELATION_ISOLATED_BOARD     -3
3723 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3724
3725 void
3726 ParseBoard12(string)
3727      char *string;
3728
3729     GameMode newGameMode;
3730     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3731     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3732     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3733     char to_play, board_chars[200];
3734     char move_str[500], str[500], elapsed_time[500];
3735     char black[32], white[32];
3736     Board board;
3737     int prevMove = currentMove;
3738     int ticking = 2;
3739     ChessMove moveType;
3740     int fromX, fromY, toX, toY;
3741     char promoChar;
3742     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3743     char *bookHit = NULL; // [HGM] book
3744     Boolean weird = FALSE, reqFlag = FALSE;
3745
3746     fromX = fromY = toX = toY = -1;
3747     
3748     newGame = FALSE;
3749
3750     if (appData.debugMode)
3751       fprintf(debugFP, _("Parsing board: %s\n"), string);
3752
3753     move_str[0] = NULLCHAR;
3754     elapsed_time[0] = NULLCHAR;
3755     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3756         int  i = 0, j;
3757         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3758             if(string[i] == ' ') { ranks++; files = 0; }
3759             else files++;
3760             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3761             i++;
3762         }
3763         for(j = 0; j <i; j++) board_chars[j] = string[j];
3764         board_chars[i] = '\0';
3765         string += i + 1;
3766     }
3767     n = sscanf(string, PATTERN, &to_play, &double_push,
3768                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3769                &gamenum, white, black, &relation, &basetime, &increment,
3770                &white_stren, &black_stren, &white_time, &black_time,
3771                &moveNum, str, elapsed_time, move_str, &ics_flip,
3772                &ticking);
3773
3774     if (n < 21) {
3775         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3776         DisplayError(str, 0);
3777         return;
3778     }
3779
3780     /* Convert the move number to internal form */
3781     moveNum = (moveNum - 1) * 2;
3782     if (to_play == 'B') moveNum++;
3783     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3784       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3785                         0, 1);
3786       return;
3787     }
3788     
3789     switch (relation) {
3790       case RELATION_OBSERVING_PLAYED:
3791       case RELATION_OBSERVING_STATIC:
3792         if (gamenum == -1) {
3793             /* Old ICC buglet */
3794             relation = RELATION_OBSERVING_STATIC;
3795         }
3796         newGameMode = IcsObserving;
3797         break;
3798       case RELATION_PLAYING_MYMOVE:
3799       case RELATION_PLAYING_NOTMYMOVE:
3800         newGameMode =
3801           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3802             IcsPlayingWhite : IcsPlayingBlack;
3803         break;
3804       case RELATION_EXAMINING:
3805         newGameMode = IcsExamining;
3806         break;
3807       case RELATION_ISOLATED_BOARD:
3808       default:
3809         /* Just display this board.  If user was doing something else,
3810            we will forget about it until the next board comes. */ 
3811         newGameMode = IcsIdle;
3812         break;
3813       case RELATION_STARTING_POSITION:
3814         newGameMode = gameMode;
3815         break;
3816     }
3817     
3818     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3819          && newGameMode == IcsObserving && appData.bgObserve) {
3820       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3821       for (k = 0; k < ranks; k++) {
3822         for (j = 0; j < files; j++)
3823           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3824         if(gameInfo.holdingsWidth > 1) {
3825              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3826              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3827         }
3828       }
3829       if(appData.dualBoard) { twoBoards = partnerUp = 1; flipView = !flipView; InitDrawingSizes(-2,0); } // [HGM] dual
3830       CopyBoard(partnerBoard, board);
3831       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3832       if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own game!
3833       sprintf(partnerStatus, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3834                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3835       DisplayMessage(partnerStatus, "");
3836       return;
3837     }
3838
3839     /* Modify behavior for initial board display on move listing
3840        of wild games.
3841        */
3842     switch (ics_getting_history) {
3843       case H_FALSE:
3844       case H_REQUESTED:
3845         break;
3846       case H_GOT_REQ_HEADER:
3847       case H_GOT_UNREQ_HEADER:
3848         /* This is the initial position of the current game */
3849         gamenum = ics_gamenum;
3850         moveNum = 0;            /* old ICS bug workaround */
3851         if (to_play == 'B') {
3852           startedFromSetupPosition = TRUE;
3853           blackPlaysFirst = TRUE;
3854           moveNum = 1;
3855           if (forwardMostMove == 0) forwardMostMove = 1;
3856           if (backwardMostMove == 0) backwardMostMove = 1;
3857           if (currentMove == 0) currentMove = 1;
3858         }
3859         newGameMode = gameMode;
3860         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3861         break;
3862       case H_GOT_UNWANTED_HEADER:
3863         /* This is an initial board that we don't want */
3864         return;
3865       case H_GETTING_MOVES:
3866         /* Should not happen */
3867         DisplayError(_("Error gathering move list: extra board"), 0);
3868         ics_getting_history = H_FALSE;
3869         return;
3870     }
3871
3872    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3873                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3874      /* [HGM] We seem to have switched variant unexpectedly
3875       * Try to guess new variant from board size
3876       */
3877           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3878           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3879           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3880           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3881           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3882           if(!weird) newVariant = VariantNormal;
3883           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3884           /* Get a move list just to see the header, which
3885              will tell us whether this is really bug or zh */
3886           if (ics_getting_history == H_FALSE) {
3887             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3888             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3889             SendToICS(str);
3890           }
3891     }
3892     
3893     /* Take action if this is the first board of a new game, or of a
3894        different game than is currently being displayed.  */
3895     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3896         relation == RELATION_ISOLATED_BOARD) {
3897         
3898         /* Forget the old game and get the history (if any) of the new one */
3899         if (gameMode != BeginningOfGame) {
3900           Reset(TRUE, TRUE);
3901         }
3902         newGame = TRUE;
3903         if (appData.autoRaiseBoard) BoardToTop();
3904         prevMove = -3;
3905         if (gamenum == -1) {
3906             newGameMode = IcsIdle;
3907         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3908                    appData.getMoveList && !reqFlag) {
3909             /* Need to get game history */
3910             ics_getting_history = H_REQUESTED;
3911             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3912             SendToICS(str);
3913         }
3914         
3915         /* Initially flip the board to have black on the bottom if playing
3916            black or if the ICS flip flag is set, but let the user change
3917            it with the Flip View button. */
3918         flipView = appData.autoFlipView ? 
3919           (newGameMode == IcsPlayingBlack) || ics_flip :
3920           appData.flipView;
3921         
3922         /* Done with values from previous mode; copy in new ones */
3923         gameMode = newGameMode;
3924         ModeHighlight();
3925         ics_gamenum = gamenum;
3926         if (gamenum == gs_gamenum) {
3927             int klen = strlen(gs_kind);
3928             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3929             sprintf(str, "ICS %s", gs_kind);
3930             gameInfo.event = StrSave(str);
3931         } else {
3932             gameInfo.event = StrSave("ICS game");
3933         }
3934         gameInfo.site = StrSave(appData.icsHost);
3935         gameInfo.date = PGNDate();
3936         gameInfo.round = StrSave("-");
3937         gameInfo.white = StrSave(white);
3938         gameInfo.black = StrSave(black);
3939         timeControl = basetime * 60 * 1000;
3940         timeControl_2 = 0;
3941         timeIncrement = increment * 1000;
3942         movesPerSession = 0;
3943         gameInfo.timeControl = TimeControlTagValue();
3944         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3945   if (appData.debugMode) {
3946     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3947     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3948     setbuf(debugFP, NULL);
3949   }
3950
3951         gameInfo.outOfBook = NULL;
3952         
3953         /* Do we have the ratings? */
3954         if (strcmp(player1Name, white) == 0 &&
3955             strcmp(player2Name, black) == 0) {
3956             if (appData.debugMode)
3957               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3958                       player1Rating, player2Rating);
3959             gameInfo.whiteRating = player1Rating;
3960             gameInfo.blackRating = player2Rating;
3961         } else if (strcmp(player2Name, white) == 0 &&
3962                    strcmp(player1Name, black) == 0) {
3963             if (appData.debugMode)
3964               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3965                       player2Rating, player1Rating);
3966             gameInfo.whiteRating = player2Rating;
3967             gameInfo.blackRating = player1Rating;
3968         }
3969         player1Name[0] = player2Name[0] = NULLCHAR;
3970
3971         /* Silence shouts if requested */
3972         if (appData.quietPlay &&
3973             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3974             SendToICS(ics_prefix);
3975             SendToICS("set shout 0\n");
3976         }
3977     }
3978     
3979     /* Deal with midgame name changes */
3980     if (!newGame) {
3981         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3982             if (gameInfo.white) free(gameInfo.white);
3983             gameInfo.white = StrSave(white);
3984         }
3985         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3986             if (gameInfo.black) free(gameInfo.black);
3987             gameInfo.black = StrSave(black);
3988         }
3989     }
3990     
3991     /* Throw away game result if anything actually changes in examine mode */
3992     if (gameMode == IcsExamining && !newGame) {
3993         gameInfo.result = GameUnfinished;
3994         if (gameInfo.resultDetails != NULL) {
3995             free(gameInfo.resultDetails);
3996             gameInfo.resultDetails = NULL;
3997         }
3998     }
3999     
4000     /* In pausing && IcsExamining mode, we ignore boards coming
4001        in if they are in a different variation than we are. */
4002     if (pauseExamInvalid) return;
4003     if (pausing && gameMode == IcsExamining) {
4004         if (moveNum <= pauseExamForwardMostMove) {
4005             pauseExamInvalid = TRUE;
4006             forwardMostMove = pauseExamForwardMostMove;
4007             return;
4008         }
4009     }
4010     
4011   if (appData.debugMode) {
4012     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4013   }
4014     /* Parse the board */
4015     for (k = 0; k < ranks; k++) {
4016       for (j = 0; j < files; j++)
4017         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4018       if(gameInfo.holdingsWidth > 1) {
4019            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4020            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4021       }
4022     }
4023     CopyBoard(boards[moveNum], board);
4024     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4025     if (moveNum == 0) {
4026         startedFromSetupPosition =
4027           !CompareBoards(board, initialPosition);
4028         if(startedFromSetupPosition)
4029             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4030     }
4031
4032     /* [HGM] Set castling rights. Take the outermost Rooks,
4033        to make it also work for FRC opening positions. Note that board12
4034        is really defective for later FRC positions, as it has no way to
4035        indicate which Rook can castle if they are on the same side of King.
4036        For the initial position we grant rights to the outermost Rooks,
4037        and remember thos rights, and we then copy them on positions
4038        later in an FRC game. This means WB might not recognize castlings with
4039        Rooks that have moved back to their original position as illegal,
4040        but in ICS mode that is not its job anyway.
4041     */
4042     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4043     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4044
4045         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4046             if(board[0][i] == WhiteRook) j = i;
4047         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4048         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4049             if(board[0][i] == WhiteRook) j = i;
4050         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4051         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4052             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4053         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4054         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4055             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4056         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4057
4058         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4059         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4060             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4061         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4062             if(board[BOARD_HEIGHT-1][k] == bKing)
4063                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4064         if(gameInfo.variant == VariantTwoKings) {
4065             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4066             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4067             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4068         }
4069     } else { int r;
4070         r = boards[moveNum][CASTLING][0] = initialRights[0];
4071         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4072         r = boards[moveNum][CASTLING][1] = initialRights[1];
4073         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4074         r = boards[moveNum][CASTLING][3] = initialRights[3];
4075         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4076         r = boards[moveNum][CASTLING][4] = initialRights[4];
4077         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4078         /* wildcastle kludge: always assume King has rights */
4079         r = boards[moveNum][CASTLING][2] = initialRights[2];
4080         r = boards[moveNum][CASTLING][5] = initialRights[5];
4081     }
4082     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4083     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4084
4085     
4086     if (ics_getting_history == H_GOT_REQ_HEADER ||
4087         ics_getting_history == H_GOT_UNREQ_HEADER) {
4088         /* This was an initial position from a move list, not
4089            the current position */
4090         return;
4091     }
4092     
4093     /* Update currentMove and known move number limits */
4094     newMove = newGame || moveNum > forwardMostMove;
4095
4096     if (newGame) {
4097         forwardMostMove = backwardMostMove = currentMove = moveNum;
4098         if (gameMode == IcsExamining && moveNum == 0) {
4099           /* Workaround for ICS limitation: we are not told the wild
4100              type when starting to examine a game.  But if we ask for
4101              the move list, the move list header will tell us */
4102             ics_getting_history = H_REQUESTED;
4103             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4104             SendToICS(str);
4105         }
4106     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4107                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4108 #if ZIPPY
4109         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4110         /* [HGM] applied this also to an engine that is silently watching        */
4111         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4112             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4113             gameInfo.variant == currentlyInitializedVariant) {
4114           takeback = forwardMostMove - moveNum;
4115           for (i = 0; i < takeback; i++) {
4116             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4117             SendToProgram("undo\n", &first);
4118           }
4119         }
4120 #endif
4121
4122         forwardMostMove = moveNum;
4123         if (!pausing || currentMove > forwardMostMove)
4124           currentMove = forwardMostMove;
4125     } else {
4126         /* New part of history that is not contiguous with old part */ 
4127         if (pausing && gameMode == IcsExamining) {
4128             pauseExamInvalid = TRUE;
4129             forwardMostMove = pauseExamForwardMostMove;
4130             return;
4131         }
4132         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4133 #if ZIPPY
4134             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4135                 // [HGM] when we will receive the move list we now request, it will be
4136                 // fed to the engine from the first move on. So if the engine is not
4137                 // in the initial position now, bring it there.
4138                 InitChessProgram(&first, 0);
4139             }
4140 #endif
4141             ics_getting_history = H_REQUESTED;
4142             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4143             SendToICS(str);
4144         }
4145         forwardMostMove = backwardMostMove = currentMove = moveNum;
4146     }
4147     
4148     /* Update the clocks */
4149     if (strchr(elapsed_time, '.')) {
4150       /* Time is in ms */
4151       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4152       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4153     } else {
4154       /* Time is in seconds */
4155       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4156       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4157     }
4158       
4159
4160 #if ZIPPY
4161     if (appData.zippyPlay && newGame &&
4162         gameMode != IcsObserving && gameMode != IcsIdle &&
4163         gameMode != IcsExamining)
4164       ZippyFirstBoard(moveNum, basetime, increment);
4165 #endif
4166     
4167     /* Put the move on the move list, first converting
4168        to canonical algebraic form. */
4169     if (moveNum > 0) {
4170   if (appData.debugMode) {
4171     if (appData.debugMode) { int f = forwardMostMove;
4172         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4173                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4174                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4175     }
4176     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4177     fprintf(debugFP, "moveNum = %d\n", moveNum);
4178     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4179     setbuf(debugFP, NULL);
4180   }
4181         if (moveNum <= backwardMostMove) {
4182             /* We don't know what the board looked like before
4183                this move.  Punt. */
4184             strcpy(parseList[moveNum - 1], move_str);
4185             strcat(parseList[moveNum - 1], " ");
4186             strcat(parseList[moveNum - 1], elapsed_time);
4187             moveList[moveNum - 1][0] = NULLCHAR;
4188         } else if (strcmp(move_str, "none") == 0) {
4189             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4190             /* Again, we don't know what the board looked like;
4191                this is really the start of the game. */
4192             parseList[moveNum - 1][0] = NULLCHAR;
4193             moveList[moveNum - 1][0] = NULLCHAR;
4194             backwardMostMove = moveNum;
4195             startedFromSetupPosition = TRUE;
4196             fromX = fromY = toX = toY = -1;
4197         } else {
4198           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4199           //                 So we parse the long-algebraic move string in stead of the SAN move
4200           int valid; char buf[MSG_SIZ], *prom;
4201
4202           // str looks something like "Q/a1-a2"; kill the slash
4203           if(str[1] == '/') 
4204                 sprintf(buf, "%c%s", str[0], str+2);
4205           else  strcpy(buf, str); // might be castling
4206           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4207                 strcat(buf, prom); // long move lacks promo specification!
4208           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4209                 if(appData.debugMode) 
4210                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4211                 strcpy(move_str, buf);
4212           }
4213           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4214                                 &fromX, &fromY, &toX, &toY, &promoChar)
4215                || ParseOneMove(buf, moveNum - 1, &moveType,
4216                                 &fromX, &fromY, &toX, &toY, &promoChar);
4217           // end of long SAN patch
4218           if (valid) {
4219             (void) CoordsToAlgebraic(boards[moveNum - 1],
4220                                      PosFlags(moveNum - 1),
4221                                      fromY, fromX, toY, toX, promoChar,
4222                                      parseList[moveNum-1]);
4223             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4224               case MT_NONE:
4225               case MT_STALEMATE:
4226               default:
4227                 break;
4228               case MT_CHECK:
4229                 if(gameInfo.variant != VariantShogi)
4230                     strcat(parseList[moveNum - 1], "+");
4231                 break;
4232               case MT_CHECKMATE:
4233               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4234                 strcat(parseList[moveNum - 1], "#");
4235                 break;
4236             }
4237             strcat(parseList[moveNum - 1], " ");
4238             strcat(parseList[moveNum - 1], elapsed_time);
4239             /* currentMoveString is set as a side-effect of ParseOneMove */
4240             strcpy(moveList[moveNum - 1], currentMoveString);
4241             strcat(moveList[moveNum - 1], "\n");
4242           } else {
4243             /* Move from ICS was illegal!?  Punt. */
4244   if (appData.debugMode) {
4245     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4247   }
4248             strcpy(parseList[moveNum - 1], move_str);
4249             strcat(parseList[moveNum - 1], " ");
4250             strcat(parseList[moveNum - 1], elapsed_time);
4251             moveList[moveNum - 1][0] = NULLCHAR;
4252             fromX = fromY = toX = toY = -1;
4253           }
4254         }
4255   if (appData.debugMode) {
4256     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4257     setbuf(debugFP, NULL);
4258   }
4259
4260 #if ZIPPY
4261         /* Send move to chess program (BEFORE animating it). */
4262         if (appData.zippyPlay && !newGame && newMove && 
4263            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4264
4265             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4266                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4267                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4268                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4269                             move_str);
4270                     DisplayError(str, 0);
4271                 } else {
4272                     if (first.sendTime) {
4273                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4274                     }
4275                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4276                     if (firstMove && !bookHit) {
4277                         firstMove = FALSE;
4278                         if (first.useColors) {
4279                           SendToProgram(gameMode == IcsPlayingWhite ?
4280                                         "white\ngo\n" :
4281                                         "black\ngo\n", &first);
4282                         } else {
4283                           SendToProgram("go\n", &first);
4284                         }
4285                         first.maybeThinking = TRUE;
4286                     }
4287                 }
4288             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4289               if (moveList[moveNum - 1][0] == NULLCHAR) {
4290                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4291                 DisplayError(str, 0);
4292               } else {
4293                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4294                 SendMoveToProgram(moveNum - 1, &first);
4295               }
4296             }
4297         }
4298 #endif
4299     }
4300
4301     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4302         /* If move comes from a remote source, animate it.  If it
4303            isn't remote, it will have already been animated. */
4304         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4305             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4306         }
4307         if (!pausing && appData.highlightLastMove) {
4308             SetHighlights(fromX, fromY, toX, toY);
4309         }
4310     }
4311     
4312     /* Start the clocks */
4313     whiteFlag = blackFlag = FALSE;
4314     appData.clockMode = !(basetime == 0 && increment == 0);
4315     if (ticking == 0) {
4316       ics_clock_paused = TRUE;
4317       StopClocks();
4318     } else if (ticking == 1) {
4319       ics_clock_paused = FALSE;
4320     }
4321     if (gameMode == IcsIdle ||
4322         relation == RELATION_OBSERVING_STATIC ||
4323         relation == RELATION_EXAMINING ||
4324         ics_clock_paused)
4325       DisplayBothClocks();
4326     else
4327       StartClocks();
4328     
4329     /* Display opponents and material strengths */
4330     if (gameInfo.variant != VariantBughouse &&
4331         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4332         if (tinyLayout || smallLayout) {
4333             if(gameInfo.variant == VariantNormal)
4334                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4335                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4336                     basetime, increment);
4337             else
4338                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4339                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4340                     basetime, increment, (int) gameInfo.variant);
4341         } else {
4342             if(gameInfo.variant == VariantNormal)
4343                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4344                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4345                     basetime, increment);
4346             else
4347                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4348                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4349                     basetime, increment, VariantName(gameInfo.variant));
4350         }
4351         DisplayTitle(str);
4352   if (appData.debugMode) {
4353     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4354   }
4355     }
4356
4357
4358     /* Display the board */
4359     if (!pausing && !appData.noGUI) {
4360       
4361       if (appData.premove)
4362           if (!gotPremove || 
4363              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4364              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4365               ClearPremoveHighlights();
4366
4367       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4368         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4369       DrawPosition(j, boards[currentMove]);
4370
4371       DisplayMove(moveNum - 1);
4372       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4373             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4374               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4375         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4376       }
4377     }
4378
4379     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4380 #if ZIPPY
4381     if(bookHit) { // [HGM] book: simulate book reply
4382         static char bookMove[MSG_SIZ]; // a bit generous?
4383
4384         programStats.nodes = programStats.depth = programStats.time = 
4385         programStats.score = programStats.got_only_move = 0;
4386         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4387
4388         strcpy(bookMove, "move ");
4389         strcat(bookMove, bookHit);
4390         HandleMachineMove(bookMove, &first);
4391     }
4392 #endif
4393 }
4394
4395 void
4396 GetMoveListEvent()
4397 {
4398     char buf[MSG_SIZ];
4399     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4400         ics_getting_history = H_REQUESTED;
4401         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4402         SendToICS(buf);
4403     }
4404 }
4405
4406 void
4407 AnalysisPeriodicEvent(force)
4408      int force;
4409 {
4410     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4411          && !force) || !appData.periodicUpdates)
4412       return;
4413
4414     /* Send . command to Crafty to collect stats */
4415     SendToProgram(".\n", &first);
4416
4417     /* Don't send another until we get a response (this makes
4418        us stop sending to old Crafty's which don't understand
4419        the "." command (sending illegal cmds resets node count & time,
4420        which looks bad)) */
4421     programStats.ok_to_send = 0;
4422 }
4423
4424 void ics_update_width(new_width)
4425         int new_width;
4426 {
4427         ics_printf("set width %d\n", new_width);
4428 }
4429
4430 void
4431 SendMoveToProgram(moveNum, cps)
4432      int moveNum;
4433      ChessProgramState *cps;
4434 {
4435     char buf[MSG_SIZ];
4436
4437     if (cps->useUsermove) {
4438       SendToProgram("usermove ", cps);
4439     }
4440     if (cps->useSAN) {
4441       char *space;
4442       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4443         int len = space - parseList[moveNum];
4444         memcpy(buf, parseList[moveNum], len);
4445         buf[len++] = '\n';
4446         buf[len] = NULLCHAR;
4447       } else {
4448         sprintf(buf, "%s\n", parseList[moveNum]);
4449       }
4450       SendToProgram(buf, cps);
4451     } else {
4452       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4453         AlphaRank(moveList[moveNum], 4);
4454         SendToProgram(moveList[moveNum], cps);
4455         AlphaRank(moveList[moveNum], 4); // and back
4456       } else
4457       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4458        * the engine. It would be nice to have a better way to identify castle 
4459        * moves here. */
4460       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4461                                                                          && cps->useOOCastle) {
4462         int fromX = moveList[moveNum][0] - AAA; 
4463         int fromY = moveList[moveNum][1] - ONE;
4464         int toX = moveList[moveNum][2] - AAA; 
4465         int toY = moveList[moveNum][3] - ONE;
4466         if((boards[moveNum][fromY][fromX] == WhiteKing 
4467             && boards[moveNum][toY][toX] == WhiteRook)
4468            || (boards[moveNum][fromY][fromX] == BlackKing 
4469                && boards[moveNum][toY][toX] == BlackRook)) {
4470           if(toX > fromX) SendToProgram("O-O\n", cps);
4471           else SendToProgram("O-O-O\n", cps);
4472         }
4473         else SendToProgram(moveList[moveNum], cps);
4474       }
4475       else SendToProgram(moveList[moveNum], cps);
4476       /* End of additions by Tord */
4477     }
4478
4479     /* [HGM] setting up the opening has brought engine in force mode! */
4480     /*       Send 'go' if we are in a mode where machine should play. */
4481     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4482         (gameMode == TwoMachinesPlay   ||
4483 #ifdef ZIPPY
4484          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4485 #endif
4486          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4487         SendToProgram("go\n", cps);
4488   if (appData.debugMode) {
4489     fprintf(debugFP, "(extra)\n");
4490   }
4491     }
4492     setboardSpoiledMachineBlack = 0;
4493 }
4494
4495 void
4496 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4497      ChessMove moveType;
4498      int fromX, fromY, toX, toY;
4499 {
4500     char user_move[MSG_SIZ];
4501
4502     switch (moveType) {
4503       default:
4504         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4505                 (int)moveType, fromX, fromY, toX, toY);
4506         DisplayError(user_move + strlen("say "), 0);
4507         break;
4508       case WhiteKingSideCastle:
4509       case BlackKingSideCastle:
4510       case WhiteQueenSideCastleWild:
4511       case BlackQueenSideCastleWild:
4512       /* PUSH Fabien */
4513       case WhiteHSideCastleFR:
4514       case BlackHSideCastleFR:
4515       /* POP Fabien */
4516         sprintf(user_move, "o-o\n");
4517         break;
4518       case WhiteQueenSideCastle:
4519       case BlackQueenSideCastle:
4520       case WhiteKingSideCastleWild:
4521       case BlackKingSideCastleWild:
4522       /* PUSH Fabien */
4523       case WhiteASideCastleFR:
4524       case BlackASideCastleFR:
4525       /* POP Fabien */
4526         sprintf(user_move, "o-o-o\n");
4527         break;
4528       case WhitePromotionQueen:
4529       case BlackPromotionQueen:
4530       case WhitePromotionRook:
4531       case BlackPromotionRook:
4532       case WhitePromotionBishop:
4533       case BlackPromotionBishop:
4534       case WhitePromotionKnight:
4535       case BlackPromotionKnight:
4536       case WhitePromotionKing:
4537       case BlackPromotionKing:
4538       case WhitePromotionChancellor:
4539       case BlackPromotionChancellor:
4540       case WhitePromotionArchbishop:
4541       case BlackPromotionArchbishop:
4542         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4543             sprintf(user_move, "%c%c%c%c=%c\n",
4544                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4545                 PieceToChar(WhiteFerz));
4546         else if(gameInfo.variant == VariantGreat)
4547             sprintf(user_move, "%c%c%c%c=%c\n",
4548                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4549                 PieceToChar(WhiteMan));
4550         else
4551             sprintf(user_move, "%c%c%c%c=%c\n",
4552                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4553                 PieceToChar(PromoPiece(moveType)));
4554         break;
4555       case WhiteDrop:
4556       case BlackDrop:
4557         sprintf(user_move, "%c@%c%c\n",
4558                 ToUpper(PieceToChar((ChessSquare) fromX)),
4559                 AAA + toX, ONE + toY);
4560         break;
4561       case NormalMove:
4562       case WhiteCapturesEnPassant:
4563       case BlackCapturesEnPassant:
4564       case IllegalMove:  /* could be a variant we don't quite understand */
4565         sprintf(user_move, "%c%c%c%c\n",
4566                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4567         break;
4568     }
4569     SendToICS(user_move);
4570     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4571         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4572 }
4573
4574 void
4575 UploadGameEvent()
4576 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4577     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4578     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4579     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4580         DisplayError("You cannot do this while you are playing or observing", 0);
4581         return;
4582     }
4583     if(gameMode != IcsExamining) { // is this ever not the case?
4584         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4585
4586         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4587             sprintf(command, "match %s", ics_handle);
4588         } else { // on FICS we must first go to general examine mode
4589             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4590         }
4591         if(gameInfo.variant != VariantNormal) {
4592             // try figure out wild number, as xboard names are not always valid on ICS
4593             for(i=1; i<=36; i++) {
4594                 sprintf(buf, "wild/%d", i);
4595                 if(StringToVariant(buf) == gameInfo.variant) break;
4596             }
4597             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4598             else if(i == 22) sprintf(buf, "%s fr\n", command);
4599             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4600         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4601         SendToICS(ics_prefix);
4602         SendToICS(buf);
4603         if(startedFromSetupPosition || backwardMostMove != 0) {
4604           fen = PositionToFEN(backwardMostMove, NULL);
4605           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4606             sprintf(buf, "loadfen %s\n", fen);
4607             SendToICS(buf);
4608           } else { // FICS: everything has to set by separate bsetup commands
4609             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4610             sprintf(buf, "bsetup fen %s\n", fen);
4611             SendToICS(buf);
4612             if(!WhiteOnMove(backwardMostMove)) {
4613                 SendToICS("bsetup tomove black\n");
4614             }
4615             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4616             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4617             SendToICS(buf);
4618             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4619             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4620             SendToICS(buf);
4621             i = boards[backwardMostMove][EP_STATUS];
4622             if(i >= 0) { // set e.p.
4623                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4624                 SendToICS(buf);
4625             }
4626             bsetup++;
4627           }
4628         }
4629       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4630             SendToICS("bsetup done\n"); // switch to normal examining.
4631     }
4632     for(i = backwardMostMove; i<last; i++) {
4633         char buf[20];
4634         sprintf(buf, "%s\n", parseList[i]);
4635         SendToICS(buf);
4636     }
4637     SendToICS(ics_prefix);
4638     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4639 }
4640
4641 void
4642 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4643      int rf, ff, rt, ft;
4644      char promoChar;
4645      char move[7];
4646 {
4647     if (rf == DROP_RANK) {
4648         sprintf(move, "%c@%c%c\n",
4649                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4650     } else {
4651         if (promoChar == 'x' || promoChar == NULLCHAR) {
4652             sprintf(move, "%c%c%c%c\n",
4653                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4654         } else {
4655             sprintf(move, "%c%c%c%c%c\n",
4656                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4657         }
4658     }
4659 }
4660
4661 void
4662 ProcessICSInitScript(f)
4663      FILE *f;
4664 {
4665     char buf[MSG_SIZ];
4666
4667     while (fgets(buf, MSG_SIZ, f)) {
4668         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4669     }
4670
4671     fclose(f);
4672 }
4673
4674
4675 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4676 void
4677 AlphaRank(char *move, int n)
4678 {
4679 //    char *p = move, c; int x, y;
4680
4681     if (appData.debugMode) {
4682         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4683     }
4684
4685     if(move[1]=='*' && 
4686        move[2]>='0' && move[2]<='9' &&
4687        move[3]>='a' && move[3]<='x'    ) {
4688         move[1] = '@';
4689         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4690         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4691     } else
4692     if(move[0]>='0' && move[0]<='9' &&
4693        move[1]>='a' && move[1]<='x' &&
4694        move[2]>='0' && move[2]<='9' &&
4695        move[3]>='a' && move[3]<='x'    ) {
4696         /* input move, Shogi -> normal */
4697         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4698         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4699         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4700         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4701     } else
4702     if(move[1]=='@' &&
4703        move[3]>='0' && move[3]<='9' &&
4704        move[2]>='a' && move[2]<='x'    ) {
4705         move[1] = '*';
4706         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4707         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4708     } else
4709     if(
4710        move[0]>='a' && move[0]<='x' &&
4711        move[3]>='0' && move[3]<='9' &&
4712        move[2]>='a' && move[2]<='x'    ) {
4713          /* output move, normal -> Shogi */
4714         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4715         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4716         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4717         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4718         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4719     }
4720     if (appData.debugMode) {
4721         fprintf(debugFP, "   out = '%s'\n", move);
4722     }
4723 }
4724
4725 char yy_textstr[8000];
4726
4727 /* Parser for moves from gnuchess, ICS, or user typein box */
4728 Boolean
4729 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4730      char *move;
4731      int moveNum;
4732      ChessMove *moveType;
4733      int *fromX, *fromY, *toX, *toY;
4734      char *promoChar;
4735 {       
4736     if (appData.debugMode) {
4737         fprintf(debugFP, "move to parse: %s\n", move);
4738     }
4739     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4740
4741     switch (*moveType) {
4742       case WhitePromotionChancellor:
4743       case BlackPromotionChancellor:
4744       case WhitePromotionArchbishop:
4745       case BlackPromotionArchbishop:
4746       case WhitePromotionQueen:
4747       case BlackPromotionQueen:
4748       case WhitePromotionRook:
4749       case BlackPromotionRook:
4750       case WhitePromotionBishop:
4751       case BlackPromotionBishop:
4752       case WhitePromotionKnight:
4753       case BlackPromotionKnight:
4754       case WhitePromotionKing:
4755       case BlackPromotionKing:
4756       case NormalMove:
4757       case WhiteCapturesEnPassant:
4758       case BlackCapturesEnPassant:
4759       case WhiteKingSideCastle:
4760       case WhiteQueenSideCastle:
4761       case BlackKingSideCastle:
4762       case BlackQueenSideCastle:
4763       case WhiteKingSideCastleWild:
4764       case WhiteQueenSideCastleWild:
4765       case BlackKingSideCastleWild:
4766       case BlackQueenSideCastleWild:
4767       /* Code added by Tord: */
4768       case WhiteHSideCastleFR:
4769       case WhiteASideCastleFR:
4770       case BlackHSideCastleFR:
4771       case BlackASideCastleFR:
4772       /* End of code added by Tord */
4773       case IllegalMove:         /* bug or odd chess variant */
4774         *fromX = currentMoveString[0] - AAA;
4775         *fromY = currentMoveString[1] - ONE;
4776         *toX = currentMoveString[2] - AAA;
4777         *toY = currentMoveString[3] - ONE;
4778         *promoChar = currentMoveString[4];
4779         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4780             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4781     if (appData.debugMode) {
4782         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4783     }
4784             *fromX = *fromY = *toX = *toY = 0;
4785             return FALSE;
4786         }
4787         if (appData.testLegality) {
4788           return (*moveType != IllegalMove);
4789         } else {
4790           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4791                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4792         }
4793
4794       case WhiteDrop:
4795       case BlackDrop:
4796         *fromX = *moveType == WhiteDrop ?
4797           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4798           (int) CharToPiece(ToLower(currentMoveString[0]));
4799         *fromY = DROP_RANK;
4800         *toX = currentMoveString[2] - AAA;
4801         *toY = currentMoveString[3] - ONE;
4802         *promoChar = NULLCHAR;
4803         return TRUE;
4804
4805       case AmbiguousMove:
4806       case ImpossibleMove:
4807       case (ChessMove) 0:       /* end of file */
4808       case ElapsedTime:
4809       case Comment:
4810       case PGNTag:
4811       case NAG:
4812       case WhiteWins:
4813       case BlackWins:
4814       case GameIsDrawn:
4815       default:
4816     if (appData.debugMode) {
4817         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4818     }
4819         /* bug? */
4820         *fromX = *fromY = *toX = *toY = 0;
4821         *promoChar = NULLCHAR;
4822         return FALSE;
4823     }
4824 }
4825
4826
4827 void
4828 ParsePV(char *pv, Boolean storeComments)
4829 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4830   int fromX, fromY, toX, toY; char promoChar;
4831   ChessMove moveType;
4832   Boolean valid;
4833   int nr = 0;
4834
4835   endPV = forwardMostMove;
4836   do {
4837     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4838     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4839     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4840 if(appData.debugMode){
4841 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);
4842 }
4843     if(!valid && nr == 0 &&
4844        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4845         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4846         // Hande case where played move is different from leading PV move
4847         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4848         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4849         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4850         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4851           endPV += 2; // if position different, keep this
4852           moveList[endPV-1][0] = fromX + AAA;
4853           moveList[endPV-1][1] = fromY + ONE;
4854           moveList[endPV-1][2] = toX + AAA;
4855           moveList[endPV-1][3] = toY + ONE;
4856           parseList[endPV-1][0] = NULLCHAR;
4857           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4858         }
4859       }
4860     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4861     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4862     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4863     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4864         valid++; // allow comments in PV
4865         continue;
4866     }
4867     nr++;
4868     if(endPV+1 > framePtr) break; // no space, truncate
4869     if(!valid) break;
4870     endPV++;
4871     CopyBoard(boards[endPV], boards[endPV-1]);
4872     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4873     moveList[endPV-1][0] = fromX + AAA;
4874     moveList[endPV-1][1] = fromY + ONE;
4875     moveList[endPV-1][2] = toX + AAA;
4876     moveList[endPV-1][3] = toY + ONE;
4877     if(storeComments)
4878         CoordsToAlgebraic(boards[endPV - 1],
4879                              PosFlags(endPV - 1),
4880                              fromY, fromX, toY, toX, promoChar,
4881                              parseList[endPV - 1]);
4882     else
4883         parseList[endPV-1][0] = NULLCHAR;
4884   } while(valid);
4885   currentMove = endPV;
4886   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4887   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4888                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4889   DrawPosition(TRUE, boards[currentMove]);
4890 }
4891
4892 static int lastX, lastY;
4893
4894 Boolean
4895 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4896 {
4897         int startPV;
4898         char *p;
4899
4900         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4901         lastX = x; lastY = y;
4902         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4903         startPV = index;
4904         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4905         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4906         index = startPV;
4907         do{ while(buf[index] && buf[index] != '\n') index++;
4908         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4909         buf[index] = 0;
4910         ParsePV(buf+startPV, FALSE);
4911         *start = startPV; *end = index-1;
4912         return TRUE;
4913 }
4914
4915 Boolean
4916 LoadPV(int x, int y)
4917 { // called on right mouse click to load PV
4918   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4919   lastX = x; lastY = y;
4920   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4921   return TRUE;
4922 }
4923
4924 void
4925 UnLoadPV()
4926 {
4927   if(endPV < 0) return;
4928   endPV = -1;
4929   currentMove = forwardMostMove;
4930   ClearPremoveHighlights();
4931   DrawPosition(TRUE, boards[currentMove]);
4932 }
4933
4934 void
4935 MovePV(int x, int y, int h)
4936 { // step through PV based on mouse coordinates (called on mouse move)
4937   int margin = h>>3, step = 0;
4938
4939   if(endPV < 0) return;
4940   // we must somehow check if right button is still down (might be released off board!)
4941   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4942   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4943   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4944   if(!step) return;
4945   lastX = x; lastY = y;
4946   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4947   currentMove += step;
4948   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4949   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4950                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4951   DrawPosition(FALSE, boards[currentMove]);
4952 }
4953
4954
4955 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4956 // All positions will have equal probability, but the current method will not provide a unique
4957 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4958 #define DARK 1
4959 #define LITE 2
4960 #define ANY 3
4961
4962 int squaresLeft[4];
4963 int piecesLeft[(int)BlackPawn];
4964 int seed, nrOfShuffles;
4965
4966 void GetPositionNumber()
4967 {       // sets global variable seed
4968         int i;
4969
4970         seed = appData.defaultFrcPosition;
4971         if(seed < 0) { // randomize based on time for negative FRC position numbers
4972                 for(i=0; i<50; i++) seed += random();
4973                 seed = random() ^ random() >> 8 ^ random() << 8;
4974                 if(seed<0) seed = -seed;
4975         }
4976 }
4977
4978 int put(Board board, int pieceType, int rank, int n, int shade)
4979 // put the piece on the (n-1)-th empty squares of the given shade
4980 {
4981         int i;
4982
4983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4984                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4985                         board[rank][i] = (ChessSquare) pieceType;
4986                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4987                         squaresLeft[ANY]--;
4988                         piecesLeft[pieceType]--; 
4989                         return i;
4990                 }
4991         }
4992         return -1;
4993 }
4994
4995
4996 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4997 // calculate where the next piece goes, (any empty square), and put it there
4998 {
4999         int i;
5000
5001         i = seed % squaresLeft[shade];
5002         nrOfShuffles *= squaresLeft[shade];
5003         seed /= squaresLeft[shade];
5004         put(board, pieceType, rank, i, shade);
5005 }
5006
5007 void AddTwoPieces(Board board, int pieceType, int rank)
5008 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5009 {
5010         int i, n=squaresLeft[ANY], j=n-1, k;
5011
5012         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5013         i = seed % k;  // pick one
5014         nrOfShuffles *= k;
5015         seed /= k;
5016         while(i >= j) i -= j--;
5017         j = n - 1 - j; i += j;
5018         put(board, pieceType, rank, j, ANY);
5019         put(board, pieceType, rank, i, ANY);
5020 }
5021
5022 void SetUpShuffle(Board board, int number)
5023 {
5024         int i, p, first=1;
5025
5026         GetPositionNumber(); nrOfShuffles = 1;
5027
5028         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5029         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5030         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5031
5032         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5033
5034         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5035             p = (int) board[0][i];
5036             if(p < (int) BlackPawn) piecesLeft[p] ++;
5037             board[0][i] = EmptySquare;
5038         }
5039
5040         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5041             // shuffles restricted to allow normal castling put KRR first
5042             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5043                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5044             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5045                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5046             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5047                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5048             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5049                 put(board, WhiteRook, 0, 0, ANY);
5050             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5051         }
5052
5053         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5054             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5055             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5056                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5057                 while(piecesLeft[p] >= 2) {
5058                     AddOnePiece(board, p, 0, LITE);
5059                     AddOnePiece(board, p, 0, DARK);
5060                 }
5061                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5062             }
5063
5064         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5065             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5066             // but we leave King and Rooks for last, to possibly obey FRC restriction
5067             if(p == (int)WhiteRook) continue;
5068             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5069             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5070         }
5071
5072         // now everything is placed, except perhaps King (Unicorn) and Rooks
5073
5074         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5075             // Last King gets castling rights
5076             while(piecesLeft[(int)WhiteUnicorn]) {
5077                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5078                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5079             }
5080
5081             while(piecesLeft[(int)WhiteKing]) {
5082                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5083                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5084             }
5085
5086
5087         } else {
5088             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5089             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5090         }
5091
5092         // Only Rooks can be left; simply place them all
5093         while(piecesLeft[(int)WhiteRook]) {
5094                 i = put(board, WhiteRook, 0, 0, ANY);
5095                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5096                         if(first) {
5097                                 first=0;
5098                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5099                         }
5100                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5101                 }
5102         }
5103         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5104             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5105         }
5106
5107         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5108 }
5109
5110 int SetCharTable( char *table, const char * map )
5111 /* [HGM] moved here from winboard.c because of its general usefulness */
5112 /*       Basically a safe strcpy that uses the last character as King */
5113 {
5114     int result = FALSE; int NrPieces;
5115
5116     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5117                     && NrPieces >= 12 && !(NrPieces&1)) {
5118         int i; /* [HGM] Accept even length from 12 to 34 */
5119
5120         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5121         for( i=0; i<NrPieces/2-1; i++ ) {
5122             table[i] = map[i];
5123             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5124         }
5125         table[(int) WhiteKing]  = map[NrPieces/2-1];
5126         table[(int) BlackKing]  = map[NrPieces-1];
5127
5128         result = TRUE;
5129     }
5130
5131     return result;
5132 }
5133
5134 void Prelude(Board board)
5135 {       // [HGM] superchess: random selection of exo-pieces
5136         int i, j, k; ChessSquare p; 
5137         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5138
5139         GetPositionNumber(); // use FRC position number
5140
5141         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5142             SetCharTable(pieceToChar, appData.pieceToCharTable);
5143             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5144                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5145         }
5146
5147         j = seed%4;                 seed /= 4; 
5148         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5149         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5150         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5151         j = seed%3 + (seed%3 >= j); seed /= 3; 
5152         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5153         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5154         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5155         j = seed%3;                 seed /= 3; 
5156         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5157         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5158         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5159         j = seed%2 + (seed%2 >= j); seed /= 2; 
5160         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5161         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5162         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5163         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5164         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5165         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5166         put(board, exoPieces[0],    0, 0, ANY);
5167         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5168 }
5169
5170 void
5171 InitPosition(redraw)
5172      int redraw;
5173 {
5174     ChessSquare (* pieces)[BOARD_FILES];
5175     int i, j, pawnRow, overrule,
5176     oldx = gameInfo.boardWidth,
5177     oldy = gameInfo.boardHeight,
5178     oldh = gameInfo.holdingsWidth,
5179     oldv = gameInfo.variant;
5180
5181     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5182
5183     /* [AS] Initialize pv info list [HGM] and game status */
5184     {
5185         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5186             pvInfoList[i].depth = 0;
5187             boards[i][EP_STATUS] = EP_NONE;
5188             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5189         }
5190
5191         initialRulePlies = 0; /* 50-move counter start */
5192
5193         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5194         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5195     }
5196
5197     
5198     /* [HGM] logic here is completely changed. In stead of full positions */
5199     /* the initialized data only consist of the two backranks. The switch */
5200     /* selects which one we will use, which is than copied to the Board   */
5201     /* initialPosition, which for the rest is initialized by Pawns and    */
5202     /* empty squares. This initial position is then copied to boards[0],  */
5203     /* possibly after shuffling, so that it remains available.            */
5204
5205     gameInfo.holdingsWidth = 0; /* default board sizes */
5206     gameInfo.boardWidth    = 8;
5207     gameInfo.boardHeight   = 8;
5208     gameInfo.holdingsSize  = 0;
5209     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5210     for(i=0; i<BOARD_FILES-2; i++)
5211       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5212     initialPosition[EP_STATUS] = EP_NONE;
5213     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5214
5215     switch (gameInfo.variant) {
5216     case VariantFischeRandom:
5217       shuffleOpenings = TRUE;
5218     default:
5219       pieces = FIDEArray;
5220       break;
5221     case VariantShatranj:
5222       pieces = ShatranjArray;
5223       nrCastlingRights = 0;
5224       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5225       break;
5226     case VariantMakruk:
5227       pieces = makrukArray;
5228       nrCastlingRights = 0;
5229       startedFromSetupPosition = TRUE;
5230       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5231       break;
5232     case VariantTwoKings:
5233       pieces = twoKingsArray;
5234       break;
5235     case VariantCapaRandom:
5236       shuffleOpenings = TRUE;
5237     case VariantCapablanca:
5238       pieces = CapablancaArray;
5239       gameInfo.boardWidth = 10;
5240       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5241       break;
5242     case VariantGothic:
5243       pieces = GothicArray;
5244       gameInfo.boardWidth = 10;
5245       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5246       break;
5247     case VariantJanus:
5248       pieces = JanusArray;
5249       gameInfo.boardWidth = 10;
5250       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5251       nrCastlingRights = 6;
5252         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5253         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5254         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5255         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5256         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5257         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5258       break;
5259     case VariantFalcon:
5260       pieces = FalconArray;
5261       gameInfo.boardWidth = 10;
5262       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5263       break;
5264     case VariantXiangqi:
5265       pieces = XiangqiArray;
5266       gameInfo.boardWidth  = 9;
5267       gameInfo.boardHeight = 10;
5268       nrCastlingRights = 0;
5269       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5270       break;
5271     case VariantShogi:
5272       pieces = ShogiArray;
5273       gameInfo.boardWidth  = 9;
5274       gameInfo.boardHeight = 9;
5275       gameInfo.holdingsSize = 7;
5276       nrCastlingRights = 0;
5277       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5278       break;
5279     case VariantCourier:
5280       pieces = CourierArray;
5281       gameInfo.boardWidth  = 12;
5282       nrCastlingRights = 0;
5283       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5284       break;
5285     case VariantKnightmate:
5286       pieces = KnightmateArray;
5287       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5288       break;
5289     case VariantFairy:
5290       pieces = fairyArray;
5291       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5292       break;
5293     case VariantGreat:
5294       pieces = GreatArray;
5295       gameInfo.boardWidth = 10;
5296       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5297       gameInfo.holdingsSize = 8;
5298       break;
5299     case VariantSuper:
5300       pieces = FIDEArray;
5301       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5302       gameInfo.holdingsSize = 8;
5303       startedFromSetupPosition = TRUE;
5304       break;
5305     case VariantCrazyhouse:
5306     case VariantBughouse:
5307       pieces = FIDEArray;
5308       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5309       gameInfo.holdingsSize = 5;
5310       break;
5311     case VariantWildCastle:
5312       pieces = FIDEArray;
5313       /* !!?shuffle with kings guaranteed to be on d or e file */
5314       shuffleOpenings = 1;
5315       break;
5316     case VariantNoCastle:
5317       pieces = FIDEArray;
5318       nrCastlingRights = 0;
5319       /* !!?unconstrained back-rank shuffle */
5320       shuffleOpenings = 1;
5321       break;
5322     }
5323
5324     overrule = 0;
5325     if(appData.NrFiles >= 0) {
5326         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5327         gameInfo.boardWidth = appData.NrFiles;
5328     }
5329     if(appData.NrRanks >= 0) {
5330         gameInfo.boardHeight = appData.NrRanks;
5331     }
5332     if(appData.holdingsSize >= 0) {
5333         i = appData.holdingsSize;
5334         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5335         gameInfo.holdingsSize = i;
5336     }
5337     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5338     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5339         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5340
5341     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5342     if(pawnRow < 1) pawnRow = 1;
5343     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5344
5345     /* User pieceToChar list overrules defaults */
5346     if(appData.pieceToCharTable != NULL)
5347         SetCharTable(pieceToChar, appData.pieceToCharTable);
5348
5349     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5350
5351         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5352             s = (ChessSquare) 0; /* account holding counts in guard band */
5353         for( i=0; i<BOARD_HEIGHT; i++ )
5354             initialPosition[i][j] = s;
5355
5356         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5357         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5358         initialPosition[pawnRow][j] = WhitePawn;
5359         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5360         if(gameInfo.variant == VariantXiangqi) {
5361             if(j&1) {
5362                 initialPosition[pawnRow][j] = 
5363                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5364                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5365                    initialPosition[2][j] = WhiteCannon;
5366                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5367                 }
5368             }
5369         }
5370         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5371     }
5372     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5373
5374             j=BOARD_LEFT+1;
5375             initialPosition[1][j] = WhiteBishop;
5376             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5377             j=BOARD_RGHT-2;
5378             initialPosition[1][j] = WhiteRook;
5379             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5380     }
5381
5382     if( nrCastlingRights == -1) {
5383         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5384         /*       This sets default castling rights from none to normal corners   */
5385         /* Variants with other castling rights must set them themselves above    */
5386         nrCastlingRights = 6;
5387        
5388         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5389         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5390         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5391         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5392         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5393         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5394      }
5395
5396      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5397      if(gameInfo.variant == VariantGreat) { // promotion commoners
5398         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5399         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5400         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5401         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5402      }
5403   if (appData.debugMode) {
5404     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5405   }
5406     if(shuffleOpenings) {
5407         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5408         startedFromSetupPosition = TRUE;
5409     }
5410     if(startedFromPositionFile) {
5411       /* [HGM] loadPos: use PositionFile for every new game */
5412       CopyBoard(initialPosition, filePosition);
5413       for(i=0; i<nrCastlingRights; i++)
5414           initialRights[i] = filePosition[CASTLING][i];
5415       startedFromSetupPosition = TRUE;
5416     }
5417
5418     CopyBoard(boards[0], initialPosition);
5419
5420     if(oldx != gameInfo.boardWidth ||
5421        oldy != gameInfo.boardHeight ||
5422        oldh != gameInfo.holdingsWidth
5423 #ifdef GOTHIC
5424        || oldv == VariantGothic ||        // For licensing popups
5425        gameInfo.variant == VariantGothic
5426 #endif
5427 #ifdef FALCON
5428        || oldv == VariantFalcon ||
5429        gameInfo.variant == VariantFalcon
5430 #endif
5431                                          )
5432             InitDrawingSizes(-2 ,0);
5433
5434     if (redraw)
5435       DrawPosition(TRUE, boards[currentMove]);
5436 }
5437
5438 void
5439 SendBoard(cps, moveNum)
5440      ChessProgramState *cps;
5441      int moveNum;
5442 {
5443     char message[MSG_SIZ];
5444     
5445     if (cps->useSetboard) {
5446       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5447       sprintf(message, "setboard %s\n", fen);
5448       SendToProgram(message, cps);
5449       free(fen);
5450
5451     } else {
5452       ChessSquare *bp;
5453       int i, j;
5454       /* Kludge to set black to move, avoiding the troublesome and now
5455        * deprecated "black" command.
5456        */
5457       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5458
5459       SendToProgram("edit\n", cps);
5460       SendToProgram("#\n", cps);
5461       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5462         bp = &boards[moveNum][i][BOARD_LEFT];
5463         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5464           if ((int) *bp < (int) BlackPawn) {
5465             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5466                     AAA + j, ONE + i);
5467             if(message[0] == '+' || message[0] == '~') {
5468                 sprintf(message, "%c%c%c+\n",
5469                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5470                         AAA + j, ONE + i);
5471             }
5472             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5473                 message[1] = BOARD_RGHT   - 1 - j + '1';
5474                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5475             }
5476             SendToProgram(message, cps);
5477           }
5478         }
5479       }
5480     
5481       SendToProgram("c\n", cps);
5482       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5483         bp = &boards[moveNum][i][BOARD_LEFT];
5484         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5485           if (((int) *bp != (int) EmptySquare)
5486               && ((int) *bp >= (int) BlackPawn)) {
5487             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5488                     AAA + j, ONE + i);
5489             if(message[0] == '+' || message[0] == '~') {
5490                 sprintf(message, "%c%c%c+\n",
5491                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5492                         AAA + j, ONE + i);
5493             }
5494             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5495                 message[1] = BOARD_RGHT   - 1 - j + '1';
5496                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5497             }
5498             SendToProgram(message, cps);
5499           }
5500         }
5501       }
5502     
5503       SendToProgram(".\n", cps);
5504     }
5505     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5506 }
5507
5508 static int autoQueen; // [HGM] oneclick
5509
5510 int
5511 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5512 {
5513     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5514     /* [HGM] add Shogi promotions */
5515     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5516     ChessSquare piece;
5517     ChessMove moveType;
5518     Boolean premove;
5519
5520     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5521     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5522
5523     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5524       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5525         return FALSE;
5526
5527     piece = boards[currentMove][fromY][fromX];
5528     if(gameInfo.variant == VariantShogi) {
5529         promotionZoneSize = 3;
5530         highestPromotingPiece = (int)WhiteFerz;
5531     } else if(gameInfo.variant == VariantMakruk) {
5532         promotionZoneSize = 3;
5533     }
5534
5535     // next weed out all moves that do not touch the promotion zone at all
5536     if((int)piece >= BlackPawn) {
5537         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5538              return FALSE;
5539         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5540     } else {
5541         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5542            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5543     }
5544
5545     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5546
5547     // weed out mandatory Shogi promotions
5548     if(gameInfo.variant == VariantShogi) {
5549         if(piece >= BlackPawn) {
5550             if(toY == 0 && piece == BlackPawn ||
5551                toY == 0 && piece == BlackQueen ||
5552                toY <= 1 && piece == BlackKnight) {
5553                 *promoChoice = '+';
5554                 return FALSE;
5555             }
5556         } else {
5557             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5558                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5559                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5560                 *promoChoice = '+';
5561                 return FALSE;
5562             }
5563         }
5564     }
5565
5566     // weed out obviously illegal Pawn moves
5567     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5568         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5569         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5570         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5571         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5572         // note we are not allowed to test for valid (non-)capture, due to premove
5573     }
5574
5575     // we either have a choice what to promote to, or (in Shogi) whether to promote
5576     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5577         *promoChoice = PieceToChar(BlackFerz);  // no choice
5578         return FALSE;
5579     }
5580     if(autoQueen) { // predetermined
5581         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5582              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5583         else *promoChoice = PieceToChar(BlackQueen);
5584         return FALSE;
5585     }
5586
5587     // suppress promotion popup on illegal moves that are not premoves
5588     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5589               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5590     if(appData.testLegality && !premove) {
5591         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5592                         fromY, fromX, toY, toX, NULLCHAR);
5593         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5594            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5595             return FALSE;
5596     }
5597
5598     return TRUE;
5599 }
5600
5601 int
5602 InPalace(row, column)
5603      int row, column;
5604 {   /* [HGM] for Xiangqi */
5605     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5606          column < (BOARD_WIDTH + 4)/2 &&
5607          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5608     return FALSE;
5609 }
5610
5611 int
5612 PieceForSquare (x, y)
5613      int x;
5614      int y;
5615 {
5616   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5617      return -1;
5618   else
5619      return boards[currentMove][y][x];
5620 }
5621
5622 int
5623 OKToStartUserMove(x, y)
5624      int x, y;
5625 {
5626     ChessSquare from_piece;
5627     int white_piece;
5628
5629     if (matchMode) return FALSE;
5630     if (gameMode == EditPosition) return TRUE;
5631
5632     if (x >= 0 && y >= 0)
5633       from_piece = boards[currentMove][y][x];
5634     else
5635       from_piece = EmptySquare;
5636
5637     if (from_piece == EmptySquare) return FALSE;
5638
5639     white_piece = (int)from_piece >= (int)WhitePawn &&
5640       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5641
5642     switch (gameMode) {
5643       case PlayFromGameFile:
5644       case AnalyzeFile:
5645       case TwoMachinesPlay:
5646       case EndOfGame:
5647         return FALSE;
5648
5649       case IcsObserving:
5650       case IcsIdle:
5651         return FALSE;
5652
5653       case MachinePlaysWhite:
5654       case IcsPlayingBlack:
5655         if (appData.zippyPlay) return FALSE;
5656         if (white_piece) {
5657             DisplayMoveError(_("You are playing Black"));
5658             return FALSE;
5659         }
5660         break;
5661
5662       case MachinePlaysBlack:
5663       case IcsPlayingWhite:
5664         if (appData.zippyPlay) return FALSE;
5665         if (!white_piece) {
5666             DisplayMoveError(_("You are playing White"));
5667             return FALSE;
5668         }
5669         break;
5670
5671       case EditGame:
5672         if (!white_piece && WhiteOnMove(currentMove)) {
5673             DisplayMoveError(_("It is White's turn"));
5674             return FALSE;
5675         }           
5676         if (white_piece && !WhiteOnMove(currentMove)) {
5677             DisplayMoveError(_("It is Black's turn"));
5678             return FALSE;
5679         }           
5680         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5681             /* Editing correspondence game history */
5682             /* Could disallow this or prompt for confirmation */
5683             cmailOldMove = -1;
5684         }
5685         break;
5686
5687       case BeginningOfGame:
5688         if (appData.icsActive) return FALSE;
5689         if (!appData.noChessProgram) {
5690             if (!white_piece) {
5691                 DisplayMoveError(_("You are playing White"));
5692                 return FALSE;
5693             }
5694         }
5695         break;
5696         
5697       case Training:
5698         if (!white_piece && WhiteOnMove(currentMove)) {
5699             DisplayMoveError(_("It is White's turn"));
5700             return FALSE;
5701         }           
5702         if (white_piece && !WhiteOnMove(currentMove)) {
5703             DisplayMoveError(_("It is Black's turn"));
5704             return FALSE;
5705         }           
5706         break;
5707
5708       default:
5709       case IcsExamining:
5710         break;
5711     }
5712     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5713         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5714         && gameMode != AnalyzeFile && gameMode != Training) {
5715         DisplayMoveError(_("Displayed position is not current"));
5716         return FALSE;
5717     }
5718     return TRUE;
5719 }
5720
5721 Boolean
5722 OnlyMove(int *x, int *y, Boolean captures) {
5723     DisambiguateClosure cl;
5724     if (appData.zippyPlay) return FALSE;
5725     switch(gameMode) {
5726       case MachinePlaysBlack:
5727       case IcsPlayingWhite:
5728       case BeginningOfGame:
5729         if(!WhiteOnMove(currentMove)) return FALSE;
5730         break;
5731       case MachinePlaysWhite:
5732       case IcsPlayingBlack:
5733         if(WhiteOnMove(currentMove)) return FALSE;
5734         break;
5735       default:
5736         return FALSE;
5737     }
5738     cl.pieceIn = EmptySquare; 
5739     cl.rfIn = *y;
5740     cl.ffIn = *x;
5741     cl.rtIn = -1;
5742     cl.ftIn = -1;
5743     cl.promoCharIn = NULLCHAR;
5744     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5745     if( cl.kind == NormalMove ||
5746         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5747         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5748         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5749         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5750       fromX = cl.ff;
5751       fromY = cl.rf;
5752       *x = cl.ft;
5753       *y = cl.rt;
5754       return TRUE;
5755     }
5756     if(cl.kind != ImpossibleMove) return FALSE;
5757     cl.pieceIn = EmptySquare;
5758     cl.rfIn = -1;
5759     cl.ffIn = -1;
5760     cl.rtIn = *y;
5761     cl.ftIn = *x;
5762     cl.promoCharIn = NULLCHAR;
5763     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5764     if( cl.kind == NormalMove ||
5765         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5766         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5767         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5768         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5769       fromX = cl.ff;
5770       fromY = cl.rf;
5771       *x = cl.ft;
5772       *y = cl.rt;
5773       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5774       return TRUE;
5775     }
5776     return FALSE;
5777 }
5778
5779 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5780 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5781 int lastLoadGameUseList = FALSE;
5782 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5783 ChessMove lastLoadGameStart = (ChessMove) 0;
5784
5785 ChessMove
5786 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5787      int fromX, fromY, toX, toY;
5788      int promoChar;
5789      Boolean captureOwn;
5790 {
5791     ChessMove moveType;
5792     ChessSquare pdown, pup;
5793
5794     /* Check if the user is playing in turn.  This is complicated because we
5795        let the user "pick up" a piece before it is his turn.  So the piece he
5796        tried to pick up may have been captured by the time he puts it down!
5797        Therefore we use the color the user is supposed to be playing in this
5798        test, not the color of the piece that is currently on the starting
5799        square---except in EditGame mode, where the user is playing both
5800        sides; fortunately there the capture race can't happen.  (It can
5801        now happen in IcsExamining mode, but that's just too bad.  The user
5802        will get a somewhat confusing message in that case.)
5803        */
5804
5805     switch (gameMode) {
5806       case PlayFromGameFile:
5807       case AnalyzeFile:
5808       case TwoMachinesPlay:
5809       case EndOfGame:
5810       case IcsObserving:
5811       case IcsIdle:
5812         /* We switched into a game mode where moves are not accepted,
5813            perhaps while the mouse button was down. */
5814         return ImpossibleMove;
5815
5816       case MachinePlaysWhite:
5817         /* User is moving for Black */
5818         if (WhiteOnMove(currentMove)) {
5819             DisplayMoveError(_("It is White's turn"));
5820             return ImpossibleMove;
5821         }
5822         break;
5823
5824       case MachinePlaysBlack:
5825         /* User is moving for White */
5826         if (!WhiteOnMove(currentMove)) {
5827             DisplayMoveError(_("It is Black's turn"));
5828             return ImpossibleMove;
5829         }
5830         break;
5831
5832       case EditGame:
5833       case IcsExamining:
5834       case BeginningOfGame:
5835       case AnalyzeMode:
5836       case Training:
5837         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5838             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5839             /* User is moving for Black */
5840             if (WhiteOnMove(currentMove)) {
5841                 DisplayMoveError(_("It is White's turn"));
5842                 return ImpossibleMove;
5843             }
5844         } else {
5845             /* User is moving for White */
5846             if (!WhiteOnMove(currentMove)) {
5847                 DisplayMoveError(_("It is Black's turn"));
5848                 return ImpossibleMove;
5849             }
5850         }
5851         break;
5852
5853       case IcsPlayingBlack:
5854         /* User is moving for Black */
5855         if (WhiteOnMove(currentMove)) {
5856             if (!appData.premove) {
5857                 DisplayMoveError(_("It is White's turn"));
5858             } else if (toX >= 0 && toY >= 0) {
5859                 premoveToX = toX;
5860                 premoveToY = toY;
5861                 premoveFromX = fromX;
5862                 premoveFromY = fromY;
5863                 premovePromoChar = promoChar;
5864                 gotPremove = 1;
5865                 if (appData.debugMode) 
5866                     fprintf(debugFP, "Got premove: fromX %d,"
5867                             "fromY %d, toX %d, toY %d\n",
5868                             fromX, fromY, toX, toY);
5869             }
5870             return ImpossibleMove;
5871         }
5872         break;
5873
5874       case IcsPlayingWhite:
5875         /* User is moving for White */
5876         if (!WhiteOnMove(currentMove)) {
5877             if (!appData.premove) {
5878                 DisplayMoveError(_("It is Black's turn"));
5879             } else if (toX >= 0 && toY >= 0) {
5880                 premoveToX = toX;
5881                 premoveToY = toY;
5882                 premoveFromX = fromX;
5883                 premoveFromY = fromY;
5884                 premovePromoChar = promoChar;
5885                 gotPremove = 1;
5886                 if (appData.debugMode) 
5887                     fprintf(debugFP, "Got premove: fromX %d,"
5888                             "fromY %d, toX %d, toY %d\n",
5889                             fromX, fromY, toX, toY);
5890             }
5891             return ImpossibleMove;
5892         }
5893         break;
5894
5895       default:
5896         break;
5897
5898       case EditPosition:
5899         /* EditPosition, empty square, or different color piece;
5900            click-click move is possible */
5901         if (toX == -2 || toY == -2) {
5902             boards[0][fromY][fromX] = EmptySquare;
5903             return AmbiguousMove;
5904         } else if (toX >= 0 && toY >= 0) {
5905             boards[0][toY][toX] = boards[0][fromY][fromX];
5906             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5907                 if(boards[0][fromY][0] != EmptySquare) {
5908                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5909                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5910                 }
5911             } else
5912             if(fromX == BOARD_RGHT+1) {
5913                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5914                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5915                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5916                 }
5917             } else
5918             boards[0][fromY][fromX] = EmptySquare;
5919             return AmbiguousMove;
5920         }
5921         return ImpossibleMove;
5922     }
5923
5924     if(toX < 0 || toY < 0) return ImpossibleMove;
5925     pdown = boards[currentMove][fromY][fromX];
5926     pup = boards[currentMove][toY][toX];
5927
5928     /* [HGM] If move started in holdings, it means a drop */
5929     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5930          if( pup != EmptySquare ) return ImpossibleMove;
5931          if(appData.testLegality) {
5932              /* it would be more logical if LegalityTest() also figured out
5933               * which drops are legal. For now we forbid pawns on back rank.
5934               * Shogi is on its own here...
5935               */
5936              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5937                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5938                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5939          }
5940          return WhiteDrop; /* Not needed to specify white or black yet */
5941     }
5942
5943     /* [HGM] always test for legality, to get promotion info */
5944     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5945                                          fromY, fromX, toY, toX, promoChar);
5946     /* [HGM] but possibly ignore an IllegalMove result */
5947     if (appData.testLegality) {
5948         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5949             DisplayMoveError(_("Illegal move"));
5950             return ImpossibleMove;
5951         }
5952     }
5953
5954     return moveType;
5955     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5956        function is made into one that returns an OK move type if FinishMove
5957        should be called. This to give the calling driver routine the
5958        opportunity to finish the userMove input with a promotion popup,
5959        without bothering the user with this for invalid or illegal moves */
5960
5961 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5962 }
5963
5964 /* Common tail of UserMoveEvent and DropMenuEvent */
5965 int
5966 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5967      ChessMove moveType;
5968      int fromX, fromY, toX, toY;
5969      /*char*/int promoChar;
5970 {
5971     char *bookHit = 0;
5972
5973     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5974         // [HGM] superchess: suppress promotions to non-available piece
5975         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5976         if(WhiteOnMove(currentMove)) {
5977             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5978         } else {
5979             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5980         }
5981     }
5982
5983     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5984        move type in caller when we know the move is a legal promotion */
5985     if(moveType == NormalMove && promoChar)
5986         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5987
5988     /* [HGM] convert drag-and-drop piece drops to standard form */
5989     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5990          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5991            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5992                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5993            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5994            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5995            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5996            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5997          fromY = DROP_RANK;
5998     }
5999
6000     /* [HGM] <popupFix> The following if has been moved here from
6001        UserMoveEvent(). Because it seemed to belong here (why not allow
6002        piece drops in training games?), and because it can only be
6003        performed after it is known to what we promote. */
6004     if (gameMode == Training) {
6005       /* compare the move played on the board to the next move in the
6006        * game. If they match, display the move and the opponent's response. 
6007        * If they don't match, display an error message.
6008        */
6009       int saveAnimate;
6010       Board testBoard;
6011       CopyBoard(testBoard, boards[currentMove]);
6012       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6013
6014       if (CompareBoards(testBoard, boards[currentMove+1])) {
6015         ForwardInner(currentMove+1);
6016
6017         /* Autoplay the opponent's response.
6018          * if appData.animate was TRUE when Training mode was entered,
6019          * the response will be animated.
6020          */
6021         saveAnimate = appData.animate;
6022         appData.animate = animateTraining;
6023         ForwardInner(currentMove+1);
6024         appData.animate = saveAnimate;
6025
6026         /* check for the end of the game */
6027         if (currentMove >= forwardMostMove) {
6028           gameMode = PlayFromGameFile;
6029           ModeHighlight();
6030           SetTrainingModeOff();
6031           DisplayInformation(_("End of game"));
6032         }
6033       } else {
6034         DisplayError(_("Incorrect move"), 0);
6035       }
6036       return 1;
6037     }
6038
6039   /* Ok, now we know that the move is good, so we can kill
6040      the previous line in Analysis Mode */
6041   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6042                                 && currentMove < forwardMostMove) {
6043     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6044   }
6045
6046   /* If we need the chess program but it's dead, restart it */
6047   ResurrectChessProgram();
6048
6049   /* A user move restarts a paused game*/
6050   if (pausing)
6051     PauseEvent();
6052
6053   thinkOutput[0] = NULLCHAR;
6054
6055   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6056
6057   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6058
6059   if (gameMode == BeginningOfGame) {
6060     if (appData.noChessProgram) {
6061       gameMode = EditGame;
6062       SetGameInfo();
6063     } else {
6064       char buf[MSG_SIZ];
6065       gameMode = MachinePlaysBlack;
6066       StartClocks();
6067       SetGameInfo();
6068       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6069       DisplayTitle(buf);
6070       if (first.sendName) {
6071         sprintf(buf, "name %s\n", gameInfo.white);
6072         SendToProgram(buf, &first);
6073       }
6074       StartClocks();
6075     }
6076     ModeHighlight();
6077   }
6078
6079   /* Relay move to ICS or chess engine */
6080   if (appData.icsActive) {
6081     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6082         gameMode == IcsExamining) {
6083       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6084         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6085         SendToICS("draw ");
6086         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6087       }
6088       // also send plain move, in case ICS does not understand atomic claims
6089       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6090       ics_user_moved = 1;
6091     }
6092   } else {
6093     if (first.sendTime && (gameMode == BeginningOfGame ||
6094                            gameMode == MachinePlaysWhite ||
6095                            gameMode == MachinePlaysBlack)) {
6096       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6097     }
6098     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6099          // [HGM] book: if program might be playing, let it use book
6100         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6101         first.maybeThinking = TRUE;
6102     } else SendMoveToProgram(forwardMostMove-1, &first);
6103     if (currentMove == cmailOldMove + 1) {
6104       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6105     }
6106   }
6107
6108   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6109
6110   switch (gameMode) {
6111   case EditGame:
6112     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6113     case MT_NONE:
6114     case MT_CHECK:
6115       break;
6116     case MT_CHECKMATE:
6117     case MT_STAINMATE:
6118       if (WhiteOnMove(currentMove)) {
6119         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6120       } else {
6121         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6122       }
6123       break;
6124     case MT_STALEMATE:
6125       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6126       break;
6127     }
6128     break;
6129     
6130   case MachinePlaysBlack:
6131   case MachinePlaysWhite:
6132     /* disable certain menu options while machine is thinking */
6133     SetMachineThinkingEnables();
6134     break;
6135
6136   default:
6137     break;
6138   }
6139
6140   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6141         
6142   if(bookHit) { // [HGM] book: simulate book reply
6143         static char bookMove[MSG_SIZ]; // a bit generous?
6144
6145         programStats.nodes = programStats.depth = programStats.time = 
6146         programStats.score = programStats.got_only_move = 0;
6147         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6148
6149         strcpy(bookMove, "move ");
6150         strcat(bookMove, bookHit);
6151         HandleMachineMove(bookMove, &first);
6152   }
6153   return 1;
6154 }
6155
6156 void
6157 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6158      int fromX, fromY, toX, toY;
6159      int promoChar;
6160 {
6161     /* [HGM] This routine was added to allow calling of its two logical
6162        parts from other modules in the old way. Before, UserMoveEvent()
6163        automatically called FinishMove() if the move was OK, and returned
6164        otherwise. I separated the two, in order to make it possible to
6165        slip a promotion popup in between. But that it always needs two
6166        calls, to the first part, (now called UserMoveTest() ), and to
6167        FinishMove if the first part succeeded. Calls that do not need
6168        to do anything in between, can call this routine the old way. 
6169     */
6170     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6171 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6172     if(moveType == AmbiguousMove)
6173         DrawPosition(FALSE, boards[currentMove]);
6174     else if(moveType != ImpossibleMove && moveType != Comment)
6175         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6176 }
6177
6178 void
6179 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6180      Board board;
6181      int flags;
6182      ChessMove kind;
6183      int rf, ff, rt, ft;
6184      VOIDSTAR closure;
6185 {
6186     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6187     Markers *m = (Markers *) closure;
6188     if(rf == fromY && ff == fromX)
6189         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6190                          || kind == WhiteCapturesEnPassant
6191                          || kind == BlackCapturesEnPassant);
6192     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6193 }
6194
6195 void
6196 MarkTargetSquares(int clear)
6197 {
6198   int x, y;
6199   if(!appData.markers || !appData.highlightDragging || 
6200      !appData.testLegality || gameMode == EditPosition) return;
6201   if(clear) {
6202     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6203   } else {
6204     int capt = 0;
6205     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6206     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6207       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6208       if(capt)
6209       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6210     }
6211   }
6212   DrawPosition(TRUE, NULL);
6213 }
6214
6215 void LeftClick(ClickType clickType, int xPix, int yPix)
6216 {
6217     int x, y;
6218     Boolean saveAnimate;
6219     static int second = 0, promotionChoice = 0;
6220     char promoChoice = NULLCHAR;
6221
6222     if(appData.seekGraph && appData.icsActive && loggedOn &&
6223         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6224         SeekGraphClick(clickType, xPix, yPix, 0);
6225         return;
6226     }
6227
6228     if (clickType == Press) ErrorPopDown();
6229     MarkTargetSquares(1);
6230
6231     x = EventToSquare(xPix, BOARD_WIDTH);
6232     y = EventToSquare(yPix, BOARD_HEIGHT);
6233     if (!flipView && y >= 0) {
6234         y = BOARD_HEIGHT - 1 - y;
6235     }
6236     if (flipView && x >= 0) {
6237         x = BOARD_WIDTH - 1 - x;
6238     }
6239
6240     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6241         if(clickType == Release) return; // ignore upclick of click-click destination
6242         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6243         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6244         if(gameInfo.holdingsWidth && 
6245                 (WhiteOnMove(currentMove) 
6246                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6247                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6248             // click in right holdings, for determining promotion piece
6249             ChessSquare p = boards[currentMove][y][x];
6250             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6251             if(p != EmptySquare) {
6252                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6253                 fromX = fromY = -1;
6254                 return;
6255             }
6256         }
6257         DrawPosition(FALSE, boards[currentMove]);
6258         return;
6259     }
6260
6261     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6262     if(clickType == Press
6263             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6264               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6265               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6266         return;
6267
6268     autoQueen = appData.alwaysPromoteToQueen;
6269
6270     if (fromX == -1) {
6271       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6272         if (clickType == Press) {
6273             /* First square */
6274             if (OKToStartUserMove(x, y)) {
6275                 fromX = x;
6276                 fromY = y;
6277                 second = 0;
6278                 MarkTargetSquares(0);
6279                 DragPieceBegin(xPix, yPix);
6280                 if (appData.highlightDragging) {
6281                     SetHighlights(x, y, -1, -1);
6282                 }
6283             }
6284         }
6285         return;
6286       }
6287     }
6288
6289     /* fromX != -1 */
6290     if (clickType == Press && gameMode != EditPosition) {
6291         ChessSquare fromP;
6292         ChessSquare toP;
6293         int frc;
6294
6295         // ignore off-board to clicks
6296         if(y < 0 || x < 0) return;
6297
6298         /* Check if clicking again on the same color piece */
6299         fromP = boards[currentMove][fromY][fromX];
6300         toP = boards[currentMove][y][x];
6301         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6302         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6303              WhitePawn <= toP && toP <= WhiteKing &&
6304              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6305              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6306             (BlackPawn <= fromP && fromP <= BlackKing && 
6307              BlackPawn <= toP && toP <= BlackKing &&
6308              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6309              !(fromP == BlackKing && toP == BlackRook && frc))) {
6310             /* Clicked again on same color piece -- changed his mind */
6311             second = (x == fromX && y == fromY);
6312            if(!second || !OnlyMove(&x, &y, TRUE)) {
6313             if (appData.highlightDragging) {
6314                 SetHighlights(x, y, -1, -1);
6315             } else {
6316                 ClearHighlights();
6317             }
6318             if (OKToStartUserMove(x, y)) {
6319                 fromX = x;
6320                 fromY = y;
6321                 MarkTargetSquares(0);
6322                 DragPieceBegin(xPix, yPix);
6323             }
6324             return;
6325            }
6326         }
6327         // ignore clicks on holdings
6328         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6329     }
6330
6331     if (clickType == Release && x == fromX && y == fromY) {
6332         DragPieceEnd(xPix, yPix);
6333         if (appData.animateDragging) {
6334             /* Undo animation damage if any */
6335             DrawPosition(FALSE, NULL);
6336         }
6337         if (second) {
6338             /* Second up/down in same square; just abort move */
6339             second = 0;
6340             fromX = fromY = -1;
6341             ClearHighlights();
6342             gotPremove = 0;
6343             ClearPremoveHighlights();
6344         } else {
6345             /* First upclick in same square; start click-click mode */
6346             SetHighlights(x, y, -1, -1);
6347         }
6348         return;
6349     }
6350
6351     /* we now have a different from- and (possibly off-board) to-square */
6352     /* Completed move */
6353     toX = x;
6354     toY = y;
6355     saveAnimate = appData.animate;
6356     if (clickType == Press) {
6357         /* Finish clickclick move */
6358         if (appData.animate || appData.highlightLastMove) {
6359             SetHighlights(fromX, fromY, toX, toY);
6360         } else {
6361             ClearHighlights();
6362         }
6363     } else {
6364         /* Finish drag move */
6365         if (appData.highlightLastMove) {
6366             SetHighlights(fromX, fromY, toX, toY);
6367         } else {
6368             ClearHighlights();
6369         }
6370         DragPieceEnd(xPix, yPix);
6371         /* Don't animate move and drag both */
6372         appData.animate = FALSE;
6373     }
6374
6375     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6376     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6377         ChessSquare piece = boards[currentMove][fromY][fromX];
6378         if(gameMode == EditPosition && piece != EmptySquare &&
6379            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6380             int n;
6381              
6382             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6383                 n = PieceToNumber(piece - (int)BlackPawn);
6384                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6385                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6386                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6387             } else
6388             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6389                 n = PieceToNumber(piece);
6390                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6391                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6392                 boards[currentMove][n][BOARD_WIDTH-2]++;
6393             }
6394             boards[currentMove][fromY][fromX] = EmptySquare;
6395         }
6396         ClearHighlights();
6397         fromX = fromY = -1;
6398         DrawPosition(TRUE, boards[currentMove]);
6399         return;
6400     }
6401
6402     // off-board moves should not be highlighted
6403     if(x < 0 || x < 0) ClearHighlights();
6404
6405     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6406         SetHighlights(fromX, fromY, toX, toY);
6407         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6408             // [HGM] super: promotion to captured piece selected from holdings
6409             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6410             promotionChoice = TRUE;
6411             // kludge follows to temporarily execute move on display, without promoting yet
6412             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6413             boards[currentMove][toY][toX] = p;
6414             DrawPosition(FALSE, boards[currentMove]);
6415             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6416             boards[currentMove][toY][toX] = q;
6417             DisplayMessage("Click in holdings to choose piece", "");
6418             return;
6419         }
6420         PromotionPopUp();
6421     } else {
6422         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6423         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6424         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6425         fromX = fromY = -1;
6426     }
6427     appData.animate = saveAnimate;
6428     if (appData.animate || appData.animateDragging) {
6429         /* Undo animation damage if needed */
6430         DrawPosition(FALSE, NULL);
6431     }
6432 }
6433
6434 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6435 {   // front-end-free part taken out of PieceMenuPopup
6436     int whichMenu; int xSqr, ySqr;
6437
6438     if(seekGraphUp) { // [HGM] seekgraph
6439         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6440         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6441         return -2;
6442     }
6443
6444     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6445          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6446         if(action == Press)   {
6447             originalFlip = flipView;
6448             flipView = !flipView; // temporarily flip board to see game from partners perspective
6449             DrawPosition(TRUE, partnerBoard);
6450             DisplayMessage(partnerStatus, "");
6451             partnerUp = TRUE;
6452         } else if(action == Release) {
6453             flipView = originalFlip;
6454             DrawPosition(TRUE, boards[currentMove]);
6455             partnerUp = FALSE;
6456         }
6457         return -2;
6458     }
6459
6460     xSqr = EventToSquare(x, BOARD_WIDTH);
6461     ySqr = EventToSquare(y, BOARD_HEIGHT);
6462     if (action == Release) UnLoadPV(); // [HGM] pv
6463     if (action != Press) return -2; // return code to be ignored
6464     switch (gameMode) {
6465       case IcsExamining:
6466         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6467       case EditPosition:
6468         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6469         if (xSqr < 0 || ySqr < 0) return -1;\r
6470         whichMenu = 0; // edit-position menu
6471         break;
6472       case IcsObserving:
6473         if(!appData.icsEngineAnalyze) return -1;
6474       case IcsPlayingWhite:
6475       case IcsPlayingBlack:
6476         if(!appData.zippyPlay) goto noZip;
6477       case AnalyzeMode:
6478       case AnalyzeFile:
6479       case MachinePlaysWhite:
6480       case MachinePlaysBlack:
6481       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6482         if (!appData.dropMenu) {
6483           LoadPV(x, y);
6484           return 2; // flag front-end to grab mouse events
6485         }
6486         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6487            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6488       case EditGame:
6489       noZip:
6490         if (xSqr < 0 || ySqr < 0) return -1;
6491         if (!appData.dropMenu || appData.testLegality &&
6492             gameInfo.variant != VariantBughouse &&
6493             gameInfo.variant != VariantCrazyhouse) return -1;
6494         whichMenu = 1; // drop menu
6495         break;
6496       default:
6497         return -1;
6498     }
6499
6500     if (((*fromX = xSqr) < 0) ||
6501         ((*fromY = ySqr) < 0)) {
6502         *fromX = *fromY = -1;
6503         return -1;
6504     }
6505     if (flipView)
6506       *fromX = BOARD_WIDTH - 1 - *fromX;
6507     else
6508       *fromY = BOARD_HEIGHT - 1 - *fromY;
6509
6510     return whichMenu;
6511 }
6512
6513 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6514 {
6515 //    char * hint = lastHint;
6516     FrontEndProgramStats stats;
6517
6518     stats.which = cps == &first ? 0 : 1;
6519     stats.depth = cpstats->depth;
6520     stats.nodes = cpstats->nodes;
6521     stats.score = cpstats->score;
6522     stats.time = cpstats->time;
6523     stats.pv = cpstats->movelist;
6524     stats.hint = lastHint;
6525     stats.an_move_index = 0;
6526     stats.an_move_count = 0;
6527
6528     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6529         stats.hint = cpstats->move_name;
6530         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6531         stats.an_move_count = cpstats->nr_moves;
6532     }
6533
6534     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6535
6536     SetProgramStats( &stats );
6537 }
6538
6539 int
6540 Adjudicate(ChessProgramState *cps)
6541 {       // [HGM] some adjudications useful with buggy engines
6542         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6543         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6544         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6545         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6546         int k, count = 0; static int bare = 1;
6547         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6548         Boolean canAdjudicate = !appData.icsActive;
6549
6550         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6551         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6552             if( appData.testLegality )
6553             {   /* [HGM] Some more adjudications for obstinate engines */
6554                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6555                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6556                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6557                 static int moveCount = 6;
6558                 ChessMove result;
6559                 char *reason = NULL;
6560
6561                 /* Count what is on board. */
6562                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6563                 {   ChessSquare p = boards[forwardMostMove][i][j];
6564                     int m=i;
6565
6566                     switch((int) p)
6567                     {   /* count B,N,R and other of each side */
6568                         case WhiteKing:
6569                         case BlackKing:
6570                              NrK++; break; // [HGM] atomic: count Kings
6571                         case WhiteKnight:
6572                              NrWN++; break;
6573                         case WhiteBishop:
6574                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6575                              bishopsColor |= 1 << ((i^j)&1);
6576                              NrWB++; break;
6577                         case BlackKnight:
6578                              NrBN++; break;
6579                         case BlackBishop:
6580                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6581                              bishopsColor |= 1 << ((i^j)&1);
6582                              NrBB++; break;
6583                         case WhiteRook:
6584                              NrWR++; break;
6585                         case BlackRook:
6586                              NrBR++; break;
6587                         case WhiteQueen:
6588                              NrWQ++; break;
6589                         case BlackQueen:
6590                              NrBQ++; break;
6591                         case EmptySquare: 
6592                              break;
6593                         case BlackPawn:
6594                              m = 7-i;
6595                         case WhitePawn:
6596                              PawnAdvance += m; NrPawns++;
6597                     }
6598                     NrPieces += (p != EmptySquare);
6599                     NrW += ((int)p < (int)BlackPawn);
6600                     if(gameInfo.variant == VariantXiangqi && 
6601                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6602                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6603                         NrW -= ((int)p < (int)BlackPawn);
6604                     }
6605                 }
6606
6607                 /* Some material-based adjudications that have to be made before stalemate test */
6608                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6609                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6610                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6611                      if(canAdjudicate && appData.checkMates) {
6612                          if(engineOpponent)
6613                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6614                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6615                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6616                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6617                          return 1;
6618                      }
6619                 }
6620
6621                 /* Bare King in Shatranj (loses) or Losers (wins) */
6622                 if( NrW == 1 || NrPieces - NrW == 1) {
6623                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6624                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6625                      if(canAdjudicate && appData.checkMates) {
6626                          if(engineOpponent)
6627                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6628                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6629                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6630                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6631                          return 1;
6632                      }
6633                   } else
6634                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6635                   {    /* bare King */
6636                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6637                         if(canAdjudicate && appData.checkMates) {
6638                             /* but only adjudicate if adjudication enabled */
6639                             if(engineOpponent)
6640                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6641                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6642                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6643                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6644                             return 1;
6645                         }
6646                   }
6647                 } else bare = 1;
6648
6649
6650             // don't wait for engine to announce game end if we can judge ourselves
6651             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6652               case MT_CHECK:
6653                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6654                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6655                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6656                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6657                             checkCnt++;
6658                         if(checkCnt >= 2) {
6659                             reason = "Xboard adjudication: 3rd check";
6660                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6661                             break;
6662                         }
6663                     }
6664                 }
6665               case MT_NONE:
6666               default:
6667                 break;
6668               case MT_STALEMATE:
6669               case MT_STAINMATE:
6670                 reason = "Xboard adjudication: Stalemate";
6671                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6672                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6673                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6674                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6675                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6676                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6677                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6678                                                                         EP_CHECKMATE : EP_WINS);
6679                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6680                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6681                 }
6682                 break;
6683               case MT_CHECKMATE:
6684                 reason = "Xboard adjudication: Checkmate";
6685                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6686                 break;
6687             }
6688
6689                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6690                     case EP_STALEMATE:
6691                         result = GameIsDrawn; break;
6692                     case EP_CHECKMATE:
6693                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6694                     case EP_WINS:
6695                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6696                     default:
6697                         result = (ChessMove) 0;
6698                 }
6699                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6700                     if(engineOpponent)
6701                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6702                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6703                     GameEnds( result, reason, GE_XBOARD );
6704                     return 1;
6705                 }
6706
6707                 /* Next absolutely insufficient mating material. */
6708                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6709                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6710                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6711                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6712                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6713
6714                      /* always flag draws, for judging claims */
6715                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6716
6717                      if(canAdjudicate && appData.materialDraws) {
6718                          /* but only adjudicate them if adjudication enabled */
6719                          if(engineOpponent) {
6720                            SendToProgram("force\n", engineOpponent); // suppress reply
6721                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6722                          }
6723                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6724                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6725                          return 1;
6726                      }
6727                 }
6728
6729                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6730                 if(NrPieces == 4 && 
6731                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6732                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6733                    || NrWN==2 || NrBN==2     /* KNNK */
6734                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6735                   ) ) {
6736                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6737                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6738                           if(engineOpponent) {
6739                             SendToProgram("force\n", engineOpponent); // suppress reply
6740                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6741                           }
6742                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6743                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6744                           return 1;
6745                      }
6746                 } else moveCount = 6;
6747             }
6748         }
6749           
6750         if (appData.debugMode) { int i;
6751             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6752                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6753                     appData.drawRepeats);
6754             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6755               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6756             
6757         }
6758
6759         // Repetition draws and 50-move rule can be applied independently of legality testing
6760
6761                 /* Check for rep-draws */
6762                 count = 0;
6763                 for(k = forwardMostMove-2;
6764                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6765                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6766                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6767                     k-=2)
6768                 {   int rights=0;
6769                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6770                         /* compare castling rights */
6771                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6772                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6773                                 rights++; /* King lost rights, while rook still had them */
6774                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6775                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6776                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6777                                    rights++; /* but at least one rook lost them */
6778                         }
6779                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6780                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6781                                 rights++; 
6782                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6783                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6784                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6785                                    rights++;
6786                         }
6787                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6788                             && appData.drawRepeats > 1) {
6789                              /* adjudicate after user-specified nr of repeats */
6790                              if(engineOpponent) {
6791                                SendToProgram("force\n", engineOpponent); // suppress reply
6792                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6793                              }
6794                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6796                                 // [HGM] xiangqi: check for forbidden perpetuals
6797                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6798                                 for(m=forwardMostMove; m>k; m-=2) {
6799                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6800                                         ourPerpetual = 0; // the current mover did not always check
6801                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6802                                         hisPerpetual = 0; // the opponent did not always check
6803                                 }
6804                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6805                                                                         ourPerpetual, hisPerpetual);
6806                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6807                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6808                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6809                                     return 1;
6810                                 }
6811                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6812                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6813                                 // Now check for perpetual chases
6814                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6815                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6816                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6817                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6818                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6819                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6820                                         return 1;
6821                                     }
6822                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6823                                         break; // Abort repetition-checking loop.
6824                                 }
6825                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6826                              }
6827                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6828                              return 1;
6829                         }
6830                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6831                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6832                     }
6833                 }
6834
6835                 /* Now we test for 50-move draws. Determine ply count */
6836                 count = forwardMostMove;
6837                 /* look for last irreversble move */
6838                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6839                     count--;
6840                 /* if we hit starting position, add initial plies */
6841                 if( count == backwardMostMove )
6842                     count -= initialRulePlies;
6843                 count = forwardMostMove - count; 
6844                 if( count >= 100)
6845                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6846                          /* this is used to judge if draw claims are legal */
6847                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6848                          if(engineOpponent) {
6849                            SendToProgram("force\n", engineOpponent); // suppress reply
6850                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6851                          }
6852                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6853                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6854                          return 1;
6855                 }
6856
6857                 /* if draw offer is pending, treat it as a draw claim
6858                  * when draw condition present, to allow engines a way to
6859                  * claim draws before making their move to avoid a race
6860                  * condition occurring after their move
6861                  */
6862                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6863                          char *p = NULL;
6864                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6865                              p = "Draw claim: 50-move rule";
6866                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6867                              p = "Draw claim: 3-fold repetition";
6868                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6869                              p = "Draw claim: insufficient mating material";
6870                          if( p != NULL && canAdjudicate) {
6871                              if(engineOpponent) {
6872                                SendToProgram("force\n", engineOpponent); // suppress reply
6873                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6874                              }
6875                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6876                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6877                              return 1;
6878                          }
6879                 }
6880
6881                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6882                     if(engineOpponent) {
6883                       SendToProgram("force\n", engineOpponent); // suppress reply
6884                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6885                     }
6886                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6888                     return 1;
6889                 }
6890         return 0;
6891 }
6892
6893 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6894 {   // [HGM] book: this routine intercepts moves to simulate book replies
6895     char *bookHit = NULL;
6896
6897     //first determine if the incoming move brings opponent into his book
6898     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6899         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6900     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6901     if(bookHit != NULL && !cps->bookSuspend) {
6902         // make sure opponent is not going to reply after receiving move to book position
6903         SendToProgram("force\n", cps);
6904         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6905     }
6906     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6907     // now arrange restart after book miss
6908     if(bookHit) {
6909         // after a book hit we never send 'go', and the code after the call to this routine
6910         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6911         char buf[MSG_SIZ];
6912         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6913         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6914         SendToProgram(buf, cps);
6915         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6916     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6917         SendToProgram("go\n", cps);
6918         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6919     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6920         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6921             SendToProgram("go\n", cps); 
6922         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6923     }
6924     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6925 }
6926
6927 char *savedMessage;
6928 ChessProgramState *savedState;
6929 void DeferredBookMove(void)
6930 {
6931         if(savedState->lastPing != savedState->lastPong)
6932                     ScheduleDelayedEvent(DeferredBookMove, 10);
6933         else
6934         HandleMachineMove(savedMessage, savedState);
6935 }
6936
6937 void
6938 HandleMachineMove(message, cps)
6939      char *message;
6940      ChessProgramState *cps;
6941 {
6942     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6943     char realname[MSG_SIZ];
6944     int fromX, fromY, toX, toY;
6945     ChessMove moveType;
6946     char promoChar;
6947     char *p;
6948     int machineWhite;
6949     char *bookHit;
6950
6951     cps->userError = 0;
6952
6953 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6954     /*
6955      * Kludge to ignore BEL characters
6956      */
6957     while (*message == '\007') message++;
6958
6959     /*
6960      * [HGM] engine debug message: ignore lines starting with '#' character
6961      */
6962     if(cps->debug && *message == '#') return;
6963
6964     /*
6965      * Look for book output
6966      */
6967     if (cps == &first && bookRequested) {
6968         if (message[0] == '\t' || message[0] == ' ') {
6969             /* Part of the book output is here; append it */
6970             strcat(bookOutput, message);
6971             strcat(bookOutput, "  \n");
6972             return;
6973         } else if (bookOutput[0] != NULLCHAR) {
6974             /* All of book output has arrived; display it */
6975             char *p = bookOutput;
6976             while (*p != NULLCHAR) {
6977                 if (*p == '\t') *p = ' ';
6978                 p++;
6979             }
6980             DisplayInformation(bookOutput);
6981             bookRequested = FALSE;
6982             /* Fall through to parse the current output */
6983         }
6984     }
6985
6986     /*
6987      * Look for machine move.
6988      */
6989     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6990         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6991     {
6992         /* This method is only useful on engines that support ping */
6993         if (cps->lastPing != cps->lastPong) {
6994           if (gameMode == BeginningOfGame) {
6995             /* Extra move from before last new; ignore */
6996             if (appData.debugMode) {
6997                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6998             }
6999           } else {
7000             if (appData.debugMode) {
7001                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7002                         cps->which, gameMode);
7003             }
7004
7005             SendToProgram("undo\n", cps);
7006           }
7007           return;
7008         }
7009
7010         switch (gameMode) {
7011           case BeginningOfGame:
7012             /* Extra move from before last reset; ignore */
7013             if (appData.debugMode) {
7014                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7015             }
7016             return;
7017
7018           case EndOfGame:
7019           case IcsIdle:
7020           default:
7021             /* Extra move after we tried to stop.  The mode test is
7022                not a reliable way of detecting this problem, but it's
7023                the best we can do on engines that don't support ping.
7024             */
7025             if (appData.debugMode) {
7026                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7027                         cps->which, gameMode);
7028             }
7029             SendToProgram("undo\n", cps);
7030             return;
7031
7032           case MachinePlaysWhite:
7033           case IcsPlayingWhite:
7034             machineWhite = TRUE;
7035             break;
7036
7037           case MachinePlaysBlack:
7038           case IcsPlayingBlack:
7039             machineWhite = FALSE;
7040             break;
7041
7042           case TwoMachinesPlay:
7043             machineWhite = (cps->twoMachinesColor[0] == 'w');
7044             break;
7045         }
7046         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7047             if (appData.debugMode) {
7048                 fprintf(debugFP,
7049                         "Ignoring move out of turn by %s, gameMode %d"
7050                         ", forwardMost %d\n",
7051                         cps->which, gameMode, forwardMostMove);
7052             }
7053             return;
7054         }
7055
7056     if (appData.debugMode) { int f = forwardMostMove;
7057         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7058                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7059                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7060     }
7061         if(cps->alphaRank) AlphaRank(machineMove, 4);
7062         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7063                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7064             /* Machine move could not be parsed; ignore it. */
7065             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7066                     machineMove, cps->which);
7067             DisplayError(buf1, 0);
7068             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7069                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7070             if (gameMode == TwoMachinesPlay) {
7071               GameEnds(machineWhite ? BlackWins : WhiteWins,
7072                        buf1, GE_XBOARD);
7073             }
7074             return;
7075         }
7076
7077         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7078         /* So we have to redo legality test with true e.p. status here,  */
7079         /* to make sure an illegal e.p. capture does not slip through,   */
7080         /* to cause a forfeit on a justified illegal-move complaint      */
7081         /* of the opponent.                                              */
7082         if( gameMode==TwoMachinesPlay && appData.testLegality
7083             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7084                                                               ) {
7085            ChessMove moveType;
7086            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7087                              fromY, fromX, toY, toX, promoChar);
7088             if (appData.debugMode) {
7089                 int i;
7090                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7091                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7092                 fprintf(debugFP, "castling rights\n");
7093             }
7094             if(moveType == IllegalMove) {
7095                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7096                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7097                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7098                            buf1, GE_XBOARD);
7099                 return;
7100            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7101            /* [HGM] Kludge to handle engines that send FRC-style castling
7102               when they shouldn't (like TSCP-Gothic) */
7103            switch(moveType) {
7104              case WhiteASideCastleFR:
7105              case BlackASideCastleFR:
7106                toX+=2;
7107                currentMoveString[2]++;
7108                break;
7109              case WhiteHSideCastleFR:
7110              case BlackHSideCastleFR:
7111                toX--;
7112                currentMoveString[2]--;
7113                break;
7114              default: ; // nothing to do, but suppresses warning of pedantic compilers
7115            }
7116         }
7117         hintRequested = FALSE;
7118         lastHint[0] = NULLCHAR;
7119         bookRequested = FALSE;
7120         /* Program may be pondering now */
7121         cps->maybeThinking = TRUE;
7122         if (cps->sendTime == 2) cps->sendTime = 1;
7123         if (cps->offeredDraw) cps->offeredDraw--;
7124
7125         /* currentMoveString is set as a side-effect of ParseOneMove */
7126         strcpy(machineMove, currentMoveString);
7127         strcat(machineMove, "\n");
7128         strcpy(moveList[forwardMostMove], machineMove);
7129
7130         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7131
7132         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7133         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7134             int count = 0;
7135
7136             while( count < adjudicateLossPlies ) {
7137                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7138
7139                 if( count & 1 ) {
7140                     score = -score; /* Flip score for winning side */
7141                 }
7142
7143                 if( score > adjudicateLossThreshold ) {
7144                     break;
7145                 }
7146
7147                 count++;
7148             }
7149
7150             if( count >= adjudicateLossPlies ) {
7151                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7152
7153                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7154                     "Xboard adjudication", 
7155                     GE_XBOARD );
7156
7157                 return;
7158             }
7159         }
7160
7161         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7162
7163 #if ZIPPY
7164         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7165             first.initDone) {
7166           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7167                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7168                 SendToICS("draw ");
7169                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7170           }
7171           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7172           ics_user_moved = 1;
7173           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7174                 char buf[3*MSG_SIZ];
7175
7176                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7177                         programStats.score / 100.,
7178                         programStats.depth,
7179                         programStats.time / 100.,
7180                         (unsigned int)programStats.nodes,
7181                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7182                         programStats.movelist);
7183                 SendToICS(buf);
7184 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7185           }
7186         }
7187 #endif
7188
7189         /* [AS] Save move info and clear stats for next move */
7190         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7191         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7192         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7193         ClearProgramStats();
7194         thinkOutput[0] = NULLCHAR;
7195         hiddenThinkOutputState = 0;
7196
7197         bookHit = NULL;
7198         if (gameMode == TwoMachinesPlay) {
7199             /* [HGM] relaying draw offers moved to after reception of move */
7200             /* and interpreting offer as claim if it brings draw condition */
7201             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7202                 SendToProgram("draw\n", cps->other);
7203             }
7204             if (cps->other->sendTime) {
7205                 SendTimeRemaining(cps->other,
7206                                   cps->other->twoMachinesColor[0] == 'w');
7207             }
7208             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7209             if (firstMove && !bookHit) {
7210                 firstMove = FALSE;
7211                 if (cps->other->useColors) {
7212                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7213                 }
7214                 SendToProgram("go\n", cps->other);
7215             }
7216             cps->other->maybeThinking = TRUE;
7217         }
7218
7219         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7220         
7221         if (!pausing && appData.ringBellAfterMoves) {
7222             RingBell();
7223         }
7224
7225         /* 
7226          * Reenable menu items that were disabled while
7227          * machine was thinking
7228          */
7229         if (gameMode != TwoMachinesPlay)
7230             SetUserThinkingEnables();
7231
7232         // [HGM] book: after book hit opponent has received move and is now in force mode
7233         // force the book reply into it, and then fake that it outputted this move by jumping
7234         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7235         if(bookHit) {
7236                 static char bookMove[MSG_SIZ]; // a bit generous?
7237
7238                 strcpy(bookMove, "move ");
7239                 strcat(bookMove, bookHit);
7240                 message = bookMove;
7241                 cps = cps->other;
7242                 programStats.nodes = programStats.depth = programStats.time = 
7243                 programStats.score = programStats.got_only_move = 0;
7244                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7245
7246                 if(cps->lastPing != cps->lastPong) {
7247                     savedMessage = message; // args for deferred call
7248                     savedState = cps;
7249                     ScheduleDelayedEvent(DeferredBookMove, 10);
7250                     return;
7251                 }
7252                 goto FakeBookMove;
7253         }
7254
7255         return;
7256     }
7257
7258     /* Set special modes for chess engines.  Later something general
7259      *  could be added here; for now there is just one kludge feature,
7260      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7261      *  when "xboard" is given as an interactive command.
7262      */
7263     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7264         cps->useSigint = FALSE;
7265         cps->useSigterm = FALSE;
7266     }
7267     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7268       ParseFeatures(message+8, cps);
7269       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7270     }
7271
7272     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7273      * want this, I was asked to put it in, and obliged.
7274      */
7275     if (!strncmp(message, "setboard ", 9)) {
7276         Board initial_position;
7277
7278         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7279
7280         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7281             DisplayError(_("Bad FEN received from engine"), 0);
7282             return ;
7283         } else {
7284            Reset(TRUE, FALSE);
7285            CopyBoard(boards[0], initial_position);
7286            initialRulePlies = FENrulePlies;
7287            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7288            else gameMode = MachinePlaysBlack;                 
7289            DrawPosition(FALSE, boards[currentMove]);
7290         }
7291         return;
7292     }
7293
7294     /*
7295      * Look for communication commands
7296      */
7297     if (!strncmp(message, "telluser ", 9)) {
7298         DisplayNote(message + 9);
7299         return;
7300     }
7301     if (!strncmp(message, "tellusererror ", 14)) {
7302         cps->userError = 1;
7303         DisplayError(message + 14, 0);
7304         return;
7305     }
7306     if (!strncmp(message, "tellopponent ", 13)) {
7307       if (appData.icsActive) {
7308         if (loggedOn) {
7309           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7310           SendToICS(buf1);
7311         }
7312       } else {
7313         DisplayNote(message + 13);
7314       }
7315       return;
7316     }
7317     if (!strncmp(message, "tellothers ", 11)) {
7318       if (appData.icsActive) {
7319         if (loggedOn) {
7320           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7321           SendToICS(buf1);
7322         }
7323       }
7324       return;
7325     }
7326     if (!strncmp(message, "tellall ", 8)) {
7327       if (appData.icsActive) {
7328         if (loggedOn) {
7329           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7330           SendToICS(buf1);
7331         }
7332       } else {
7333         DisplayNote(message + 8);
7334       }
7335       return;
7336     }
7337     if (strncmp(message, "warning", 7) == 0) {
7338         /* Undocumented feature, use tellusererror in new code */
7339         DisplayError(message, 0);
7340         return;
7341     }
7342     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7343         strcpy(realname, cps->tidy);
7344         strcat(realname, " query");
7345         AskQuestion(realname, buf2, buf1, cps->pr);
7346         return;
7347     }
7348     /* Commands from the engine directly to ICS.  We don't allow these to be 
7349      *  sent until we are logged on. Crafty kibitzes have been known to 
7350      *  interfere with the login process.
7351      */
7352     if (loggedOn) {
7353         if (!strncmp(message, "tellics ", 8)) {
7354             SendToICS(message + 8);
7355             SendToICS("\n");
7356             return;
7357         }
7358         if (!strncmp(message, "tellicsnoalias ", 15)) {
7359             SendToICS(ics_prefix);
7360             SendToICS(message + 15);
7361             SendToICS("\n");
7362             return;
7363         }
7364         /* The following are for backward compatibility only */
7365         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7366             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7367             SendToICS(ics_prefix);
7368             SendToICS(message);
7369             SendToICS("\n");
7370             return;
7371         }
7372     }
7373     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7374         return;
7375     }
7376     /*
7377      * If the move is illegal, cancel it and redraw the board.
7378      * Also deal with other error cases.  Matching is rather loose
7379      * here to accommodate engines written before the spec.
7380      */
7381     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7382         strncmp(message, "Error", 5) == 0) {
7383         if (StrStr(message, "name") || 
7384             StrStr(message, "rating") || StrStr(message, "?") ||
7385             StrStr(message, "result") || StrStr(message, "board") ||
7386             StrStr(message, "bk") || StrStr(message, "computer") ||
7387             StrStr(message, "variant") || StrStr(message, "hint") ||
7388             StrStr(message, "random") || StrStr(message, "depth") ||
7389             StrStr(message, "accepted")) {
7390             return;
7391         }
7392         if (StrStr(message, "protover")) {
7393           /* Program is responding to input, so it's apparently done
7394              initializing, and this error message indicates it is
7395              protocol version 1.  So we don't need to wait any longer
7396              for it to initialize and send feature commands. */
7397           FeatureDone(cps, 1);
7398           cps->protocolVersion = 1;
7399           return;
7400         }
7401         cps->maybeThinking = FALSE;
7402
7403         if (StrStr(message, "draw")) {
7404             /* Program doesn't have "draw" command */
7405             cps->sendDrawOffers = 0;
7406             return;
7407         }
7408         if (cps->sendTime != 1 &&
7409             (StrStr(message, "time") || StrStr(message, "otim"))) {
7410           /* Program apparently doesn't have "time" or "otim" command */
7411           cps->sendTime = 0;
7412           return;
7413         }
7414         if (StrStr(message, "analyze")) {
7415             cps->analysisSupport = FALSE;
7416             cps->analyzing = FALSE;
7417             Reset(FALSE, TRUE);
7418             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7419             DisplayError(buf2, 0);
7420             return;
7421         }
7422         if (StrStr(message, "(no matching move)st")) {
7423           /* Special kludge for GNU Chess 4 only */
7424           cps->stKludge = TRUE;
7425           SendTimeControl(cps, movesPerSession, timeControl,
7426                           timeIncrement, appData.searchDepth,
7427                           searchTime);
7428           return;
7429         }
7430         if (StrStr(message, "(no matching move)sd")) {
7431           /* Special kludge for GNU Chess 4 only */
7432           cps->sdKludge = TRUE;
7433           SendTimeControl(cps, movesPerSession, timeControl,
7434                           timeIncrement, appData.searchDepth,
7435                           searchTime);
7436           return;
7437         }
7438         if (!StrStr(message, "llegal")) {
7439             return;
7440         }
7441         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7442             gameMode == IcsIdle) return;
7443         if (forwardMostMove <= backwardMostMove) return;
7444         if (pausing) PauseEvent();
7445       if(appData.forceIllegal) {
7446             // [HGM] illegal: machine refused move; force position after move into it
7447           SendToProgram("force\n", cps);
7448           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7449                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7450                 // when black is to move, while there might be nothing on a2 or black
7451                 // might already have the move. So send the board as if white has the move.
7452                 // But first we must change the stm of the engine, as it refused the last move
7453                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7454                 if(WhiteOnMove(forwardMostMove)) {
7455                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7456                     SendBoard(cps, forwardMostMove); // kludgeless board
7457                 } else {
7458                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7459                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7460                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7461                 }
7462           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7463             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7464                  gameMode == TwoMachinesPlay)
7465               SendToProgram("go\n", cps);
7466             return;
7467       } else
7468         if (gameMode == PlayFromGameFile) {
7469             /* Stop reading this game file */
7470             gameMode = EditGame;
7471             ModeHighlight();
7472         }
7473         currentMove = forwardMostMove-1;
7474         DisplayMove(currentMove-1); /* before DisplayMoveError */
7475         SwitchClocks(forwardMostMove-1); // [HGM] race
7476         DisplayBothClocks();
7477         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7478                 parseList[currentMove], cps->which);
7479         DisplayMoveError(buf1);
7480         DrawPosition(FALSE, boards[currentMove]);
7481
7482         /* [HGM] illegal-move claim should forfeit game when Xboard */
7483         /* only passes fully legal moves                            */
7484         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7485             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7486                                 "False illegal-move claim", GE_XBOARD );
7487         }
7488         return;
7489     }
7490     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7491         /* Program has a broken "time" command that
7492            outputs a string not ending in newline.
7493            Don't use it. */
7494         cps->sendTime = 0;
7495     }
7496     
7497     /*
7498      * If chess program startup fails, exit with an error message.
7499      * Attempts to recover here are futile.
7500      */
7501     if ((StrStr(message, "unknown host") != NULL)
7502         || (StrStr(message, "No remote directory") != NULL)
7503         || (StrStr(message, "not found") != NULL)
7504         || (StrStr(message, "No such file") != NULL)
7505         || (StrStr(message, "can't alloc") != NULL)
7506         || (StrStr(message, "Permission denied") != NULL)) {
7507
7508         cps->maybeThinking = FALSE;
7509         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7510                 cps->which, cps->program, cps->host, message);
7511         RemoveInputSource(cps->isr);
7512         DisplayFatalError(buf1, 0, 1);
7513         return;
7514     }
7515     
7516     /* 
7517      * Look for hint output
7518      */
7519     if (sscanf(message, "Hint: %s", buf1) == 1) {
7520         if (cps == &first && hintRequested) {
7521             hintRequested = FALSE;
7522             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7523                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7524                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7525                                     PosFlags(forwardMostMove),
7526                                     fromY, fromX, toY, toX, promoChar, buf1);
7527                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7528                 DisplayInformation(buf2);
7529             } else {
7530                 /* Hint move could not be parsed!? */
7531               snprintf(buf2, sizeof(buf2),
7532                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7533                         buf1, cps->which);
7534                 DisplayError(buf2, 0);
7535             }
7536         } else {
7537             strcpy(lastHint, buf1);
7538         }
7539         return;
7540     }
7541
7542     /*
7543      * Ignore other messages if game is not in progress
7544      */
7545     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7546         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7547
7548     /*
7549      * look for win, lose, draw, or draw offer
7550      */
7551     if (strncmp(message, "1-0", 3) == 0) {
7552         char *p, *q, *r = "";
7553         p = strchr(message, '{');
7554         if (p) {
7555             q = strchr(p, '}');
7556             if (q) {
7557                 *q = NULLCHAR;
7558                 r = p + 1;
7559             }
7560         }
7561         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7562         return;
7563     } else if (strncmp(message, "0-1", 3) == 0) {
7564         char *p, *q, *r = "";
7565         p = strchr(message, '{');
7566         if (p) {
7567             q = strchr(p, '}');
7568             if (q) {
7569                 *q = NULLCHAR;
7570                 r = p + 1;
7571             }
7572         }
7573         /* Kludge for Arasan 4.1 bug */
7574         if (strcmp(r, "Black resigns") == 0) {
7575             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7576             return;
7577         }
7578         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7579         return;
7580     } else if (strncmp(message, "1/2", 3) == 0) {
7581         char *p, *q, *r = "";
7582         p = strchr(message, '{');
7583         if (p) {
7584             q = strchr(p, '}');
7585             if (q) {
7586                 *q = NULLCHAR;
7587                 r = p + 1;
7588             }
7589         }
7590             
7591         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7592         return;
7593
7594     } else if (strncmp(message, "White resign", 12) == 0) {
7595         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7596         return;
7597     } else if (strncmp(message, "Black resign", 12) == 0) {
7598         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7599         return;
7600     } else if (strncmp(message, "White matches", 13) == 0 ||
7601                strncmp(message, "Black matches", 13) == 0   ) {
7602         /* [HGM] ignore GNUShogi noises */
7603         return;
7604     } else if (strncmp(message, "White", 5) == 0 &&
7605                message[5] != '(' &&
7606                StrStr(message, "Black") == NULL) {
7607         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7608         return;
7609     } else if (strncmp(message, "Black", 5) == 0 &&
7610                message[5] != '(') {
7611         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7612         return;
7613     } else if (strcmp(message, "resign") == 0 ||
7614                strcmp(message, "computer resigns") == 0) {
7615         switch (gameMode) {
7616           case MachinePlaysBlack:
7617           case IcsPlayingBlack:
7618             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7619             break;
7620           case MachinePlaysWhite:
7621           case IcsPlayingWhite:
7622             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7623             break;
7624           case TwoMachinesPlay:
7625             if (cps->twoMachinesColor[0] == 'w')
7626               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7627             else
7628               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7629             break;
7630           default:
7631             /* can't happen */
7632             break;
7633         }
7634         return;
7635     } else if (strncmp(message, "opponent mates", 14) == 0) {
7636         switch (gameMode) {
7637           case MachinePlaysBlack:
7638           case IcsPlayingBlack:
7639             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7640             break;
7641           case MachinePlaysWhite:
7642           case IcsPlayingWhite:
7643             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7644             break;
7645           case TwoMachinesPlay:
7646             if (cps->twoMachinesColor[0] == 'w')
7647               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7648             else
7649               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7650             break;
7651           default:
7652             /* can't happen */
7653             break;
7654         }
7655         return;
7656     } else if (strncmp(message, "computer mates", 14) == 0) {
7657         switch (gameMode) {
7658           case MachinePlaysBlack:
7659           case IcsPlayingBlack:
7660             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7661             break;
7662           case MachinePlaysWhite:
7663           case IcsPlayingWhite:
7664             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7665             break;
7666           case TwoMachinesPlay:
7667             if (cps->twoMachinesColor[0] == 'w')
7668               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7669             else
7670               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7671             break;
7672           default:
7673             /* can't happen */
7674             break;
7675         }
7676         return;
7677     } else if (strncmp(message, "checkmate", 9) == 0) {
7678         if (WhiteOnMove(forwardMostMove)) {
7679             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7680         } else {
7681             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7682         }
7683         return;
7684     } else if (strstr(message, "Draw") != NULL ||
7685                strstr(message, "game is a draw") != NULL) {
7686         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7687         return;
7688     } else if (strstr(message, "offer") != NULL &&
7689                strstr(message, "draw") != NULL) {
7690 #if ZIPPY
7691         if (appData.zippyPlay && first.initDone) {
7692             /* Relay offer to ICS */
7693             SendToICS(ics_prefix);
7694             SendToICS("draw\n");
7695         }
7696 #endif
7697         cps->offeredDraw = 2; /* valid until this engine moves twice */
7698         if (gameMode == TwoMachinesPlay) {
7699             if (cps->other->offeredDraw) {
7700                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7701             /* [HGM] in two-machine mode we delay relaying draw offer      */
7702             /* until after we also have move, to see if it is really claim */
7703             }
7704         } else if (gameMode == MachinePlaysWhite ||
7705                    gameMode == MachinePlaysBlack) {
7706           if (userOfferedDraw) {
7707             DisplayInformation(_("Machine accepts your draw offer"));
7708             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7709           } else {
7710             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7711           }
7712         }
7713     }
7714
7715     
7716     /*
7717      * Look for thinking output
7718      */
7719     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7720           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7721                                 ) {
7722         int plylev, mvleft, mvtot, curscore, time;
7723         char mvname[MOVE_LEN];
7724         u64 nodes; // [DM]
7725         char plyext;
7726         int ignore = FALSE;
7727         int prefixHint = FALSE;
7728         mvname[0] = NULLCHAR;
7729
7730         switch (gameMode) {
7731           case MachinePlaysBlack:
7732           case IcsPlayingBlack:
7733             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7734             break;
7735           case MachinePlaysWhite:
7736           case IcsPlayingWhite:
7737             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7738             break;
7739           case AnalyzeMode:
7740           case AnalyzeFile:
7741             break;
7742           case IcsObserving: /* [DM] icsEngineAnalyze */
7743             if (!appData.icsEngineAnalyze) ignore = TRUE;
7744             break;
7745           case TwoMachinesPlay:
7746             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7747                 ignore = TRUE;
7748             }
7749             break;
7750           default:
7751             ignore = TRUE;
7752             break;
7753         }
7754
7755         if (!ignore) {
7756             buf1[0] = NULLCHAR;
7757             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7758                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7759
7760                 if (plyext != ' ' && plyext != '\t') {
7761                     time *= 100;
7762                 }
7763
7764                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7765                 if( cps->scoreIsAbsolute && 
7766                     ( gameMode == MachinePlaysBlack ||
7767                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7768                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7769                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7770                      !WhiteOnMove(currentMove)
7771                     ) )
7772                 {
7773                     curscore = -curscore;
7774                 }
7775
7776
7777                 programStats.depth = plylev;
7778                 programStats.nodes = nodes;
7779                 programStats.time = time;
7780                 programStats.score = curscore;
7781                 programStats.got_only_move = 0;
7782
7783                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7784                         int ticklen;
7785
7786                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7787                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7788                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7789                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7790                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7791                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7792                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7793                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7794                 }
7795
7796                 /* Buffer overflow protection */
7797                 if (buf1[0] != NULLCHAR) {
7798                     if (strlen(buf1) >= sizeof(programStats.movelist)
7799                         && appData.debugMode) {
7800                         fprintf(debugFP,
7801                                 "PV is too long; using the first %u bytes.\n",
7802                                 (unsigned) sizeof(programStats.movelist) - 1);
7803                     }
7804
7805                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7806                 } else {
7807                     sprintf(programStats.movelist, " no PV\n");
7808                 }
7809
7810                 if (programStats.seen_stat) {
7811                     programStats.ok_to_send = 1;
7812                 }
7813
7814                 if (strchr(programStats.movelist, '(') != NULL) {
7815                     programStats.line_is_book = 1;
7816                     programStats.nr_moves = 0;
7817                     programStats.moves_left = 0;
7818                 } else {
7819                     programStats.line_is_book = 0;
7820                 }
7821
7822                 SendProgramStatsToFrontend( cps, &programStats );
7823
7824                 /* 
7825                     [AS] Protect the thinkOutput buffer from overflow... this
7826                     is only useful if buf1 hasn't overflowed first!
7827                 */
7828                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7829                         plylev, 
7830                         (gameMode == TwoMachinesPlay ?
7831                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7832                         ((double) curscore) / 100.0,
7833                         prefixHint ? lastHint : "",
7834                         prefixHint ? " " : "" );
7835
7836                 if( buf1[0] != NULLCHAR ) {
7837                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7838
7839                     if( strlen(buf1) > max_len ) {
7840                         if( appData.debugMode) {
7841                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7842                         }
7843                         buf1[max_len+1] = '\0';
7844                     }
7845
7846                     strcat( thinkOutput, buf1 );
7847                 }
7848
7849                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7850                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7851                     DisplayMove(currentMove - 1);
7852                 }
7853                 return;
7854
7855             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7856                 /* crafty (9.25+) says "(only move) <move>"
7857                  * if there is only 1 legal move
7858                  */
7859                 sscanf(p, "(only move) %s", buf1);
7860                 sprintf(thinkOutput, "%s (only move)", buf1);
7861                 sprintf(programStats.movelist, "%s (only move)", buf1);
7862                 programStats.depth = 1;
7863                 programStats.nr_moves = 1;
7864                 programStats.moves_left = 1;
7865                 programStats.nodes = 1;
7866                 programStats.time = 1;
7867                 programStats.got_only_move = 1;
7868
7869                 /* Not really, but we also use this member to
7870                    mean "line isn't going to change" (Crafty
7871                    isn't searching, so stats won't change) */
7872                 programStats.line_is_book = 1;
7873
7874                 SendProgramStatsToFrontend( cps, &programStats );
7875                 
7876                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7877                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7878                     DisplayMove(currentMove - 1);
7879                 }
7880                 return;
7881             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7882                               &time, &nodes, &plylev, &mvleft,
7883                               &mvtot, mvname) >= 5) {
7884                 /* The stat01: line is from Crafty (9.29+) in response
7885                    to the "." command */
7886                 programStats.seen_stat = 1;
7887                 cps->maybeThinking = TRUE;
7888
7889                 if (programStats.got_only_move || !appData.periodicUpdates)
7890                   return;
7891
7892                 programStats.depth = plylev;
7893                 programStats.time = time;
7894                 programStats.nodes = nodes;
7895                 programStats.moves_left = mvleft;
7896                 programStats.nr_moves = mvtot;
7897                 strcpy(programStats.move_name, mvname);
7898                 programStats.ok_to_send = 1;
7899                 programStats.movelist[0] = '\0';
7900
7901                 SendProgramStatsToFrontend( cps, &programStats );
7902
7903                 return;
7904
7905             } else if (strncmp(message,"++",2) == 0) {
7906                 /* Crafty 9.29+ outputs this */
7907                 programStats.got_fail = 2;
7908                 return;
7909
7910             } else if (strncmp(message,"--",2) == 0) {
7911                 /* Crafty 9.29+ outputs this */
7912                 programStats.got_fail = 1;
7913                 return;
7914
7915             } else if (thinkOutput[0] != NULLCHAR &&
7916                        strncmp(message, "    ", 4) == 0) {
7917                 unsigned message_len;
7918
7919                 p = message;
7920                 while (*p && *p == ' ') p++;
7921
7922                 message_len = strlen( p );
7923
7924                 /* [AS] Avoid buffer overflow */
7925                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7926                     strcat(thinkOutput, " ");
7927                     strcat(thinkOutput, p);
7928                 }
7929
7930                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7931                     strcat(programStats.movelist, " ");
7932                     strcat(programStats.movelist, p);
7933                 }
7934
7935                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7936                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7937                     DisplayMove(currentMove - 1);
7938                 }
7939                 return;
7940             }
7941         }
7942         else {
7943             buf1[0] = NULLCHAR;
7944
7945             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7946                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7947             {
7948                 ChessProgramStats cpstats;
7949
7950                 if (plyext != ' ' && plyext != '\t') {
7951                     time *= 100;
7952                 }
7953
7954                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7955                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7956                     curscore = -curscore;
7957                 }
7958
7959                 cpstats.depth = plylev;
7960                 cpstats.nodes = nodes;
7961                 cpstats.time = time;
7962                 cpstats.score = curscore;
7963                 cpstats.got_only_move = 0;
7964                 cpstats.movelist[0] = '\0';
7965
7966                 if (buf1[0] != NULLCHAR) {
7967                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7968                 }
7969
7970                 cpstats.ok_to_send = 0;
7971                 cpstats.line_is_book = 0;
7972                 cpstats.nr_moves = 0;
7973                 cpstats.moves_left = 0;
7974
7975                 SendProgramStatsToFrontend( cps, &cpstats );
7976             }
7977         }
7978     }
7979 }
7980
7981
7982 /* Parse a game score from the character string "game", and
7983    record it as the history of the current game.  The game
7984    score is NOT assumed to start from the standard position. 
7985    The display is not updated in any way.
7986    */
7987 void
7988 ParseGameHistory(game)
7989      char *game;
7990 {
7991     ChessMove moveType;
7992     int fromX, fromY, toX, toY, boardIndex;
7993     char promoChar;
7994     char *p, *q;
7995     char buf[MSG_SIZ];
7996
7997     if (appData.debugMode)
7998       fprintf(debugFP, "Parsing game history: %s\n", game);
7999
8000     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8001     gameInfo.site = StrSave(appData.icsHost);
8002     gameInfo.date = PGNDate();
8003     gameInfo.round = StrSave("-");
8004
8005     /* Parse out names of players */
8006     while (*game == ' ') game++;
8007     p = buf;
8008     while (*game != ' ') *p++ = *game++;
8009     *p = NULLCHAR;
8010     gameInfo.white = StrSave(buf);
8011     while (*game == ' ') game++;
8012     p = buf;
8013     while (*game != ' ' && *game != '\n') *p++ = *game++;
8014     *p = NULLCHAR;
8015     gameInfo.black = StrSave(buf);
8016
8017     /* Parse moves */
8018     boardIndex = blackPlaysFirst ? 1 : 0;
8019     yynewstr(game);
8020     for (;;) {
8021         yyboardindex = boardIndex;
8022         moveType = (ChessMove) yylex();
8023         switch (moveType) {
8024           case IllegalMove:             /* maybe suicide chess, etc. */
8025   if (appData.debugMode) {
8026     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8027     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8028     setbuf(debugFP, NULL);
8029   }
8030           case WhitePromotionChancellor:
8031           case BlackPromotionChancellor:
8032           case WhitePromotionArchbishop:
8033           case BlackPromotionArchbishop:
8034           case WhitePromotionQueen:
8035           case BlackPromotionQueen:
8036           case WhitePromotionRook:
8037           case BlackPromotionRook:
8038           case WhitePromotionBishop:
8039           case BlackPromotionBishop:
8040           case WhitePromotionKnight:
8041           case BlackPromotionKnight:
8042           case WhitePromotionKing:
8043           case BlackPromotionKing:
8044           case NormalMove:
8045           case WhiteCapturesEnPassant:
8046           case BlackCapturesEnPassant:
8047           case WhiteKingSideCastle:
8048           case WhiteQueenSideCastle:
8049           case BlackKingSideCastle:
8050           case BlackQueenSideCastle:
8051           case WhiteKingSideCastleWild:
8052           case WhiteQueenSideCastleWild:
8053           case BlackKingSideCastleWild:
8054           case BlackQueenSideCastleWild:
8055           /* PUSH Fabien */
8056           case WhiteHSideCastleFR:
8057           case WhiteASideCastleFR:
8058           case BlackHSideCastleFR:
8059           case BlackASideCastleFR:
8060           /* POP Fabien */
8061             fromX = currentMoveString[0] - AAA;
8062             fromY = currentMoveString[1] - ONE;
8063             toX = currentMoveString[2] - AAA;
8064             toY = currentMoveString[3] - ONE;
8065             promoChar = currentMoveString[4];
8066             break;
8067           case WhiteDrop:
8068           case BlackDrop:
8069             fromX = moveType == WhiteDrop ?
8070               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8071             (int) CharToPiece(ToLower(currentMoveString[0]));
8072             fromY = DROP_RANK;
8073             toX = currentMoveString[2] - AAA;
8074             toY = currentMoveString[3] - ONE;
8075             promoChar = NULLCHAR;
8076             break;
8077           case AmbiguousMove:
8078             /* bug? */
8079             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8080   if (appData.debugMode) {
8081     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8082     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8083     setbuf(debugFP, NULL);
8084   }
8085             DisplayError(buf, 0);
8086             return;
8087           case ImpossibleMove:
8088             /* bug? */
8089             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8090   if (appData.debugMode) {
8091     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8092     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8093     setbuf(debugFP, NULL);
8094   }
8095             DisplayError(buf, 0);
8096             return;
8097           case (ChessMove) 0:   /* end of file */
8098             if (boardIndex < backwardMostMove) {
8099                 /* Oops, gap.  How did that happen? */
8100                 DisplayError(_("Gap in move list"), 0);
8101                 return;
8102             }
8103             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8104             if (boardIndex > forwardMostMove) {
8105                 forwardMostMove = boardIndex;
8106             }
8107             return;
8108           case ElapsedTime:
8109             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8110                 strcat(parseList[boardIndex-1], " ");
8111                 strcat(parseList[boardIndex-1], yy_text);
8112             }
8113             continue;
8114           case Comment:
8115           case PGNTag:
8116           case NAG:
8117           default:
8118             /* ignore */
8119             continue;
8120           case WhiteWins:
8121           case BlackWins:
8122           case GameIsDrawn:
8123           case GameUnfinished:
8124             if (gameMode == IcsExamining) {
8125                 if (boardIndex < backwardMostMove) {
8126                     /* Oops, gap.  How did that happen? */
8127                     return;
8128                 }
8129                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8130                 return;
8131             }
8132             gameInfo.result = moveType;
8133             p = strchr(yy_text, '{');
8134             if (p == NULL) p = strchr(yy_text, '(');
8135             if (p == NULL) {
8136                 p = yy_text;
8137                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8138             } else {
8139                 q = strchr(p, *p == '{' ? '}' : ')');
8140                 if (q != NULL) *q = NULLCHAR;
8141                 p++;
8142             }
8143             gameInfo.resultDetails = StrSave(p);
8144             continue;
8145         }
8146         if (boardIndex >= forwardMostMove &&
8147             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8148             backwardMostMove = blackPlaysFirst ? 1 : 0;
8149             return;
8150         }
8151         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8152                                  fromY, fromX, toY, toX, promoChar,
8153                                  parseList[boardIndex]);
8154         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8155         /* currentMoveString is set as a side-effect of yylex */
8156         strcpy(moveList[boardIndex], currentMoveString);
8157         strcat(moveList[boardIndex], "\n");
8158         boardIndex++;
8159         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8160         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8161           case MT_NONE:
8162           case MT_STALEMATE:
8163           default:
8164             break;
8165           case MT_CHECK:
8166             if(gameInfo.variant != VariantShogi)
8167                 strcat(parseList[boardIndex - 1], "+");
8168             break;
8169           case MT_CHECKMATE:
8170           case MT_STAINMATE:
8171             strcat(parseList[boardIndex - 1], "#");
8172             break;
8173         }
8174     }
8175 }
8176
8177
8178 /* Apply a move to the given board  */
8179 void
8180 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8181      int fromX, fromY, toX, toY;
8182      int promoChar;
8183      Board board;
8184 {
8185   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8186   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8187
8188     /* [HGM] compute & store e.p. status and castling rights for new position */
8189     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8190     { int i;
8191
8192       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8193       oldEP = (signed char)board[EP_STATUS];
8194       board[EP_STATUS] = EP_NONE;
8195
8196       if( board[toY][toX] != EmptySquare ) 
8197            board[EP_STATUS] = EP_CAPTURE;  
8198
8199       if( board[fromY][fromX] == WhitePawn ) {
8200            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8201                board[EP_STATUS] = EP_PAWN_MOVE;
8202            if( toY-fromY==2) {
8203                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8204                         gameInfo.variant != VariantBerolina || toX < fromX)
8205                       board[EP_STATUS] = toX | berolina;
8206                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8207                         gameInfo.variant != VariantBerolina || toX > fromX) 
8208                       board[EP_STATUS] = toX;
8209            }
8210       } else 
8211       if( board[fromY][fromX] == BlackPawn ) {
8212            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8213                board[EP_STATUS] = EP_PAWN_MOVE; 
8214            if( toY-fromY== -2) {
8215                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8216                         gameInfo.variant != VariantBerolina || toX < fromX)
8217                       board[EP_STATUS] = toX | berolina;
8218                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8219                         gameInfo.variant != VariantBerolina || toX > fromX) 
8220                       board[EP_STATUS] = toX;
8221            }
8222        }
8223
8224        for(i=0; i<nrCastlingRights; i++) {
8225            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8226               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8227              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8228        }
8229
8230     }
8231
8232   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8233   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8234        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8235          
8236   if (fromX == toX && fromY == toY) return;
8237
8238   if (fromY == DROP_RANK) {
8239         /* must be first */
8240         piece = board[toY][toX] = (ChessSquare) fromX;
8241   } else {
8242      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8243      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8244      if(gameInfo.variant == VariantKnightmate)
8245          king += (int) WhiteUnicorn - (int) WhiteKing;
8246
8247     /* Code added by Tord: */
8248     /* FRC castling assumed when king captures friendly rook. */
8249     if (board[fromY][fromX] == WhiteKing &&
8250              board[toY][toX] == WhiteRook) {
8251       board[fromY][fromX] = EmptySquare;
8252       board[toY][toX] = EmptySquare;
8253       if(toX > fromX) {
8254         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8255       } else {
8256         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8257       }
8258     } else if (board[fromY][fromX] == BlackKing &&
8259                board[toY][toX] == BlackRook) {
8260       board[fromY][fromX] = EmptySquare;
8261       board[toY][toX] = EmptySquare;
8262       if(toX > fromX) {
8263         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8264       } else {
8265         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8266       }
8267     /* End of code added by Tord */
8268
8269     } else if (board[fromY][fromX] == king
8270         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8271         && toY == fromY && toX > fromX+1) {
8272         board[fromY][fromX] = EmptySquare;
8273         board[toY][toX] = king;
8274         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8275         board[fromY][BOARD_RGHT-1] = EmptySquare;
8276     } else if (board[fromY][fromX] == king
8277         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8278                && toY == fromY && toX < fromX-1) {
8279         board[fromY][fromX] = EmptySquare;
8280         board[toY][toX] = king;
8281         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8282         board[fromY][BOARD_LEFT] = EmptySquare;
8283     } else if (board[fromY][fromX] == WhitePawn
8284                && toY >= BOARD_HEIGHT-promoRank
8285                && gameInfo.variant != VariantXiangqi
8286                ) {
8287         /* white pawn promotion */
8288         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8289         if (board[toY][toX] == EmptySquare) {
8290             board[toY][toX] = WhiteQueen;
8291         }
8292         if(gameInfo.variant==VariantBughouse ||
8293            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8294             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8295         board[fromY][fromX] = EmptySquare;
8296     } else if ((fromY == BOARD_HEIGHT-4)
8297                && (toX != fromX)
8298                && gameInfo.variant != VariantXiangqi
8299                && gameInfo.variant != VariantBerolina
8300                && (board[fromY][fromX] == WhitePawn)
8301                && (board[toY][toX] == EmptySquare)) {
8302         board[fromY][fromX] = EmptySquare;
8303         board[toY][toX] = WhitePawn;
8304         captured = board[toY - 1][toX];
8305         board[toY - 1][toX] = EmptySquare;
8306     } else if ((fromY == BOARD_HEIGHT-4)
8307                && (toX == fromX)
8308                && gameInfo.variant == VariantBerolina
8309                && (board[fromY][fromX] == WhitePawn)
8310                && (board[toY][toX] == EmptySquare)) {
8311         board[fromY][fromX] = EmptySquare;
8312         board[toY][toX] = WhitePawn;
8313         if(oldEP & EP_BEROLIN_A) {
8314                 captured = board[fromY][fromX-1];
8315                 board[fromY][fromX-1] = EmptySquare;
8316         }else{  captured = board[fromY][fromX+1];
8317                 board[fromY][fromX+1] = EmptySquare;
8318         }
8319     } else if (board[fromY][fromX] == king
8320         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8321                && toY == fromY && toX > fromX+1) {
8322         board[fromY][fromX] = EmptySquare;
8323         board[toY][toX] = king;
8324         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8325         board[fromY][BOARD_RGHT-1] = EmptySquare;
8326     } else if (board[fromY][fromX] == king
8327         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8328                && toY == fromY && toX < fromX-1) {
8329         board[fromY][fromX] = EmptySquare;
8330         board[toY][toX] = king;
8331         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8332         board[fromY][BOARD_LEFT] = EmptySquare;
8333     } else if (fromY == 7 && fromX == 3
8334                && board[fromY][fromX] == BlackKing
8335                && toY == 7 && toX == 5) {
8336         board[fromY][fromX] = EmptySquare;
8337         board[toY][toX] = BlackKing;
8338         board[fromY][7] = EmptySquare;
8339         board[toY][4] = BlackRook;
8340     } else if (fromY == 7 && fromX == 3
8341                && board[fromY][fromX] == BlackKing
8342                && toY == 7 && toX == 1) {
8343         board[fromY][fromX] = EmptySquare;
8344         board[toY][toX] = BlackKing;
8345         board[fromY][0] = EmptySquare;
8346         board[toY][2] = BlackRook;
8347     } else if (board[fromY][fromX] == BlackPawn
8348                && toY < promoRank
8349                && gameInfo.variant != VariantXiangqi
8350                ) {
8351         /* black pawn promotion */
8352         board[toY][toX] = CharToPiece(ToLower(promoChar));
8353         if (board[toY][toX] == EmptySquare) {
8354             board[toY][toX] = BlackQueen;
8355         }
8356         if(gameInfo.variant==VariantBughouse ||
8357            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8358             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8359         board[fromY][fromX] = EmptySquare;
8360     } else if ((fromY == 3)
8361                && (toX != fromX)
8362                && gameInfo.variant != VariantXiangqi
8363                && gameInfo.variant != VariantBerolina
8364                && (board[fromY][fromX] == BlackPawn)
8365                && (board[toY][toX] == EmptySquare)) {
8366         board[fromY][fromX] = EmptySquare;
8367         board[toY][toX] = BlackPawn;
8368         captured = board[toY + 1][toX];
8369         board[toY + 1][toX] = EmptySquare;
8370     } else if ((fromY == 3)
8371                && (toX == fromX)
8372                && gameInfo.variant == VariantBerolina
8373                && (board[fromY][fromX] == BlackPawn)
8374                && (board[toY][toX] == EmptySquare)) {
8375         board[fromY][fromX] = EmptySquare;
8376         board[toY][toX] = BlackPawn;
8377         if(oldEP & EP_BEROLIN_A) {
8378                 captured = board[fromY][fromX-1];
8379                 board[fromY][fromX-1] = EmptySquare;
8380         }else{  captured = board[fromY][fromX+1];
8381                 board[fromY][fromX+1] = EmptySquare;
8382         }
8383     } else {
8384         board[toY][toX] = board[fromY][fromX];
8385         board[fromY][fromX] = EmptySquare;
8386     }
8387
8388     /* [HGM] now we promote for Shogi, if needed */
8389     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8390         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8391   }
8392
8393     if (gameInfo.holdingsWidth != 0) {
8394
8395       /* !!A lot more code needs to be written to support holdings  */
8396       /* [HGM] OK, so I have written it. Holdings are stored in the */
8397       /* penultimate board files, so they are automaticlly stored   */
8398       /* in the game history.                                       */
8399       if (fromY == DROP_RANK) {
8400         /* Delete from holdings, by decreasing count */
8401         /* and erasing image if necessary            */
8402         p = (int) fromX;
8403         if(p < (int) BlackPawn) { /* white drop */
8404              p -= (int)WhitePawn;
8405                  p = PieceToNumber((ChessSquare)p);
8406              if(p >= gameInfo.holdingsSize) p = 0;
8407              if(--board[p][BOARD_WIDTH-2] <= 0)
8408                   board[p][BOARD_WIDTH-1] = EmptySquare;
8409              if((int)board[p][BOARD_WIDTH-2] < 0)
8410                         board[p][BOARD_WIDTH-2] = 0;
8411         } else {                  /* black drop */
8412              p -= (int)BlackPawn;
8413                  p = PieceToNumber((ChessSquare)p);
8414              if(p >= gameInfo.holdingsSize) p = 0;
8415              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8416                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8417              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8418                         board[BOARD_HEIGHT-1-p][1] = 0;
8419         }
8420       }
8421       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8422           && gameInfo.variant != VariantBughouse        ) {
8423         /* [HGM] holdings: Add to holdings, if holdings exist */
8424         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8425                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8426                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8427         }
8428         p = (int) captured;
8429         if (p >= (int) BlackPawn) {
8430           p -= (int)BlackPawn;
8431           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8432                   /* in Shogi restore piece to its original  first */
8433                   captured = (ChessSquare) (DEMOTED captured);
8434                   p = DEMOTED p;
8435           }
8436           p = PieceToNumber((ChessSquare)p);
8437           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8438           board[p][BOARD_WIDTH-2]++;
8439           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8440         } else {
8441           p -= (int)WhitePawn;
8442           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8443                   captured = (ChessSquare) (DEMOTED captured);
8444                   p = DEMOTED p;
8445           }
8446           p = PieceToNumber((ChessSquare)p);
8447           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8448           board[BOARD_HEIGHT-1-p][1]++;
8449           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8450         }
8451       }
8452     } else if (gameInfo.variant == VariantAtomic) {
8453       if (captured != EmptySquare) {
8454         int y, x;
8455         for (y = toY-1; y <= toY+1; y++) {
8456           for (x = toX-1; x <= toX+1; x++) {
8457             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8458                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8459               board[y][x] = EmptySquare;
8460             }
8461           }
8462         }
8463         board[toY][toX] = EmptySquare;
8464       }
8465     }
8466     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8467         /* [HGM] Shogi promotions */
8468         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8469     }
8470
8471     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8472                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8473         // [HGM] superchess: take promotion piece out of holdings
8474         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8475         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8476             if(!--board[k][BOARD_WIDTH-2])
8477                 board[k][BOARD_WIDTH-1] = EmptySquare;
8478         } else {
8479             if(!--board[BOARD_HEIGHT-1-k][1])
8480                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8481         }
8482     }
8483
8484 }
8485
8486 /* Updates forwardMostMove */
8487 void
8488 MakeMove(fromX, fromY, toX, toY, promoChar)
8489      int fromX, fromY, toX, toY;
8490      int promoChar;
8491 {
8492 //    forwardMostMove++; // [HGM] bare: moved downstream
8493
8494     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8495         int timeLeft; static int lastLoadFlag=0; int king, piece;
8496         piece = boards[forwardMostMove][fromY][fromX];
8497         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8498         if(gameInfo.variant == VariantKnightmate)
8499             king += (int) WhiteUnicorn - (int) WhiteKing;
8500         if(forwardMostMove == 0) {
8501             if(blackPlaysFirst) 
8502                 fprintf(serverMoves, "%s;", second.tidy);
8503             fprintf(serverMoves, "%s;", first.tidy);
8504             if(!blackPlaysFirst) 
8505                 fprintf(serverMoves, "%s;", second.tidy);
8506         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8507         lastLoadFlag = loadFlag;
8508         // print base move
8509         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8510         // print castling suffix
8511         if( toY == fromY && piece == king ) {
8512             if(toX-fromX > 1)
8513                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8514             if(fromX-toX >1)
8515                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8516         }
8517         // e.p. suffix
8518         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8519              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8520              boards[forwardMostMove][toY][toX] == EmptySquare
8521              && fromX != toX )
8522                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8523         // promotion suffix
8524         if(promoChar != NULLCHAR)
8525                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8526         if(!loadFlag) {
8527             fprintf(serverMoves, "/%d/%d",
8528                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8529             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8530             else                      timeLeft = blackTimeRemaining/1000;
8531             fprintf(serverMoves, "/%d", timeLeft);
8532         }
8533         fflush(serverMoves);
8534     }
8535
8536     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8537       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8538                         0, 1);
8539       return;
8540     }
8541     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8542     if (commentList[forwardMostMove+1] != NULL) {
8543         free(commentList[forwardMostMove+1]);
8544         commentList[forwardMostMove+1] = NULL;
8545     }
8546     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8547     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8548     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8549     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8550     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8551     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8552     gameInfo.result = GameUnfinished;
8553     if (gameInfo.resultDetails != NULL) {
8554         free(gameInfo.resultDetails);
8555         gameInfo.resultDetails = NULL;
8556     }
8557     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8558                               moveList[forwardMostMove - 1]);
8559     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8560                              PosFlags(forwardMostMove - 1),
8561                              fromY, fromX, toY, toX, promoChar,
8562                              parseList[forwardMostMove - 1]);
8563     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8564       case MT_NONE:
8565       case MT_STALEMATE:
8566       default:
8567         break;
8568       case MT_CHECK:
8569         if(gameInfo.variant != VariantShogi)
8570             strcat(parseList[forwardMostMove - 1], "+");
8571         break;
8572       case MT_CHECKMATE:
8573       case MT_STAINMATE:
8574         strcat(parseList[forwardMostMove - 1], "#");
8575         break;
8576     }
8577     if (appData.debugMode) {
8578         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8579     }
8580
8581 }
8582
8583 /* Updates currentMove if not pausing */
8584 void
8585 ShowMove(fromX, fromY, toX, toY)
8586 {
8587     int instant = (gameMode == PlayFromGameFile) ?
8588         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8589     if(appData.noGUI) return;
8590     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8591         if (!instant) {
8592             if (forwardMostMove == currentMove + 1) {
8593                 AnimateMove(boards[forwardMostMove - 1],
8594                             fromX, fromY, toX, toY);
8595             }
8596             if (appData.highlightLastMove) {
8597                 SetHighlights(fromX, fromY, toX, toY);
8598             }
8599         }
8600         currentMove = forwardMostMove;
8601     }
8602
8603     if (instant) return;
8604
8605     DisplayMove(currentMove - 1);
8606     DrawPosition(FALSE, boards[currentMove]);
8607     DisplayBothClocks();
8608     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8609 }
8610
8611 void SendEgtPath(ChessProgramState *cps)
8612 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8613         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8614
8615         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8616
8617         while(*p) {
8618             char c, *q = name+1, *r, *s;
8619
8620             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8621             while(*p && *p != ',') *q++ = *p++;
8622             *q++ = ':'; *q = 0;
8623             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8624                 strcmp(name, ",nalimov:") == 0 ) {
8625                 // take nalimov path from the menu-changeable option first, if it is defined
8626                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8627                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8628             } else
8629             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8630                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8631                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8632                 s = r = StrStr(s, ":") + 1; // beginning of path info
8633                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8634                 c = *r; *r = 0;             // temporarily null-terminate path info
8635                     *--q = 0;               // strip of trailig ':' from name
8636                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8637                 *r = c;
8638                 SendToProgram(buf,cps);     // send egtbpath command for this format
8639             }
8640             if(*p == ',') p++; // read away comma to position for next format name
8641         }
8642 }
8643
8644 void
8645 InitChessProgram(cps, setup)
8646      ChessProgramState *cps;
8647      int setup; /* [HGM] needed to setup FRC opening position */
8648 {
8649     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8650     if (appData.noChessProgram) return;
8651     hintRequested = FALSE;
8652     bookRequested = FALSE;
8653
8654     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8655     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8656     if(cps->memSize) { /* [HGM] memory */
8657         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8658         SendToProgram(buf, cps);
8659     }
8660     SendEgtPath(cps); /* [HGM] EGT */
8661     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8662         sprintf(buf, "cores %d\n", appData.smpCores);
8663         SendToProgram(buf, cps);
8664     }
8665
8666     SendToProgram(cps->initString, cps);
8667     if (gameInfo.variant != VariantNormal &&
8668         gameInfo.variant != VariantLoadable
8669         /* [HGM] also send variant if board size non-standard */
8670         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8671                                             ) {
8672       char *v = VariantName(gameInfo.variant);
8673       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8674         /* [HGM] in protocol 1 we have to assume all variants valid */
8675         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8676         DisplayFatalError(buf, 0, 1);
8677         return;
8678       }
8679
8680       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8681       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8682       if( gameInfo.variant == VariantXiangqi )
8683            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8684       if( gameInfo.variant == VariantShogi )
8685            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8686       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8687            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8688       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8689                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8690            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8691       if( gameInfo.variant == VariantCourier )
8692            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8693       if( gameInfo.variant == VariantSuper )
8694            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8695       if( gameInfo.variant == VariantGreat )
8696            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8697
8698       if(overruled) {
8699            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8700                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8701            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8702            if(StrStr(cps->variants, b) == NULL) { 
8703                // specific sized variant not known, check if general sizing allowed
8704                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8705                    if(StrStr(cps->variants, "boardsize") == NULL) {
8706                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8707                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8708                        DisplayFatalError(buf, 0, 1);
8709                        return;
8710                    }
8711                    /* [HGM] here we really should compare with the maximum supported board size */
8712                }
8713            }
8714       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8715       sprintf(buf, "variant %s\n", b);
8716       SendToProgram(buf, cps);
8717     }
8718     currentlyInitializedVariant = gameInfo.variant;
8719
8720     /* [HGM] send opening position in FRC to first engine */
8721     if(setup) {
8722           SendToProgram("force\n", cps);
8723           SendBoard(cps, 0);
8724           /* engine is now in force mode! Set flag to wake it up after first move. */
8725           setboardSpoiledMachineBlack = 1;
8726     }
8727
8728     if (cps->sendICS) {
8729       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8730       SendToProgram(buf, cps);
8731     }
8732     cps->maybeThinking = FALSE;
8733     cps->offeredDraw = 0;
8734     if (!appData.icsActive) {
8735         SendTimeControl(cps, movesPerSession, timeControl,
8736                         timeIncrement, appData.searchDepth,
8737                         searchTime);
8738     }
8739     if (appData.showThinking 
8740         // [HGM] thinking: four options require thinking output to be sent
8741         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8742                                 ) {
8743         SendToProgram("post\n", cps);
8744     }
8745     SendToProgram("hard\n", cps);
8746     if (!appData.ponderNextMove) {
8747         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8748            it without being sure what state we are in first.  "hard"
8749            is not a toggle, so that one is OK.
8750          */
8751         SendToProgram("easy\n", cps);
8752     }
8753     if (cps->usePing) {
8754       sprintf(buf, "ping %d\n", ++cps->lastPing);
8755       SendToProgram(buf, cps);
8756     }
8757     cps->initDone = TRUE;
8758 }   
8759
8760
8761 void
8762 StartChessProgram(cps)
8763      ChessProgramState *cps;
8764 {
8765     char buf[MSG_SIZ];
8766     int err;
8767
8768     if (appData.noChessProgram) return;
8769     cps->initDone = FALSE;
8770
8771     if (strcmp(cps->host, "localhost") == 0) {
8772         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8773     } else if (*appData.remoteShell == NULLCHAR) {
8774         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8775     } else {
8776         if (*appData.remoteUser == NULLCHAR) {
8777           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8778                     cps->program);
8779         } else {
8780           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8781                     cps->host, appData.remoteUser, cps->program);
8782         }
8783         err = StartChildProcess(buf, "", &cps->pr);
8784     }
8785     
8786     if (err != 0) {
8787         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8788         DisplayFatalError(buf, err, 1);
8789         cps->pr = NoProc;
8790         cps->isr = NULL;
8791         return;
8792     }
8793     
8794     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8795     if (cps->protocolVersion > 1) {
8796       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8797       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8798       cps->comboCnt = 0;  //                and values of combo boxes
8799       SendToProgram(buf, cps);
8800     } else {
8801       SendToProgram("xboard\n", cps);
8802     }
8803 }
8804
8805
8806 void
8807 TwoMachinesEventIfReady P((void))
8808 {
8809   if (first.lastPing != first.lastPong) {
8810     DisplayMessage("", _("Waiting for first chess program"));
8811     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8812     return;
8813   }
8814   if (second.lastPing != second.lastPong) {
8815     DisplayMessage("", _("Waiting for second chess program"));
8816     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8817     return;
8818   }
8819   ThawUI();
8820   TwoMachinesEvent();
8821 }
8822
8823 void
8824 NextMatchGame P((void))
8825 {
8826     int index; /* [HGM] autoinc: step load index during match */
8827     Reset(FALSE, TRUE);
8828     if (*appData.loadGameFile != NULLCHAR) {
8829         index = appData.loadGameIndex;
8830         if(index < 0) { // [HGM] autoinc
8831             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8832             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8833         } 
8834         LoadGameFromFile(appData.loadGameFile,
8835                          index,
8836                          appData.loadGameFile, FALSE);
8837     } else if (*appData.loadPositionFile != NULLCHAR) {
8838         index = appData.loadPositionIndex;
8839         if(index < 0) { // [HGM] autoinc
8840             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8841             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8842         } 
8843         LoadPositionFromFile(appData.loadPositionFile,
8844                              index,
8845                              appData.loadPositionFile);
8846     }
8847     TwoMachinesEventIfReady();
8848 }
8849
8850 void UserAdjudicationEvent( int result )
8851 {
8852     ChessMove gameResult = GameIsDrawn;
8853
8854     if( result > 0 ) {
8855         gameResult = WhiteWins;
8856     }
8857     else if( result < 0 ) {
8858         gameResult = BlackWins;
8859     }
8860
8861     if( gameMode == TwoMachinesPlay ) {
8862         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8863     }
8864 }
8865
8866
8867 // [HGM] save: calculate checksum of game to make games easily identifiable
8868 int StringCheckSum(char *s)
8869 {
8870         int i = 0;
8871         if(s==NULL) return 0;
8872         while(*s) i = i*259 + *s++;
8873         return i;
8874 }
8875
8876 int GameCheckSum()
8877 {
8878         int i, sum=0;
8879         for(i=backwardMostMove; i<forwardMostMove; i++) {
8880                 sum += pvInfoList[i].depth;
8881                 sum += StringCheckSum(parseList[i]);
8882                 sum += StringCheckSum(commentList[i]);
8883                 sum *= 261;
8884         }
8885         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8886         return sum + StringCheckSum(commentList[i]);
8887 } // end of save patch
8888
8889 void
8890 GameEnds(result, resultDetails, whosays)
8891      ChessMove result;
8892      char *resultDetails;
8893      int whosays;
8894 {
8895     GameMode nextGameMode;
8896     int isIcsGame;
8897     char buf[MSG_SIZ];
8898
8899     if(endingGame) return; /* [HGM] crash: forbid recursion */
8900     endingGame = 1;
8901     if(twoBoards) { twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); } // [HGM] dual
8902
8903     if (appData.debugMode) {
8904       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8905               result, resultDetails ? resultDetails : "(null)", whosays);
8906     }
8907
8908     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8909
8910     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8911         /* If we are playing on ICS, the server decides when the
8912            game is over, but the engine can offer to draw, claim 
8913            a draw, or resign. 
8914          */
8915 #if ZIPPY
8916         if (appData.zippyPlay && first.initDone) {
8917             if (result == GameIsDrawn) {
8918                 /* In case draw still needs to be claimed */
8919                 SendToICS(ics_prefix);
8920                 SendToICS("draw\n");
8921             } else if (StrCaseStr(resultDetails, "resign")) {
8922                 SendToICS(ics_prefix);
8923                 SendToICS("resign\n");
8924             }
8925         }
8926 #endif
8927         endingGame = 0; /* [HGM] crash */
8928         return;
8929     }
8930
8931     /* If we're loading the game from a file, stop */
8932     if (whosays == GE_FILE) {
8933       (void) StopLoadGameTimer();
8934       gameFileFP = NULL;
8935     }
8936
8937     /* Cancel draw offers */
8938     first.offeredDraw = second.offeredDraw = 0;
8939
8940     /* If this is an ICS game, only ICS can really say it's done;
8941        if not, anyone can. */
8942     isIcsGame = (gameMode == IcsPlayingWhite || 
8943                  gameMode == IcsPlayingBlack || 
8944                  gameMode == IcsObserving    || 
8945                  gameMode == IcsExamining);
8946
8947     if (!isIcsGame || whosays == GE_ICS) {
8948         /* OK -- not an ICS game, or ICS said it was done */
8949         StopClocks();
8950         if (!isIcsGame && !appData.noChessProgram) 
8951           SetUserThinkingEnables();
8952     
8953         /* [HGM] if a machine claims the game end we verify this claim */
8954         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8955             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8956                 char claimer;
8957                 ChessMove trueResult = (ChessMove) -1;
8958
8959                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8960                                             first.twoMachinesColor[0] :
8961                                             second.twoMachinesColor[0] ;
8962
8963                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8964                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8965                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8966                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8967                 } else
8968                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8969                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8970                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8971                 } else
8972                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8973                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8974                 }
8975
8976                 // now verify win claims, but not in drop games, as we don't understand those yet
8977                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8978                                                  || gameInfo.variant == VariantGreat) &&
8979                     (result == WhiteWins && claimer == 'w' ||
8980                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8981                       if (appData.debugMode) {
8982                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8983                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8984                       }
8985                       if(result != trueResult) {
8986                               sprintf(buf, "False win claim: '%s'", resultDetails);
8987                               result = claimer == 'w' ? BlackWins : WhiteWins;
8988                               resultDetails = buf;
8989                       }
8990                 } else
8991                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8992                     && (forwardMostMove <= backwardMostMove ||
8993                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8994                         (claimer=='b')==(forwardMostMove&1))
8995                                                                                   ) {
8996                       /* [HGM] verify: draws that were not flagged are false claims */
8997                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8998                       result = claimer == 'w' ? BlackWins : WhiteWins;
8999                       resultDetails = buf;
9000                 }
9001                 /* (Claiming a loss is accepted no questions asked!) */
9002             }
9003             /* [HGM] bare: don't allow bare King to win */
9004             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9005                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9006                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9007                && result != GameIsDrawn)
9008             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9009                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9010                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9011                         if(p >= 0 && p <= (int)WhiteKing) k++;
9012                 }
9013                 if (appData.debugMode) {
9014                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9015                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9016                 }
9017                 if(k <= 1) {
9018                         result = GameIsDrawn;
9019                         sprintf(buf, "%s but bare king", resultDetails);
9020                         resultDetails = buf;
9021                 }
9022             }
9023         }
9024
9025
9026         if(serverMoves != NULL && !loadFlag) { char c = '=';
9027             if(result==WhiteWins) c = '+';
9028             if(result==BlackWins) c = '-';
9029             if(resultDetails != NULL)
9030                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9031         }
9032         if (resultDetails != NULL) {
9033             gameInfo.result = result;
9034             gameInfo.resultDetails = StrSave(resultDetails);
9035
9036             /* display last move only if game was not loaded from file */
9037             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9038                 DisplayMove(currentMove - 1);
9039     
9040             if (forwardMostMove != 0) {
9041                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9042                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9043                                                                 ) {
9044                     if (*appData.saveGameFile != NULLCHAR) {
9045                         SaveGameToFile(appData.saveGameFile, TRUE);
9046                     } else if (appData.autoSaveGames) {
9047                         AutoSaveGame();
9048                     }
9049                     if (*appData.savePositionFile != NULLCHAR) {
9050                         SavePositionToFile(appData.savePositionFile);
9051                     }
9052                 }
9053             }
9054
9055             /* Tell program how game ended in case it is learning */
9056             /* [HGM] Moved this to after saving the PGN, just in case */
9057             /* engine died and we got here through time loss. In that */
9058             /* case we will get a fatal error writing the pipe, which */
9059             /* would otherwise lose us the PGN.                       */
9060             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9061             /* output during GameEnds should never be fatal anymore   */
9062             if (gameMode == MachinePlaysWhite ||
9063                 gameMode == MachinePlaysBlack ||
9064                 gameMode == TwoMachinesPlay ||
9065                 gameMode == IcsPlayingWhite ||
9066                 gameMode == IcsPlayingBlack ||
9067                 gameMode == BeginningOfGame) {
9068                 char buf[MSG_SIZ];
9069                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9070                         resultDetails);
9071                 if (first.pr != NoProc) {
9072                     SendToProgram(buf, &first);
9073                 }
9074                 if (second.pr != NoProc &&
9075                     gameMode == TwoMachinesPlay) {
9076                     SendToProgram(buf, &second);
9077                 }
9078             }
9079         }
9080
9081         if (appData.icsActive) {
9082             if (appData.quietPlay &&
9083                 (gameMode == IcsPlayingWhite ||
9084                  gameMode == IcsPlayingBlack)) {
9085                 SendToICS(ics_prefix);
9086                 SendToICS("set shout 1\n");
9087             }
9088             nextGameMode = IcsIdle;
9089             ics_user_moved = FALSE;
9090             /* clean up premove.  It's ugly when the game has ended and the
9091              * premove highlights are still on the board.
9092              */
9093             if (gotPremove) {
9094               gotPremove = FALSE;
9095               ClearPremoveHighlights();
9096               DrawPosition(FALSE, boards[currentMove]);
9097             }
9098             if (whosays == GE_ICS) {
9099                 switch (result) {
9100                 case WhiteWins:
9101                     if (gameMode == IcsPlayingWhite)
9102                         PlayIcsWinSound();
9103                     else if(gameMode == IcsPlayingBlack)
9104                         PlayIcsLossSound();
9105                     break;
9106                 case BlackWins:
9107                     if (gameMode == IcsPlayingBlack)
9108                         PlayIcsWinSound();
9109                     else if(gameMode == IcsPlayingWhite)
9110                         PlayIcsLossSound();
9111                     break;
9112                 case GameIsDrawn:
9113                     PlayIcsDrawSound();
9114                     break;
9115                 default:
9116                     PlayIcsUnfinishedSound();
9117                 }
9118             }
9119         } else if (gameMode == EditGame ||
9120                    gameMode == PlayFromGameFile || 
9121                    gameMode == AnalyzeMode || 
9122                    gameMode == AnalyzeFile) {
9123             nextGameMode = gameMode;
9124         } else {
9125             nextGameMode = EndOfGame;
9126         }
9127         pausing = FALSE;
9128         ModeHighlight();
9129     } else {
9130         nextGameMode = gameMode;
9131     }
9132
9133     if (appData.noChessProgram) {
9134         gameMode = nextGameMode;
9135         ModeHighlight();
9136         endingGame = 0; /* [HGM] crash */
9137         return;
9138     }
9139
9140     if (first.reuse) {
9141         /* Put first chess program into idle state */
9142         if (first.pr != NoProc &&
9143             (gameMode == MachinePlaysWhite ||
9144              gameMode == MachinePlaysBlack ||
9145              gameMode == TwoMachinesPlay ||
9146              gameMode == IcsPlayingWhite ||
9147              gameMode == IcsPlayingBlack ||
9148              gameMode == BeginningOfGame)) {
9149             SendToProgram("force\n", &first);
9150             if (first.usePing) {
9151               char buf[MSG_SIZ];
9152               sprintf(buf, "ping %d\n", ++first.lastPing);
9153               SendToProgram(buf, &first);
9154             }
9155         }
9156     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9157         /* Kill off first chess program */
9158         if (first.isr != NULL)
9159           RemoveInputSource(first.isr);
9160         first.isr = NULL;
9161     
9162         if (first.pr != NoProc) {
9163             ExitAnalyzeMode();
9164             DoSleep( appData.delayBeforeQuit );
9165             SendToProgram("quit\n", &first);
9166             DoSleep( appData.delayAfterQuit );
9167             DestroyChildProcess(first.pr, first.useSigterm);
9168         }
9169         first.pr = NoProc;
9170     }
9171     if (second.reuse) {
9172         /* Put second chess program into idle state */
9173         if (second.pr != NoProc &&
9174             gameMode == TwoMachinesPlay) {
9175             SendToProgram("force\n", &second);
9176             if (second.usePing) {
9177               char buf[MSG_SIZ];
9178               sprintf(buf, "ping %d\n", ++second.lastPing);
9179               SendToProgram(buf, &second);
9180             }
9181         }
9182     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9183         /* Kill off second chess program */
9184         if (second.isr != NULL)
9185           RemoveInputSource(second.isr);
9186         second.isr = NULL;
9187     
9188         if (second.pr != NoProc) {
9189             DoSleep( appData.delayBeforeQuit );
9190             SendToProgram("quit\n", &second);
9191             DoSleep( appData.delayAfterQuit );
9192             DestroyChildProcess(second.pr, second.useSigterm);
9193         }
9194         second.pr = NoProc;
9195     }
9196
9197     if (matchMode && gameMode == TwoMachinesPlay) {
9198         switch (result) {
9199         case WhiteWins:
9200           if (first.twoMachinesColor[0] == 'w') {
9201             first.matchWins++;
9202           } else {
9203             second.matchWins++;
9204           }
9205           break;
9206         case BlackWins:
9207           if (first.twoMachinesColor[0] == 'b') {
9208             first.matchWins++;
9209           } else {
9210             second.matchWins++;
9211           }
9212           break;
9213         default:
9214           break;
9215         }
9216         if (matchGame < appData.matchGames) {
9217             char *tmp;
9218             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9219                 tmp = first.twoMachinesColor;
9220                 first.twoMachinesColor = second.twoMachinesColor;
9221                 second.twoMachinesColor = tmp;
9222             }
9223             gameMode = nextGameMode;
9224             matchGame++;
9225             if(appData.matchPause>10000 || appData.matchPause<10)
9226                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9227             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9228             endingGame = 0; /* [HGM] crash */
9229             return;
9230         } else {
9231             char buf[MSG_SIZ];
9232             gameMode = nextGameMode;
9233             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9234                     first.tidy, second.tidy,
9235                     first.matchWins, second.matchWins,
9236                     appData.matchGames - (first.matchWins + second.matchWins));
9237             DisplayFatalError(buf, 0, 0);
9238         }
9239     }
9240     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9241         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9242       ExitAnalyzeMode();
9243     gameMode = nextGameMode;
9244     ModeHighlight();
9245     endingGame = 0;  /* [HGM] crash */
9246 }
9247
9248 /* Assumes program was just initialized (initString sent).
9249    Leaves program in force mode. */
9250 void
9251 FeedMovesToProgram(cps, upto) 
9252      ChessProgramState *cps;
9253      int upto;
9254 {
9255     int i;
9256     
9257     if (appData.debugMode)
9258       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9259               startedFromSetupPosition ? "position and " : "",
9260               backwardMostMove, upto, cps->which);
9261     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9262         // [HGM] variantswitch: make engine aware of new variant
9263         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9264                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9265         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9266         SendToProgram(buf, cps);
9267         currentlyInitializedVariant = gameInfo.variant;
9268     }
9269     SendToProgram("force\n", cps);
9270     if (startedFromSetupPosition) {
9271         SendBoard(cps, backwardMostMove);
9272     if (appData.debugMode) {
9273         fprintf(debugFP, "feedMoves\n");
9274     }
9275     }
9276     for (i = backwardMostMove; i < upto; i++) {
9277         SendMoveToProgram(i, cps);
9278     }
9279 }
9280
9281
9282 void
9283 ResurrectChessProgram()
9284 {
9285      /* The chess program may have exited.
9286         If so, restart it and feed it all the moves made so far. */
9287
9288     if (appData.noChessProgram || first.pr != NoProc) return;
9289     
9290     StartChessProgram(&first);
9291     InitChessProgram(&first, FALSE);
9292     FeedMovesToProgram(&first, currentMove);
9293
9294     if (!first.sendTime) {
9295         /* can't tell gnuchess what its clock should read,
9296            so we bow to its notion. */
9297         ResetClocks();
9298         timeRemaining[0][currentMove] = whiteTimeRemaining;
9299         timeRemaining[1][currentMove] = blackTimeRemaining;
9300     }
9301
9302     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9303                 appData.icsEngineAnalyze) && first.analysisSupport) {
9304       SendToProgram("analyze\n", &first);
9305       first.analyzing = TRUE;
9306     }
9307 }
9308
9309 /*
9310  * Button procedures
9311  */
9312 void
9313 Reset(redraw, init)
9314      int redraw, init;
9315 {
9316     int i;
9317
9318     if (appData.debugMode) {
9319         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9320                 redraw, init, gameMode);
9321     }
9322     CleanupTail(); // [HGM] vari: delete any stored variations
9323     pausing = pauseExamInvalid = FALSE;
9324     startedFromSetupPosition = blackPlaysFirst = FALSE;
9325     firstMove = TRUE;
9326     whiteFlag = blackFlag = FALSE;
9327     userOfferedDraw = FALSE;
9328     hintRequested = bookRequested = FALSE;
9329     first.maybeThinking = FALSE;
9330     second.maybeThinking = FALSE;
9331     first.bookSuspend = FALSE; // [HGM] book
9332     second.bookSuspend = FALSE;
9333     thinkOutput[0] = NULLCHAR;
9334     lastHint[0] = NULLCHAR;
9335     ClearGameInfo(&gameInfo);
9336     gameInfo.variant = StringToVariant(appData.variant);
9337     ics_user_moved = ics_clock_paused = FALSE;
9338     ics_getting_history = H_FALSE;
9339     ics_gamenum = -1;
9340     white_holding[0] = black_holding[0] = NULLCHAR;
9341     ClearProgramStats();
9342     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9343     
9344     ResetFrontEnd();
9345     ClearHighlights();
9346     flipView = appData.flipView;
9347     ClearPremoveHighlights();
9348     gotPremove = FALSE;
9349     alarmSounded = FALSE;
9350
9351     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9352     if(appData.serverMovesName != NULL) {
9353         /* [HGM] prepare to make moves file for broadcasting */
9354         clock_t t = clock();
9355         if(serverMoves != NULL) fclose(serverMoves);
9356         serverMoves = fopen(appData.serverMovesName, "r");
9357         if(serverMoves != NULL) {
9358             fclose(serverMoves);
9359             /* delay 15 sec before overwriting, so all clients can see end */
9360             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9361         }
9362         serverMoves = fopen(appData.serverMovesName, "w");
9363     }
9364
9365     ExitAnalyzeMode();
9366     gameMode = BeginningOfGame;
9367     ModeHighlight();
9368     if(appData.icsActive) gameInfo.variant = VariantNormal;
9369     currentMove = forwardMostMove = backwardMostMove = 0;
9370     InitPosition(redraw);
9371     for (i = 0; i < MAX_MOVES; i++) {
9372         if (commentList[i] != NULL) {
9373             free(commentList[i]);
9374             commentList[i] = NULL;
9375         }
9376     }
9377     ResetClocks();
9378     timeRemaining[0][0] = whiteTimeRemaining;
9379     timeRemaining[1][0] = blackTimeRemaining;
9380     if (first.pr == NULL) {
9381         StartChessProgram(&first);
9382     }
9383     if (init) {
9384             InitChessProgram(&first, startedFromSetupPosition);
9385     }
9386     DisplayTitle("");
9387     DisplayMessage("", "");
9388     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9389     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9390 }
9391
9392 void
9393 AutoPlayGameLoop()
9394 {
9395     for (;;) {
9396         if (!AutoPlayOneMove())
9397           return;
9398         if (matchMode || appData.timeDelay == 0)
9399           continue;
9400         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9401           return;
9402         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9403         break;
9404     }
9405 }
9406
9407
9408 int
9409 AutoPlayOneMove()
9410 {
9411     int fromX, fromY, toX, toY;
9412
9413     if (appData.debugMode) {
9414       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9415     }
9416
9417     if (gameMode != PlayFromGameFile)
9418       return FALSE;
9419
9420     if (currentMove >= forwardMostMove) {
9421       gameMode = EditGame;
9422       ModeHighlight();
9423
9424       /* [AS] Clear current move marker at the end of a game */
9425       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9426
9427       return FALSE;
9428     }
9429     
9430     toX = moveList[currentMove][2] - AAA;
9431     toY = moveList[currentMove][3] - ONE;
9432
9433     if (moveList[currentMove][1] == '@') {
9434         if (appData.highlightLastMove) {
9435             SetHighlights(-1, -1, toX, toY);
9436         }
9437     } else {
9438         fromX = moveList[currentMove][0] - AAA;
9439         fromY = moveList[currentMove][1] - ONE;
9440
9441         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9442
9443         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9444
9445         if (appData.highlightLastMove) {
9446             SetHighlights(fromX, fromY, toX, toY);
9447         }
9448     }
9449     DisplayMove(currentMove);
9450     SendMoveToProgram(currentMove++, &first);
9451     DisplayBothClocks();
9452     DrawPosition(FALSE, boards[currentMove]);
9453     // [HGM] PV info: always display, routine tests if empty
9454     DisplayComment(currentMove - 1, commentList[currentMove]);
9455     return TRUE;
9456 }
9457
9458
9459 int
9460 LoadGameOneMove(readAhead)
9461      ChessMove readAhead;
9462 {
9463     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9464     char promoChar = NULLCHAR;
9465     ChessMove moveType;
9466     char move[MSG_SIZ];
9467     char *p, *q;
9468     
9469     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9470         gameMode != AnalyzeMode && gameMode != Training) {
9471         gameFileFP = NULL;
9472         return FALSE;
9473     }
9474     
9475     yyboardindex = forwardMostMove;
9476     if (readAhead != (ChessMove)0) {
9477       moveType = readAhead;
9478     } else {
9479       if (gameFileFP == NULL)
9480           return FALSE;
9481       moveType = (ChessMove) yylex();
9482     }
9483     
9484     done = FALSE;
9485     switch (moveType) {
9486       case Comment:
9487         if (appData.debugMode) 
9488           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9489         p = yy_text;
9490
9491         /* append the comment but don't display it */
9492         AppendComment(currentMove, p, FALSE);
9493         return TRUE;
9494
9495       case WhiteCapturesEnPassant:
9496       case BlackCapturesEnPassant:
9497       case WhitePromotionChancellor:
9498       case BlackPromotionChancellor:
9499       case WhitePromotionArchbishop:
9500       case BlackPromotionArchbishop:
9501       case WhitePromotionCentaur:
9502       case BlackPromotionCentaur:
9503       case WhitePromotionQueen:
9504       case BlackPromotionQueen:
9505       case WhitePromotionRook:
9506       case BlackPromotionRook:
9507       case WhitePromotionBishop:
9508       case BlackPromotionBishop:
9509       case WhitePromotionKnight:
9510       case BlackPromotionKnight:
9511       case WhitePromotionKing:
9512       case BlackPromotionKing:
9513       case NormalMove:
9514       case WhiteKingSideCastle:
9515       case WhiteQueenSideCastle:
9516       case BlackKingSideCastle:
9517       case BlackQueenSideCastle:
9518       case WhiteKingSideCastleWild:
9519       case WhiteQueenSideCastleWild:
9520       case BlackKingSideCastleWild:
9521       case BlackQueenSideCastleWild:
9522       /* PUSH Fabien */
9523       case WhiteHSideCastleFR:
9524       case WhiteASideCastleFR:
9525       case BlackHSideCastleFR:
9526       case BlackASideCastleFR:
9527       /* POP Fabien */
9528         if (appData.debugMode)
9529           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9530         fromX = currentMoveString[0] - AAA;
9531         fromY = currentMoveString[1] - ONE;
9532         toX = currentMoveString[2] - AAA;
9533         toY = currentMoveString[3] - ONE;
9534         promoChar = currentMoveString[4];
9535         break;
9536
9537       case WhiteDrop:
9538       case BlackDrop:
9539         if (appData.debugMode)
9540           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9541         fromX = moveType == WhiteDrop ?
9542           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9543         (int) CharToPiece(ToLower(currentMoveString[0]));
9544         fromY = DROP_RANK;
9545         toX = currentMoveString[2] - AAA;
9546         toY = currentMoveString[3] - ONE;
9547         break;
9548
9549       case WhiteWins:
9550       case BlackWins:
9551       case GameIsDrawn:
9552       case GameUnfinished:
9553         if (appData.debugMode)
9554           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9555         p = strchr(yy_text, '{');
9556         if (p == NULL) p = strchr(yy_text, '(');
9557         if (p == NULL) {
9558             p = yy_text;
9559             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9560         } else {
9561             q = strchr(p, *p == '{' ? '}' : ')');
9562             if (q != NULL) *q = NULLCHAR;
9563             p++;
9564         }
9565         GameEnds(moveType, p, GE_FILE);
9566         done = TRUE;
9567         if (cmailMsgLoaded) {
9568             ClearHighlights();
9569             flipView = WhiteOnMove(currentMove);
9570             if (moveType == GameUnfinished) flipView = !flipView;
9571             if (appData.debugMode)
9572               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9573         }
9574         break;
9575
9576       case (ChessMove) 0:       /* end of file */
9577         if (appData.debugMode)
9578           fprintf(debugFP, "Parser hit end of file\n");
9579         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9580           case MT_NONE:
9581           case MT_CHECK:
9582             break;
9583           case MT_CHECKMATE:
9584           case MT_STAINMATE:
9585             if (WhiteOnMove(currentMove)) {
9586                 GameEnds(BlackWins, "Black mates", GE_FILE);
9587             } else {
9588                 GameEnds(WhiteWins, "White mates", GE_FILE);
9589             }
9590             break;
9591           case MT_STALEMATE:
9592             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9593             break;
9594         }
9595         done = TRUE;
9596         break;
9597
9598       case MoveNumberOne:
9599         if (lastLoadGameStart == GNUChessGame) {
9600             /* GNUChessGames have numbers, but they aren't move numbers */
9601             if (appData.debugMode)
9602               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9603                       yy_text, (int) moveType);
9604             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9605         }
9606         /* else fall thru */
9607
9608       case XBoardGame:
9609       case GNUChessGame:
9610       case PGNTag:
9611         /* Reached start of next game in file */
9612         if (appData.debugMode)
9613           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9614         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9615           case MT_NONE:
9616           case MT_CHECK:
9617             break;
9618           case MT_CHECKMATE:
9619           case MT_STAINMATE:
9620             if (WhiteOnMove(currentMove)) {
9621                 GameEnds(BlackWins, "Black mates", GE_FILE);
9622             } else {
9623                 GameEnds(WhiteWins, "White mates", GE_FILE);
9624             }
9625             break;
9626           case MT_STALEMATE:
9627             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9628             break;
9629         }
9630         done = TRUE;
9631         break;
9632
9633       case PositionDiagram:     /* should not happen; ignore */
9634       case ElapsedTime:         /* ignore */
9635       case NAG:                 /* ignore */
9636         if (appData.debugMode)
9637           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9638                   yy_text, (int) moveType);
9639         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9640
9641       case IllegalMove:
9642         if (appData.testLegality) {
9643             if (appData.debugMode)
9644               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9645             sprintf(move, _("Illegal move: %d.%s%s"),
9646                     (forwardMostMove / 2) + 1,
9647                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9648             DisplayError(move, 0);
9649             done = TRUE;
9650         } else {
9651             if (appData.debugMode)
9652               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9653                       yy_text, currentMoveString);
9654             fromX = currentMoveString[0] - AAA;
9655             fromY = currentMoveString[1] - ONE;
9656             toX = currentMoveString[2] - AAA;
9657             toY = currentMoveString[3] - ONE;
9658             promoChar = currentMoveString[4];
9659         }
9660         break;
9661
9662       case AmbiguousMove:
9663         if (appData.debugMode)
9664           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9665         sprintf(move, _("Ambiguous move: %d.%s%s"),
9666                 (forwardMostMove / 2) + 1,
9667                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9668         DisplayError(move, 0);
9669         done = TRUE;
9670         break;
9671
9672       default:
9673       case ImpossibleMove:
9674         if (appData.debugMode)
9675           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9676         sprintf(move, _("Illegal move: %d.%s%s"),
9677                 (forwardMostMove / 2) + 1,
9678                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9679         DisplayError(move, 0);
9680         done = TRUE;
9681         break;
9682     }
9683
9684     if (done) {
9685         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9686             DrawPosition(FALSE, boards[currentMove]);
9687             DisplayBothClocks();
9688             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9689               DisplayComment(currentMove - 1, commentList[currentMove]);
9690         }
9691         (void) StopLoadGameTimer();
9692         gameFileFP = NULL;
9693         cmailOldMove = forwardMostMove;
9694         return FALSE;
9695     } else {
9696         /* currentMoveString is set as a side-effect of yylex */
9697         strcat(currentMoveString, "\n");
9698         strcpy(moveList[forwardMostMove], currentMoveString);
9699         
9700         thinkOutput[0] = NULLCHAR;
9701         MakeMove(fromX, fromY, toX, toY, promoChar);
9702         currentMove = forwardMostMove;
9703         return TRUE;
9704     }
9705 }
9706
9707 /* Load the nth game from the given file */
9708 int
9709 LoadGameFromFile(filename, n, title, useList)
9710      char *filename;
9711      int n;
9712      char *title;
9713      /*Boolean*/ int useList;
9714 {
9715     FILE *f;
9716     char buf[MSG_SIZ];
9717
9718     if (strcmp(filename, "-") == 0) {
9719         f = stdin;
9720         title = "stdin";
9721     } else {
9722         f = fopen(filename, "rb");
9723         if (f == NULL) {
9724           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9725             DisplayError(buf, errno);
9726             return FALSE;
9727         }
9728     }
9729     if (fseek(f, 0, 0) == -1) {
9730         /* f is not seekable; probably a pipe */
9731         useList = FALSE;
9732     }
9733     if (useList && n == 0) {
9734         int error = GameListBuild(f);
9735         if (error) {
9736             DisplayError(_("Cannot build game list"), error);
9737         } else if (!ListEmpty(&gameList) &&
9738                    ((ListGame *) gameList.tailPred)->number > 1) {
9739             GameListPopUp(f, title);
9740             return TRUE;
9741         }
9742         GameListDestroy();
9743         n = 1;
9744     }
9745     if (n == 0) n = 1;
9746     return LoadGame(f, n, title, FALSE);
9747 }
9748
9749
9750 void
9751 MakeRegisteredMove()
9752 {
9753     int fromX, fromY, toX, toY;
9754     char promoChar;
9755     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9756         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9757           case CMAIL_MOVE:
9758           case CMAIL_DRAW:
9759             if (appData.debugMode)
9760               fprintf(debugFP, "Restoring %s for game %d\n",
9761                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9762     
9763             thinkOutput[0] = NULLCHAR;
9764             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9765             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9766             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9767             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9768             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9769             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9770             MakeMove(fromX, fromY, toX, toY, promoChar);
9771             ShowMove(fromX, fromY, toX, toY);
9772               
9773             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9774               case MT_NONE:
9775               case MT_CHECK:
9776                 break;
9777                 
9778               case MT_CHECKMATE:
9779               case MT_STAINMATE:
9780                 if (WhiteOnMove(currentMove)) {
9781                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9782                 } else {
9783                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9784                 }
9785                 break;
9786                 
9787               case MT_STALEMATE:
9788                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9789                 break;
9790             }
9791
9792             break;
9793             
9794           case CMAIL_RESIGN:
9795             if (WhiteOnMove(currentMove)) {
9796                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9797             } else {
9798                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9799             }
9800             break;
9801             
9802           case CMAIL_ACCEPT:
9803             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9804             break;
9805               
9806           default:
9807             break;
9808         }
9809     }
9810
9811     return;
9812 }
9813
9814 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9815 int
9816 CmailLoadGame(f, gameNumber, title, useList)
9817      FILE *f;
9818      int gameNumber;
9819      char *title;
9820      int useList;
9821 {
9822     int retVal;
9823
9824     if (gameNumber > nCmailGames) {
9825         DisplayError(_("No more games in this message"), 0);
9826         return FALSE;
9827     }
9828     if (f == lastLoadGameFP) {
9829         int offset = gameNumber - lastLoadGameNumber;
9830         if (offset == 0) {
9831             cmailMsg[0] = NULLCHAR;
9832             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9833                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9834                 nCmailMovesRegistered--;
9835             }
9836             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9837             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9838                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9839             }
9840         } else {
9841             if (! RegisterMove()) return FALSE;
9842         }
9843     }
9844
9845     retVal = LoadGame(f, gameNumber, title, useList);
9846
9847     /* Make move registered during previous look at this game, if any */
9848     MakeRegisteredMove();
9849
9850     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9851         commentList[currentMove]
9852           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9853         DisplayComment(currentMove - 1, commentList[currentMove]);
9854     }
9855
9856     return retVal;
9857 }
9858
9859 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9860 int
9861 ReloadGame(offset)
9862      int offset;
9863 {
9864     int gameNumber = lastLoadGameNumber + offset;
9865     if (lastLoadGameFP == NULL) {
9866         DisplayError(_("No game has been loaded yet"), 0);
9867         return FALSE;
9868     }
9869     if (gameNumber <= 0) {
9870         DisplayError(_("Can't back up any further"), 0);
9871         return FALSE;
9872     }
9873     if (cmailMsgLoaded) {
9874         return CmailLoadGame(lastLoadGameFP, gameNumber,
9875                              lastLoadGameTitle, lastLoadGameUseList);
9876     } else {
9877         return LoadGame(lastLoadGameFP, gameNumber,
9878                         lastLoadGameTitle, lastLoadGameUseList);
9879     }
9880 }
9881
9882
9883
9884 /* Load the nth game from open file f */
9885 int
9886 LoadGame(f, gameNumber, title, useList)
9887      FILE *f;
9888      int gameNumber;
9889      char *title;
9890      int useList;
9891 {
9892     ChessMove cm;
9893     char buf[MSG_SIZ];
9894     int gn = gameNumber;
9895     ListGame *lg = NULL;
9896     int numPGNTags = 0;
9897     int err;
9898     GameMode oldGameMode;
9899     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9900
9901     if (appData.debugMode) 
9902         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9903
9904     if (gameMode == Training )
9905         SetTrainingModeOff();
9906
9907     oldGameMode = gameMode;
9908     if (gameMode != BeginningOfGame) {
9909       Reset(FALSE, TRUE);
9910     }
9911
9912     gameFileFP = f;
9913     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9914         fclose(lastLoadGameFP);
9915     }
9916
9917     if (useList) {
9918         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9919         
9920         if (lg) {
9921             fseek(f, lg->offset, 0);
9922             GameListHighlight(gameNumber);
9923             gn = 1;
9924         }
9925         else {
9926             DisplayError(_("Game number out of range"), 0);
9927             return FALSE;
9928         }
9929     } else {
9930         GameListDestroy();
9931         if (fseek(f, 0, 0) == -1) {
9932             if (f == lastLoadGameFP ?
9933                 gameNumber == lastLoadGameNumber + 1 :
9934                 gameNumber == 1) {
9935                 gn = 1;
9936             } else {
9937                 DisplayError(_("Can't seek on game file"), 0);
9938                 return FALSE;
9939             }
9940         }
9941     }
9942     lastLoadGameFP = f;
9943     lastLoadGameNumber = gameNumber;
9944     strcpy(lastLoadGameTitle, title);
9945     lastLoadGameUseList = useList;
9946
9947     yynewfile(f);
9948
9949     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9950       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9951                 lg->gameInfo.black);
9952             DisplayTitle(buf);
9953     } else if (*title != NULLCHAR) {
9954         if (gameNumber > 1) {
9955             sprintf(buf, "%s %d", title, gameNumber);
9956             DisplayTitle(buf);
9957         } else {
9958             DisplayTitle(title);
9959         }
9960     }
9961
9962     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9963         gameMode = PlayFromGameFile;
9964         ModeHighlight();
9965     }
9966
9967     currentMove = forwardMostMove = backwardMostMove = 0;
9968     CopyBoard(boards[0], initialPosition);
9969     StopClocks();
9970
9971     /*
9972      * Skip the first gn-1 games in the file.
9973      * Also skip over anything that precedes an identifiable 
9974      * start of game marker, to avoid being confused by 
9975      * garbage at the start of the file.  Currently 
9976      * recognized start of game markers are the move number "1",
9977      * the pattern "gnuchess .* game", the pattern
9978      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9979      * A game that starts with one of the latter two patterns
9980      * will also have a move number 1, possibly
9981      * following a position diagram.
9982      * 5-4-02: Let's try being more lenient and allowing a game to
9983      * start with an unnumbered move.  Does that break anything?
9984      */
9985     cm = lastLoadGameStart = (ChessMove) 0;
9986     while (gn > 0) {
9987         yyboardindex = forwardMostMove;
9988         cm = (ChessMove) yylex();
9989         switch (cm) {
9990           case (ChessMove) 0:
9991             if (cmailMsgLoaded) {
9992                 nCmailGames = CMAIL_MAX_GAMES - gn;
9993             } else {
9994                 Reset(TRUE, TRUE);
9995                 DisplayError(_("Game not found in file"), 0);
9996             }
9997             return FALSE;
9998
9999           case GNUChessGame:
10000           case XBoardGame:
10001             gn--;
10002             lastLoadGameStart = cm;
10003             break;
10004             
10005           case MoveNumberOne:
10006             switch (lastLoadGameStart) {
10007               case GNUChessGame:
10008               case XBoardGame:
10009               case PGNTag:
10010                 break;
10011               case MoveNumberOne:
10012               case (ChessMove) 0:
10013                 gn--;           /* count this game */
10014                 lastLoadGameStart = cm;
10015                 break;
10016               default:
10017                 /* impossible */
10018                 break;
10019             }
10020             break;
10021
10022           case PGNTag:
10023             switch (lastLoadGameStart) {
10024               case GNUChessGame:
10025               case PGNTag:
10026               case MoveNumberOne:
10027               case (ChessMove) 0:
10028                 gn--;           /* count this game */
10029                 lastLoadGameStart = cm;
10030                 break;
10031               case XBoardGame:
10032                 lastLoadGameStart = cm; /* game counted already */
10033                 break;
10034               default:
10035                 /* impossible */
10036                 break;
10037             }
10038             if (gn > 0) {
10039                 do {
10040                     yyboardindex = forwardMostMove;
10041                     cm = (ChessMove) yylex();
10042                 } while (cm == PGNTag || cm == Comment);
10043             }
10044             break;
10045
10046           case WhiteWins:
10047           case BlackWins:
10048           case GameIsDrawn:
10049             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10050                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10051                     != CMAIL_OLD_RESULT) {
10052                     nCmailResults ++ ;
10053                     cmailResult[  CMAIL_MAX_GAMES
10054                                 - gn - 1] = CMAIL_OLD_RESULT;
10055                 }
10056             }
10057             break;
10058
10059           case NormalMove:
10060             /* Only a NormalMove can be at the start of a game
10061              * without a position diagram. */
10062             if (lastLoadGameStart == (ChessMove) 0) {
10063               gn--;
10064               lastLoadGameStart = MoveNumberOne;
10065             }
10066             break;
10067
10068           default:
10069             break;
10070         }
10071     }
10072     
10073     if (appData.debugMode)
10074       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10075
10076     if (cm == XBoardGame) {
10077         /* Skip any header junk before position diagram and/or move 1 */
10078         for (;;) {
10079             yyboardindex = forwardMostMove;
10080             cm = (ChessMove) yylex();
10081
10082             if (cm == (ChessMove) 0 ||
10083                 cm == GNUChessGame || cm == XBoardGame) {
10084                 /* Empty game; pretend end-of-file and handle later */
10085                 cm = (ChessMove) 0;
10086                 break;
10087             }
10088
10089             if (cm == MoveNumberOne || cm == PositionDiagram ||
10090                 cm == PGNTag || cm == Comment)
10091               break;
10092         }
10093     } else if (cm == GNUChessGame) {
10094         if (gameInfo.event != NULL) {
10095             free(gameInfo.event);
10096         }
10097         gameInfo.event = StrSave(yy_text);
10098     }   
10099
10100     startedFromSetupPosition = FALSE;
10101     while (cm == PGNTag) {
10102         if (appData.debugMode) 
10103           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10104         err = ParsePGNTag(yy_text, &gameInfo);
10105         if (!err) numPGNTags++;
10106
10107         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10108         if(gameInfo.variant != oldVariant) {
10109             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10110             InitPosition(TRUE);
10111             oldVariant = gameInfo.variant;
10112             if (appData.debugMode) 
10113               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10114         }
10115
10116
10117         if (gameInfo.fen != NULL) {
10118           Board initial_position;
10119           startedFromSetupPosition = TRUE;
10120           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10121             Reset(TRUE, TRUE);
10122             DisplayError(_("Bad FEN position in file"), 0);
10123             return FALSE;
10124           }
10125           CopyBoard(boards[0], initial_position);
10126           if (blackPlaysFirst) {
10127             currentMove = forwardMostMove = backwardMostMove = 1;
10128             CopyBoard(boards[1], initial_position);
10129             strcpy(moveList[0], "");
10130             strcpy(parseList[0], "");
10131             timeRemaining[0][1] = whiteTimeRemaining;
10132             timeRemaining[1][1] = blackTimeRemaining;
10133             if (commentList[0] != NULL) {
10134               commentList[1] = commentList[0];
10135               commentList[0] = NULL;
10136             }
10137           } else {
10138             currentMove = forwardMostMove = backwardMostMove = 0;
10139           }
10140           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10141           {   int i;
10142               initialRulePlies = FENrulePlies;
10143               for( i=0; i< nrCastlingRights; i++ )
10144                   initialRights[i] = initial_position[CASTLING][i];
10145           }
10146           yyboardindex = forwardMostMove;
10147           free(gameInfo.fen);
10148           gameInfo.fen = NULL;
10149         }
10150
10151         yyboardindex = forwardMostMove;
10152         cm = (ChessMove) yylex();
10153
10154         /* Handle comments interspersed among the tags */
10155         while (cm == Comment) {
10156             char *p;
10157             if (appData.debugMode) 
10158               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10159             p = yy_text;
10160             AppendComment(currentMove, p, FALSE);
10161             yyboardindex = forwardMostMove;
10162             cm = (ChessMove) yylex();
10163         }
10164     }
10165
10166     /* don't rely on existence of Event tag since if game was
10167      * pasted from clipboard the Event tag may not exist
10168      */
10169     if (numPGNTags > 0){
10170         char *tags;
10171         if (gameInfo.variant == VariantNormal) {
10172           gameInfo.variant = StringToVariant(gameInfo.event);
10173         }
10174         if (!matchMode) {
10175           if( appData.autoDisplayTags ) {
10176             tags = PGNTags(&gameInfo);
10177             TagsPopUp(tags, CmailMsg());
10178             free(tags);
10179           }
10180         }
10181     } else {
10182         /* Make something up, but don't display it now */
10183         SetGameInfo();
10184         TagsPopDown();
10185     }
10186
10187     if (cm == PositionDiagram) {
10188         int i, j;
10189         char *p;
10190         Board initial_position;
10191
10192         if (appData.debugMode)
10193           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10194
10195         if (!startedFromSetupPosition) {
10196             p = yy_text;
10197             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10198               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10199                 switch (*p) {
10200                   case '[':
10201                   case '-':
10202                   case ' ':
10203                   case '\t':
10204                   case '\n':
10205                   case '\r':
10206                     break;
10207                   default:
10208                     initial_position[i][j++] = CharToPiece(*p);
10209                     break;
10210                 }
10211             while (*p == ' ' || *p == '\t' ||
10212                    *p == '\n' || *p == '\r') p++;
10213         
10214             if (strncmp(p, "black", strlen("black"))==0)
10215               blackPlaysFirst = TRUE;
10216             else
10217               blackPlaysFirst = FALSE;
10218             startedFromSetupPosition = TRUE;
10219         
10220             CopyBoard(boards[0], initial_position);
10221             if (blackPlaysFirst) {
10222                 currentMove = forwardMostMove = backwardMostMove = 1;
10223                 CopyBoard(boards[1], initial_position);
10224                 strcpy(moveList[0], "");
10225                 strcpy(parseList[0], "");
10226                 timeRemaining[0][1] = whiteTimeRemaining;
10227                 timeRemaining[1][1] = blackTimeRemaining;
10228                 if (commentList[0] != NULL) {
10229                     commentList[1] = commentList[0];
10230                     commentList[0] = NULL;
10231                 }
10232             } else {
10233                 currentMove = forwardMostMove = backwardMostMove = 0;
10234             }
10235         }
10236         yyboardindex = forwardMostMove;
10237         cm = (ChessMove) yylex();
10238     }
10239
10240     if (first.pr == NoProc) {
10241         StartChessProgram(&first);
10242     }
10243     InitChessProgram(&first, FALSE);
10244     SendToProgram("force\n", &first);
10245     if (startedFromSetupPosition) {
10246         SendBoard(&first, forwardMostMove);
10247     if (appData.debugMode) {
10248         fprintf(debugFP, "Load Game\n");
10249     }
10250         DisplayBothClocks();
10251     }      
10252
10253     /* [HGM] server: flag to write setup moves in broadcast file as one */
10254     loadFlag = appData.suppressLoadMoves;
10255
10256     while (cm == Comment) {
10257         char *p;
10258         if (appData.debugMode) 
10259           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10260         p = yy_text;
10261         AppendComment(currentMove, p, FALSE);
10262         yyboardindex = forwardMostMove;
10263         cm = (ChessMove) yylex();
10264     }
10265
10266     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10267         cm == WhiteWins || cm == BlackWins ||
10268         cm == GameIsDrawn || cm == GameUnfinished) {
10269         DisplayMessage("", _("No moves in game"));
10270         if (cmailMsgLoaded) {
10271             if (appData.debugMode)
10272               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10273             ClearHighlights();
10274             flipView = FALSE;
10275         }
10276         DrawPosition(FALSE, boards[currentMove]);
10277         DisplayBothClocks();
10278         gameMode = EditGame;
10279         ModeHighlight();
10280         gameFileFP = NULL;
10281         cmailOldMove = 0;
10282         return TRUE;
10283     }
10284
10285     // [HGM] PV info: routine tests if comment empty
10286     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10287         DisplayComment(currentMove - 1, commentList[currentMove]);
10288     }
10289     if (!matchMode && appData.timeDelay != 0) 
10290       DrawPosition(FALSE, boards[currentMove]);
10291
10292     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10293       programStats.ok_to_send = 1;
10294     }
10295
10296     /* if the first token after the PGN tags is a move
10297      * and not move number 1, retrieve it from the parser 
10298      */
10299     if (cm != MoveNumberOne)
10300         LoadGameOneMove(cm);
10301
10302     /* load the remaining moves from the file */
10303     while (LoadGameOneMove((ChessMove)0)) {
10304       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10305       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10306     }
10307
10308     /* rewind to the start of the game */
10309     currentMove = backwardMostMove;
10310
10311     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10312
10313     if (oldGameMode == AnalyzeFile ||
10314         oldGameMode == AnalyzeMode) {
10315       AnalyzeFileEvent();
10316     }
10317
10318     if (matchMode || appData.timeDelay == 0) {
10319       ToEndEvent();
10320       gameMode = EditGame;
10321       ModeHighlight();
10322     } else if (appData.timeDelay > 0) {
10323       AutoPlayGameLoop();
10324     }
10325
10326     if (appData.debugMode) 
10327         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10328
10329     loadFlag = 0; /* [HGM] true game starts */
10330     return TRUE;
10331 }
10332
10333 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10334 int
10335 ReloadPosition(offset)
10336      int offset;
10337 {
10338     int positionNumber = lastLoadPositionNumber + offset;
10339     if (lastLoadPositionFP == NULL) {
10340         DisplayError(_("No position has been loaded yet"), 0);
10341         return FALSE;
10342     }
10343     if (positionNumber <= 0) {
10344         DisplayError(_("Can't back up any further"), 0);
10345         return FALSE;
10346     }
10347     return LoadPosition(lastLoadPositionFP, positionNumber,
10348                         lastLoadPositionTitle);
10349 }
10350
10351 /* Load the nth position from the given file */
10352 int
10353 LoadPositionFromFile(filename, n, title)
10354      char *filename;
10355      int n;
10356      char *title;
10357 {
10358     FILE *f;
10359     char buf[MSG_SIZ];
10360
10361     if (strcmp(filename, "-") == 0) {
10362         return LoadPosition(stdin, n, "stdin");
10363     } else {
10364         f = fopen(filename, "rb");
10365         if (f == NULL) {
10366             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10367             DisplayError(buf, errno);
10368             return FALSE;
10369         } else {
10370             return LoadPosition(f, n, title);
10371         }
10372     }
10373 }
10374
10375 /* Load the nth position from the given open file, and close it */
10376 int
10377 LoadPosition(f, positionNumber, title)
10378      FILE *f;
10379      int positionNumber;
10380      char *title;
10381 {
10382     char *p, line[MSG_SIZ];
10383     Board initial_position;
10384     int i, j, fenMode, pn;
10385     
10386     if (gameMode == Training )
10387         SetTrainingModeOff();
10388
10389     if (gameMode != BeginningOfGame) {
10390         Reset(FALSE, TRUE);
10391     }
10392     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10393         fclose(lastLoadPositionFP);
10394     }
10395     if (positionNumber == 0) positionNumber = 1;
10396     lastLoadPositionFP = f;
10397     lastLoadPositionNumber = positionNumber;
10398     strcpy(lastLoadPositionTitle, title);
10399     if (first.pr == NoProc) {
10400       StartChessProgram(&first);
10401       InitChessProgram(&first, FALSE);
10402     }    
10403     pn = positionNumber;
10404     if (positionNumber < 0) {
10405         /* Negative position number means to seek to that byte offset */
10406         if (fseek(f, -positionNumber, 0) == -1) {
10407             DisplayError(_("Can't seek on position file"), 0);
10408             return FALSE;
10409         };
10410         pn = 1;
10411     } else {
10412         if (fseek(f, 0, 0) == -1) {
10413             if (f == lastLoadPositionFP ?
10414                 positionNumber == lastLoadPositionNumber + 1 :
10415                 positionNumber == 1) {
10416                 pn = 1;
10417             } else {
10418                 DisplayError(_("Can't seek on position file"), 0);
10419                 return FALSE;
10420             }
10421         }
10422     }
10423     /* See if this file is FEN or old-style xboard */
10424     if (fgets(line, MSG_SIZ, f) == NULL) {
10425         DisplayError(_("Position not found in file"), 0);
10426         return FALSE;
10427     }
10428     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10429     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10430
10431     if (pn >= 2) {
10432         if (fenMode || line[0] == '#') pn--;
10433         while (pn > 0) {
10434             /* skip positions before number pn */
10435             if (fgets(line, MSG_SIZ, f) == NULL) {
10436                 Reset(TRUE, TRUE);
10437                 DisplayError(_("Position not found in file"), 0);
10438                 return FALSE;
10439             }
10440             if (fenMode || line[0] == '#') pn--;
10441         }
10442     }
10443
10444     if (fenMode) {
10445         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10446             DisplayError(_("Bad FEN position in file"), 0);
10447             return FALSE;
10448         }
10449     } else {
10450         (void) fgets(line, MSG_SIZ, f);
10451         (void) fgets(line, MSG_SIZ, f);
10452     
10453         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10454             (void) fgets(line, MSG_SIZ, f);
10455             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10456                 if (*p == ' ')
10457                   continue;
10458                 initial_position[i][j++] = CharToPiece(*p);
10459             }
10460         }
10461     
10462         blackPlaysFirst = FALSE;
10463         if (!feof(f)) {
10464             (void) fgets(line, MSG_SIZ, f);
10465             if (strncmp(line, "black", strlen("black"))==0)
10466               blackPlaysFirst = TRUE;
10467         }
10468     }
10469     startedFromSetupPosition = TRUE;
10470     
10471     SendToProgram("force\n", &first);
10472     CopyBoard(boards[0], initial_position);
10473     if (blackPlaysFirst) {
10474         currentMove = forwardMostMove = backwardMostMove = 1;
10475         strcpy(moveList[0], "");
10476         strcpy(parseList[0], "");
10477         CopyBoard(boards[1], initial_position);
10478         DisplayMessage("", _("Black to play"));
10479     } else {
10480         currentMove = forwardMostMove = backwardMostMove = 0;
10481         DisplayMessage("", _("White to play"));
10482     }
10483     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10484     SendBoard(&first, forwardMostMove);
10485     if (appData.debugMode) {
10486 int i, j;
10487   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10488   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10489         fprintf(debugFP, "Load Position\n");
10490     }
10491
10492     if (positionNumber > 1) {
10493         sprintf(line, "%s %d", title, positionNumber);
10494         DisplayTitle(line);
10495     } else {
10496         DisplayTitle(title);
10497     }
10498     gameMode = EditGame;
10499     ModeHighlight();
10500     ResetClocks();
10501     timeRemaining[0][1] = whiteTimeRemaining;
10502     timeRemaining[1][1] = blackTimeRemaining;
10503     DrawPosition(FALSE, boards[currentMove]);
10504    
10505     return TRUE;
10506 }
10507
10508
10509 void
10510 CopyPlayerNameIntoFileName(dest, src)
10511      char **dest, *src;
10512 {
10513     while (*src != NULLCHAR && *src != ',') {
10514         if (*src == ' ') {
10515             *(*dest)++ = '_';
10516             src++;
10517         } else {
10518             *(*dest)++ = *src++;
10519         }
10520     }
10521 }
10522
10523 char *DefaultFileName(ext)
10524      char *ext;
10525 {
10526     static char def[MSG_SIZ];
10527     char *p;
10528
10529     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10530         p = def;
10531         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10532         *p++ = '-';
10533         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10534         *p++ = '.';
10535         strcpy(p, ext);
10536     } else {
10537         def[0] = NULLCHAR;
10538     }
10539     return def;
10540 }
10541
10542 /* Save the current game to the given file */
10543 int
10544 SaveGameToFile(filename, append)
10545      char *filename;
10546      int append;
10547 {
10548     FILE *f;
10549     char buf[MSG_SIZ];
10550
10551     if (strcmp(filename, "-") == 0) {
10552         return SaveGame(stdout, 0, NULL);
10553     } else {
10554         f = fopen(filename, append ? "a" : "w");
10555         if (f == NULL) {
10556             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10557             DisplayError(buf, errno);
10558             return FALSE;
10559         } else {
10560             return SaveGame(f, 0, NULL);
10561         }
10562     }
10563 }
10564
10565 char *
10566 SavePart(str)
10567      char *str;
10568 {
10569     static char buf[MSG_SIZ];
10570     char *p;
10571     
10572     p = strchr(str, ' ');
10573     if (p == NULL) return str;
10574     strncpy(buf, str, p - str);
10575     buf[p - str] = NULLCHAR;
10576     return buf;
10577 }
10578
10579 #define PGN_MAX_LINE 75
10580
10581 #define PGN_SIDE_WHITE  0
10582 #define PGN_SIDE_BLACK  1
10583
10584 /* [AS] */
10585 static int FindFirstMoveOutOfBook( int side )
10586 {
10587     int result = -1;
10588
10589     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10590         int index = backwardMostMove;
10591         int has_book_hit = 0;
10592
10593         if( (index % 2) != side ) {
10594             index++;
10595         }
10596
10597         while( index < forwardMostMove ) {
10598             /* Check to see if engine is in book */
10599             int depth = pvInfoList[index].depth;
10600             int score = pvInfoList[index].score;
10601             int in_book = 0;
10602
10603             if( depth <= 2 ) {
10604                 in_book = 1;
10605             }
10606             else if( score == 0 && depth == 63 ) {
10607                 in_book = 1; /* Zappa */
10608             }
10609             else if( score == 2 && depth == 99 ) {
10610                 in_book = 1; /* Abrok */
10611             }
10612
10613             has_book_hit += in_book;
10614
10615             if( ! in_book ) {
10616                 result = index;
10617
10618                 break;
10619             }
10620
10621             index += 2;
10622         }
10623     }
10624
10625     return result;
10626 }
10627
10628 /* [AS] */
10629 void GetOutOfBookInfo( char * buf )
10630 {
10631     int oob[2];
10632     int i;
10633     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10634
10635     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10636     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10637
10638     *buf = '\0';
10639
10640     if( oob[0] >= 0 || oob[1] >= 0 ) {
10641         for( i=0; i<2; i++ ) {
10642             int idx = oob[i];
10643
10644             if( idx >= 0 ) {
10645                 if( i > 0 && oob[0] >= 0 ) {
10646                     strcat( buf, "   " );
10647                 }
10648
10649                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10650                 sprintf( buf+strlen(buf), "%s%.2f", 
10651                     pvInfoList[idx].score >= 0 ? "+" : "",
10652                     pvInfoList[idx].score / 100.0 );
10653             }
10654         }
10655     }
10656 }
10657
10658 /* Save game in PGN style and close the file */
10659 int
10660 SaveGamePGN(f)
10661      FILE *f;
10662 {
10663     int i, offset, linelen, newblock;
10664     time_t tm;
10665 //    char *movetext;
10666     char numtext[32];
10667     int movelen, numlen, blank;
10668     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10669
10670     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10671     
10672     tm = time((time_t *) NULL);
10673     
10674     PrintPGNTags(f, &gameInfo);
10675     
10676     if (backwardMostMove > 0 || startedFromSetupPosition) {
10677         char *fen = PositionToFEN(backwardMostMove, NULL);
10678         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10679         fprintf(f, "\n{--------------\n");
10680         PrintPosition(f, backwardMostMove);
10681         fprintf(f, "--------------}\n");
10682         free(fen);
10683     }
10684     else {
10685         /* [AS] Out of book annotation */
10686         if( appData.saveOutOfBookInfo ) {
10687             char buf[64];
10688
10689             GetOutOfBookInfo( buf );
10690
10691             if( buf[0] != '\0' ) {
10692                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10693             }
10694         }
10695
10696         fprintf(f, "\n");
10697     }
10698
10699     i = backwardMostMove;
10700     linelen = 0;
10701     newblock = TRUE;
10702
10703     while (i < forwardMostMove) {
10704         /* Print comments preceding this move */
10705         if (commentList[i] != NULL) {
10706             if (linelen > 0) fprintf(f, "\n");
10707             fprintf(f, "%s", commentList[i]);
10708             linelen = 0;
10709             newblock = TRUE;
10710         }
10711
10712         /* Format move number */
10713         if ((i % 2) == 0) {
10714             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10715         } else {
10716             if (newblock) {
10717                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10718             } else {
10719                 numtext[0] = NULLCHAR;
10720             }
10721         }
10722         numlen = strlen(numtext);
10723         newblock = FALSE;
10724
10725         /* Print move number */
10726         blank = linelen > 0 && numlen > 0;
10727         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10728             fprintf(f, "\n");
10729             linelen = 0;
10730             blank = 0;
10731         }
10732         if (blank) {
10733             fprintf(f, " ");
10734             linelen++;
10735         }
10736         fprintf(f, "%s", numtext);
10737         linelen += numlen;
10738
10739         /* Get move */
10740         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10741         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10742
10743         /* Print move */
10744         blank = linelen > 0 && movelen > 0;
10745         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10746             fprintf(f, "\n");
10747             linelen = 0;
10748             blank = 0;
10749         }
10750         if (blank) {
10751             fprintf(f, " ");
10752             linelen++;
10753         }
10754         fprintf(f, "%s", move_buffer);
10755         linelen += movelen;
10756
10757         /* [AS] Add PV info if present */
10758         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10759             /* [HGM] add time */
10760             char buf[MSG_SIZ]; int seconds;
10761
10762             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10763
10764             if( seconds <= 0) buf[0] = 0; else
10765             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10766                 seconds = (seconds + 4)/10; // round to full seconds
10767                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10768                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10769             }
10770
10771             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10772                 pvInfoList[i].score >= 0 ? "+" : "",
10773                 pvInfoList[i].score / 100.0,
10774                 pvInfoList[i].depth,
10775                 buf );
10776
10777             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10778
10779             /* Print score/depth */
10780             blank = linelen > 0 && movelen > 0;
10781             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10782                 fprintf(f, "\n");
10783                 linelen = 0;
10784                 blank = 0;
10785             }
10786             if (blank) {
10787                 fprintf(f, " ");
10788                 linelen++;
10789             }
10790             fprintf(f, "%s", move_buffer);
10791             linelen += movelen;
10792         }
10793
10794         i++;
10795     }
10796     
10797     /* Start a new line */
10798     if (linelen > 0) fprintf(f, "\n");
10799
10800     /* Print comments after last move */
10801     if (commentList[i] != NULL) {
10802         fprintf(f, "%s\n", commentList[i]);
10803     }
10804
10805     /* Print result */
10806     if (gameInfo.resultDetails != NULL &&
10807         gameInfo.resultDetails[0] != NULLCHAR) {
10808         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10809                 PGNResult(gameInfo.result));
10810     } else {
10811         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10812     }
10813
10814     fclose(f);
10815     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10816     return TRUE;
10817 }
10818
10819 /* Save game in old style and close the file */
10820 int
10821 SaveGameOldStyle(f)
10822      FILE *f;
10823 {
10824     int i, offset;
10825     time_t tm;
10826     
10827     tm = time((time_t *) NULL);
10828     
10829     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10830     PrintOpponents(f);
10831     
10832     if (backwardMostMove > 0 || startedFromSetupPosition) {
10833         fprintf(f, "\n[--------------\n");
10834         PrintPosition(f, backwardMostMove);
10835         fprintf(f, "--------------]\n");
10836     } else {
10837         fprintf(f, "\n");
10838     }
10839
10840     i = backwardMostMove;
10841     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10842
10843     while (i < forwardMostMove) {
10844         if (commentList[i] != NULL) {
10845             fprintf(f, "[%s]\n", commentList[i]);
10846         }
10847
10848         if ((i % 2) == 1) {
10849             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10850             i++;
10851         } else {
10852             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10853             i++;
10854             if (commentList[i] != NULL) {
10855                 fprintf(f, "\n");
10856                 continue;
10857             }
10858             if (i >= forwardMostMove) {
10859                 fprintf(f, "\n");
10860                 break;
10861             }
10862             fprintf(f, "%s\n", parseList[i]);
10863             i++;
10864         }
10865     }
10866     
10867     if (commentList[i] != NULL) {
10868         fprintf(f, "[%s]\n", commentList[i]);
10869     }
10870
10871     /* This isn't really the old style, but it's close enough */
10872     if (gameInfo.resultDetails != NULL &&
10873         gameInfo.resultDetails[0] != NULLCHAR) {
10874         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10875                 gameInfo.resultDetails);
10876     } else {
10877         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10878     }
10879
10880     fclose(f);
10881     return TRUE;
10882 }
10883
10884 /* Save the current game to open file f and close the file */
10885 int
10886 SaveGame(f, dummy, dummy2)
10887      FILE *f;
10888      int dummy;
10889      char *dummy2;
10890 {
10891     if (gameMode == EditPosition) EditPositionDone(TRUE);
10892     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10893     if (appData.oldSaveStyle)
10894       return SaveGameOldStyle(f);
10895     else
10896       return SaveGamePGN(f);
10897 }
10898
10899 /* Save the current position to the given file */
10900 int
10901 SavePositionToFile(filename)
10902      char *filename;
10903 {
10904     FILE *f;
10905     char buf[MSG_SIZ];
10906
10907     if (strcmp(filename, "-") == 0) {
10908         return SavePosition(stdout, 0, NULL);
10909     } else {
10910         f = fopen(filename, "a");
10911         if (f == NULL) {
10912             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10913             DisplayError(buf, errno);
10914             return FALSE;
10915         } else {
10916             SavePosition(f, 0, NULL);
10917             return TRUE;
10918         }
10919     }
10920 }
10921
10922 /* Save the current position to the given open file and close the file */
10923 int
10924 SavePosition(f, dummy, dummy2)
10925      FILE *f;
10926      int dummy;
10927      char *dummy2;
10928 {
10929     time_t tm;
10930     char *fen;
10931     
10932     if (gameMode == EditPosition) EditPositionDone(TRUE);
10933     if (appData.oldSaveStyle) {
10934         tm = time((time_t *) NULL);
10935     
10936         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10937         PrintOpponents(f);
10938         fprintf(f, "[--------------\n");
10939         PrintPosition(f, currentMove);
10940         fprintf(f, "--------------]\n");
10941     } else {
10942         fen = PositionToFEN(currentMove, NULL);
10943         fprintf(f, "%s\n", fen);
10944         free(fen);
10945     }
10946     fclose(f);
10947     return TRUE;
10948 }
10949
10950 void
10951 ReloadCmailMsgEvent(unregister)
10952      int unregister;
10953 {
10954 #if !WIN32
10955     static char *inFilename = NULL;
10956     static char *outFilename;
10957     int i;
10958     struct stat inbuf, outbuf;
10959     int status;
10960     
10961     /* Any registered moves are unregistered if unregister is set, */
10962     /* i.e. invoked by the signal handler */
10963     if (unregister) {
10964         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10965             cmailMoveRegistered[i] = FALSE;
10966             if (cmailCommentList[i] != NULL) {
10967                 free(cmailCommentList[i]);
10968                 cmailCommentList[i] = NULL;
10969             }
10970         }
10971         nCmailMovesRegistered = 0;
10972     }
10973
10974     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10975         cmailResult[i] = CMAIL_NOT_RESULT;
10976     }
10977     nCmailResults = 0;
10978
10979     if (inFilename == NULL) {
10980         /* Because the filenames are static they only get malloced once  */
10981         /* and they never get freed                                      */
10982         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10983         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10984
10985         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10986         sprintf(outFilename, "%s.out", appData.cmailGameName);
10987     }
10988     
10989     status = stat(outFilename, &outbuf);
10990     if (status < 0) {
10991         cmailMailedMove = FALSE;
10992     } else {
10993         status = stat(inFilename, &inbuf);
10994         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10995     }
10996     
10997     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10998        counts the games, notes how each one terminated, etc.
10999        
11000        It would be nice to remove this kludge and instead gather all
11001        the information while building the game list.  (And to keep it
11002        in the game list nodes instead of having a bunch of fixed-size
11003        parallel arrays.)  Note this will require getting each game's
11004        termination from the PGN tags, as the game list builder does
11005        not process the game moves.  --mann
11006        */
11007     cmailMsgLoaded = TRUE;
11008     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11009     
11010     /* Load first game in the file or popup game menu */
11011     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11012
11013 #endif /* !WIN32 */
11014     return;
11015 }
11016
11017 int
11018 RegisterMove()
11019 {
11020     FILE *f;
11021     char string[MSG_SIZ];
11022
11023     if (   cmailMailedMove
11024         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11025         return TRUE;            /* Allow free viewing  */
11026     }
11027
11028     /* Unregister move to ensure that we don't leave RegisterMove        */
11029     /* with the move registered when the conditions for registering no   */
11030     /* longer hold                                                       */
11031     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11032         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11033         nCmailMovesRegistered --;
11034
11035         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11036           {
11037               free(cmailCommentList[lastLoadGameNumber - 1]);
11038               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11039           }
11040     }
11041
11042     if (cmailOldMove == -1) {
11043         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11044         return FALSE;
11045     }
11046
11047     if (currentMove > cmailOldMove + 1) {
11048         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11049         return FALSE;
11050     }
11051
11052     if (currentMove < cmailOldMove) {
11053         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11054         return FALSE;
11055     }
11056
11057     if (forwardMostMove > currentMove) {
11058         /* Silently truncate extra moves */
11059         TruncateGame();
11060     }
11061
11062     if (   (currentMove == cmailOldMove + 1)
11063         || (   (currentMove == cmailOldMove)
11064             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11065                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11066         if (gameInfo.result != GameUnfinished) {
11067             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11068         }
11069
11070         if (commentList[currentMove] != NULL) {
11071             cmailCommentList[lastLoadGameNumber - 1]
11072               = StrSave(commentList[currentMove]);
11073         }
11074         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11075
11076         if (appData.debugMode)
11077           fprintf(debugFP, "Saving %s for game %d\n",
11078                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11079
11080         sprintf(string,
11081                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11082         
11083         f = fopen(string, "w");
11084         if (appData.oldSaveStyle) {
11085             SaveGameOldStyle(f); /* also closes the file */
11086             
11087             sprintf(string, "%s.pos.out", appData.cmailGameName);
11088             f = fopen(string, "w");
11089             SavePosition(f, 0, NULL); /* also closes the file */
11090         } else {
11091             fprintf(f, "{--------------\n");
11092             PrintPosition(f, currentMove);
11093             fprintf(f, "--------------}\n\n");
11094             
11095             SaveGame(f, 0, NULL); /* also closes the file*/
11096         }
11097         
11098         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11099         nCmailMovesRegistered ++;
11100     } else if (nCmailGames == 1) {
11101         DisplayError(_("You have not made a move yet"), 0);
11102         return FALSE;
11103     }
11104
11105     return TRUE;
11106 }
11107
11108 void
11109 MailMoveEvent()
11110 {
11111 #if !WIN32
11112     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11113     FILE *commandOutput;
11114     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11115     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11116     int nBuffers;
11117     int i;
11118     int archived;
11119     char *arcDir;
11120
11121     if (! cmailMsgLoaded) {
11122         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11123         return;
11124     }
11125
11126     if (nCmailGames == nCmailResults) {
11127         DisplayError(_("No unfinished games"), 0);
11128         return;
11129     }
11130
11131 #if CMAIL_PROHIBIT_REMAIL
11132     if (cmailMailedMove) {
11133         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);
11134         DisplayError(msg, 0);
11135         return;
11136     }
11137 #endif
11138
11139     if (! (cmailMailedMove || RegisterMove())) return;
11140     
11141     if (   cmailMailedMove
11142         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11143         sprintf(string, partCommandString,
11144                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11145         commandOutput = popen(string, "r");
11146
11147         if (commandOutput == NULL) {
11148             DisplayError(_("Failed to invoke cmail"), 0);
11149         } else {
11150             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11151                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11152             }
11153             if (nBuffers > 1) {
11154                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11155                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11156                 nBytes = MSG_SIZ - 1;
11157             } else {
11158                 (void) memcpy(msg, buffer, nBytes);
11159             }
11160             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11161
11162             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11163                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11164
11165                 archived = TRUE;
11166                 for (i = 0; i < nCmailGames; i ++) {
11167                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11168                         archived = FALSE;
11169                     }
11170                 }
11171                 if (   archived
11172                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11173                         != NULL)) {
11174                     sprintf(buffer, "%s/%s.%s.archive",
11175                             arcDir,
11176                             appData.cmailGameName,
11177                             gameInfo.date);
11178                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11179                     cmailMsgLoaded = FALSE;
11180                 }
11181             }
11182
11183             DisplayInformation(msg);
11184             pclose(commandOutput);
11185         }
11186     } else {
11187         if ((*cmailMsg) != '\0') {
11188             DisplayInformation(cmailMsg);
11189         }
11190     }
11191
11192     return;
11193 #endif /* !WIN32 */
11194 }
11195
11196 char *
11197 CmailMsg()
11198 {
11199 #if WIN32
11200     return NULL;
11201 #else
11202     int  prependComma = 0;
11203     char number[5];
11204     char string[MSG_SIZ];       /* Space for game-list */
11205     int  i;
11206     
11207     if (!cmailMsgLoaded) return "";
11208
11209     if (cmailMailedMove) {
11210         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11211     } else {
11212         /* Create a list of games left */
11213         sprintf(string, "[");
11214         for (i = 0; i < nCmailGames; i ++) {
11215             if (! (   cmailMoveRegistered[i]
11216                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11217                 if (prependComma) {
11218                     sprintf(number, ",%d", i + 1);
11219                 } else {
11220                     sprintf(number, "%d", i + 1);
11221                     prependComma = 1;
11222                 }
11223                 
11224                 strcat(string, number);
11225             }
11226         }
11227         strcat(string, "]");
11228
11229         if (nCmailMovesRegistered + nCmailResults == 0) {
11230             switch (nCmailGames) {
11231               case 1:
11232                 sprintf(cmailMsg,
11233                         _("Still need to make move for game\n"));
11234                 break;
11235                 
11236               case 2:
11237                 sprintf(cmailMsg,
11238                         _("Still need to make moves for both games\n"));
11239                 break;
11240                 
11241               default:
11242                 sprintf(cmailMsg,
11243                         _("Still need to make moves for all %d games\n"),
11244                         nCmailGames);
11245                 break;
11246             }
11247         } else {
11248             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11249               case 1:
11250                 sprintf(cmailMsg,
11251                         _("Still need to make a move for game %s\n"),
11252                         string);
11253                 break;
11254                 
11255               case 0:
11256                 if (nCmailResults == nCmailGames) {
11257                     sprintf(cmailMsg, _("No unfinished games\n"));
11258                 } else {
11259                     sprintf(cmailMsg, _("Ready to send mail\n"));
11260                 }
11261                 break;
11262                 
11263               default:
11264                 sprintf(cmailMsg,
11265                         _("Still need to make moves for games %s\n"),
11266                         string);
11267             }
11268         }
11269     }
11270     return cmailMsg;
11271 #endif /* WIN32 */
11272 }
11273
11274 void
11275 ResetGameEvent()
11276 {
11277     if (gameMode == Training)
11278       SetTrainingModeOff();
11279
11280     Reset(TRUE, TRUE);
11281     cmailMsgLoaded = FALSE;
11282     if (appData.icsActive) {
11283       SendToICS(ics_prefix);
11284       SendToICS("refresh\n");
11285     }
11286 }
11287
11288 void
11289 ExitEvent(status)
11290      int status;
11291 {
11292     exiting++;
11293     if (exiting > 2) {
11294       /* Give up on clean exit */
11295       exit(status);
11296     }
11297     if (exiting > 1) {
11298       /* Keep trying for clean exit */
11299       return;
11300     }
11301
11302     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11303
11304     if (telnetISR != NULL) {
11305       RemoveInputSource(telnetISR);
11306     }
11307     if (icsPR != NoProc) {
11308       DestroyChildProcess(icsPR, TRUE);
11309     }
11310
11311     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11312     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11313
11314     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11315     /* make sure this other one finishes before killing it!                  */
11316     if(endingGame) { int count = 0;
11317         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11318         while(endingGame && count++ < 10) DoSleep(1);
11319         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11320     }
11321
11322     /* Kill off chess programs */
11323     if (first.pr != NoProc) {
11324         ExitAnalyzeMode();
11325         
11326         DoSleep( appData.delayBeforeQuit );
11327         SendToProgram("quit\n", &first);
11328         DoSleep( appData.delayAfterQuit );
11329         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11330     }
11331     if (second.pr != NoProc) {
11332         DoSleep( appData.delayBeforeQuit );
11333         SendToProgram("quit\n", &second);
11334         DoSleep( appData.delayAfterQuit );
11335         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11336     }
11337     if (first.isr != NULL) {
11338         RemoveInputSource(first.isr);
11339     }
11340     if (second.isr != NULL) {
11341         RemoveInputSource(second.isr);
11342     }
11343
11344     ShutDownFrontEnd();
11345     exit(status);
11346 }
11347
11348 void
11349 PauseEvent()
11350 {
11351     if (appData.debugMode)
11352         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11353     if (pausing) {
11354         pausing = FALSE;
11355         ModeHighlight();
11356         if (gameMode == MachinePlaysWhite ||
11357             gameMode == MachinePlaysBlack) {
11358             StartClocks();
11359         } else {
11360             DisplayBothClocks();
11361         }
11362         if (gameMode == PlayFromGameFile) {
11363             if (appData.timeDelay >= 0) 
11364                 AutoPlayGameLoop();
11365         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11366             Reset(FALSE, TRUE);
11367             SendToICS(ics_prefix);
11368             SendToICS("refresh\n");
11369         } else if (currentMove < forwardMostMove) {
11370             ForwardInner(forwardMostMove);
11371         }
11372         pauseExamInvalid = FALSE;
11373     } else {
11374         switch (gameMode) {
11375           default:
11376             return;
11377           case IcsExamining:
11378             pauseExamForwardMostMove = forwardMostMove;
11379             pauseExamInvalid = FALSE;
11380             /* fall through */
11381           case IcsObserving:
11382           case IcsPlayingWhite:
11383           case IcsPlayingBlack:
11384             pausing = TRUE;
11385             ModeHighlight();
11386             return;
11387           case PlayFromGameFile:
11388             (void) StopLoadGameTimer();
11389             pausing = TRUE;
11390             ModeHighlight();
11391             break;
11392           case BeginningOfGame:
11393             if (appData.icsActive) return;
11394             /* else fall through */
11395           case MachinePlaysWhite:
11396           case MachinePlaysBlack:
11397           case TwoMachinesPlay:
11398             if (forwardMostMove == 0)
11399               return;           /* don't pause if no one has moved */
11400             if ((gameMode == MachinePlaysWhite &&
11401                  !WhiteOnMove(forwardMostMove)) ||
11402                 (gameMode == MachinePlaysBlack &&
11403                  WhiteOnMove(forwardMostMove))) {
11404                 StopClocks();
11405             }
11406             pausing = TRUE;
11407             ModeHighlight();
11408             break;
11409         }
11410     }
11411 }
11412
11413 void
11414 EditCommentEvent()
11415 {
11416     char title[MSG_SIZ];
11417
11418     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11419         strcpy(title, _("Edit comment"));
11420     } else {
11421         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11422                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11423                 parseList[currentMove - 1]);
11424     }
11425
11426     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11427 }
11428
11429
11430 void
11431 EditTagsEvent()
11432 {
11433     char *tags = PGNTags(&gameInfo);
11434     EditTagsPopUp(tags);
11435     free(tags);
11436 }
11437
11438 void
11439 AnalyzeModeEvent()
11440 {
11441     if (appData.noChessProgram || gameMode == AnalyzeMode)
11442       return;
11443
11444     if (gameMode != AnalyzeFile) {
11445         if (!appData.icsEngineAnalyze) {
11446                EditGameEvent();
11447                if (gameMode != EditGame) return;
11448         }
11449         ResurrectChessProgram();
11450         SendToProgram("analyze\n", &first);
11451         first.analyzing = TRUE;
11452         /*first.maybeThinking = TRUE;*/
11453         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11454         EngineOutputPopUp();
11455     }
11456     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11457     pausing = FALSE;
11458     ModeHighlight();
11459     SetGameInfo();
11460
11461     StartAnalysisClock();
11462     GetTimeMark(&lastNodeCountTime);
11463     lastNodeCount = 0;
11464 }
11465
11466 void
11467 AnalyzeFileEvent()
11468 {
11469     if (appData.noChessProgram || gameMode == AnalyzeFile)
11470       return;
11471
11472     if (gameMode != AnalyzeMode) {
11473         EditGameEvent();
11474         if (gameMode != EditGame) return;
11475         ResurrectChessProgram();
11476         SendToProgram("analyze\n", &first);
11477         first.analyzing = TRUE;
11478         /*first.maybeThinking = TRUE;*/
11479         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11480         EngineOutputPopUp();
11481     }
11482     gameMode = AnalyzeFile;
11483     pausing = FALSE;
11484     ModeHighlight();
11485     SetGameInfo();
11486
11487     StartAnalysisClock();
11488     GetTimeMark(&lastNodeCountTime);
11489     lastNodeCount = 0;
11490 }
11491
11492 void
11493 MachineWhiteEvent()
11494 {
11495     char buf[MSG_SIZ];
11496     char *bookHit = NULL;
11497
11498     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11499       return;
11500
11501
11502     if (gameMode == PlayFromGameFile || 
11503         gameMode == TwoMachinesPlay  || 
11504         gameMode == Training         || 
11505         gameMode == AnalyzeMode      || 
11506         gameMode == EndOfGame)
11507         EditGameEvent();
11508
11509     if (gameMode == EditPosition) 
11510         EditPositionDone(TRUE);
11511
11512     if (!WhiteOnMove(currentMove)) {
11513         DisplayError(_("It is not White's turn"), 0);
11514         return;
11515     }
11516   
11517     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11518       ExitAnalyzeMode();
11519
11520     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11521         gameMode == AnalyzeFile)
11522         TruncateGame();
11523
11524     ResurrectChessProgram();    /* in case it isn't running */
11525     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11526         gameMode = MachinePlaysWhite;
11527         ResetClocks();
11528     } else
11529     gameMode = MachinePlaysWhite;
11530     pausing = FALSE;
11531     ModeHighlight();
11532     SetGameInfo();
11533     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11534     DisplayTitle(buf);
11535     if (first.sendName) {
11536       sprintf(buf, "name %s\n", gameInfo.black);
11537       SendToProgram(buf, &first);
11538     }
11539     if (first.sendTime) {
11540       if (first.useColors) {
11541         SendToProgram("black\n", &first); /*gnu kludge*/
11542       }
11543       SendTimeRemaining(&first, TRUE);
11544     }
11545     if (first.useColors) {
11546       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11547     }
11548     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11549     SetMachineThinkingEnables();
11550     first.maybeThinking = TRUE;
11551     StartClocks();
11552     firstMove = FALSE;
11553
11554     if (appData.autoFlipView && !flipView) {
11555       flipView = !flipView;
11556       DrawPosition(FALSE, NULL);
11557       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11558     }
11559
11560     if(bookHit) { // [HGM] book: simulate book reply
11561         static char bookMove[MSG_SIZ]; // a bit generous?
11562
11563         programStats.nodes = programStats.depth = programStats.time = 
11564         programStats.score = programStats.got_only_move = 0;
11565         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11566
11567         strcpy(bookMove, "move ");
11568         strcat(bookMove, bookHit);
11569         HandleMachineMove(bookMove, &first);
11570     }
11571 }
11572
11573 void
11574 MachineBlackEvent()
11575 {
11576     char buf[MSG_SIZ];
11577    char *bookHit = NULL;
11578
11579     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11580         return;
11581
11582
11583     if (gameMode == PlayFromGameFile || 
11584         gameMode == TwoMachinesPlay  || 
11585         gameMode == Training         || 
11586         gameMode == AnalyzeMode      || 
11587         gameMode == EndOfGame)
11588         EditGameEvent();
11589
11590     if (gameMode == EditPosition) 
11591         EditPositionDone(TRUE);
11592
11593     if (WhiteOnMove(currentMove)) {
11594         DisplayError(_("It is not Black's turn"), 0);
11595         return;
11596     }
11597     
11598     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11599       ExitAnalyzeMode();
11600
11601     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11602         gameMode == AnalyzeFile)
11603         TruncateGame();
11604
11605     ResurrectChessProgram();    /* in case it isn't running */
11606     gameMode = MachinePlaysBlack;
11607     pausing = FALSE;
11608     ModeHighlight();
11609     SetGameInfo();
11610     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11611     DisplayTitle(buf);
11612     if (first.sendName) {
11613       sprintf(buf, "name %s\n", gameInfo.white);
11614       SendToProgram(buf, &first);
11615     }
11616     if (first.sendTime) {
11617       if (first.useColors) {
11618         SendToProgram("white\n", &first); /*gnu kludge*/
11619       }
11620       SendTimeRemaining(&first, FALSE);
11621     }
11622     if (first.useColors) {
11623       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11624     }
11625     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11626     SetMachineThinkingEnables();
11627     first.maybeThinking = TRUE;
11628     StartClocks();
11629
11630     if (appData.autoFlipView && flipView) {
11631       flipView = !flipView;
11632       DrawPosition(FALSE, NULL);
11633       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11634     }
11635     if(bookHit) { // [HGM] book: simulate book reply
11636         static char bookMove[MSG_SIZ]; // a bit generous?
11637
11638         programStats.nodes = programStats.depth = programStats.time = 
11639         programStats.score = programStats.got_only_move = 0;
11640         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11641
11642         strcpy(bookMove, "move ");
11643         strcat(bookMove, bookHit);
11644         HandleMachineMove(bookMove, &first);
11645     }
11646 }
11647
11648
11649 void
11650 DisplayTwoMachinesTitle()
11651 {
11652     char buf[MSG_SIZ];
11653     if (appData.matchGames > 0) {
11654         if (first.twoMachinesColor[0] == 'w') {
11655             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11656                     gameInfo.white, gameInfo.black,
11657                     first.matchWins, second.matchWins,
11658                     matchGame - 1 - (first.matchWins + second.matchWins));
11659         } else {
11660             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11661                     gameInfo.white, gameInfo.black,
11662                     second.matchWins, first.matchWins,
11663                     matchGame - 1 - (first.matchWins + second.matchWins));
11664         }
11665     } else {
11666         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11667     }
11668     DisplayTitle(buf);
11669 }
11670
11671 void
11672 TwoMachinesEvent P((void))
11673 {
11674     int i;
11675     char buf[MSG_SIZ];
11676     ChessProgramState *onmove;
11677     char *bookHit = NULL;
11678     
11679     if (appData.noChessProgram) return;
11680
11681     switch (gameMode) {
11682       case TwoMachinesPlay:
11683         return;
11684       case MachinePlaysWhite:
11685       case MachinePlaysBlack:
11686         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11687             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11688             return;
11689         }
11690         /* fall through */
11691       case BeginningOfGame:
11692       case PlayFromGameFile:
11693       case EndOfGame:
11694         EditGameEvent();
11695         if (gameMode != EditGame) return;
11696         break;
11697       case EditPosition:
11698         EditPositionDone(TRUE);
11699         break;
11700       case AnalyzeMode:
11701       case AnalyzeFile:
11702         ExitAnalyzeMode();
11703         break;
11704       case EditGame:
11705       default:
11706         break;
11707     }
11708
11709 //    forwardMostMove = currentMove;
11710     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11711     ResurrectChessProgram();    /* in case first program isn't running */
11712
11713     if (second.pr == NULL) {
11714         StartChessProgram(&second);
11715         if (second.protocolVersion == 1) {
11716           TwoMachinesEventIfReady();
11717         } else {
11718           /* kludge: allow timeout for initial "feature" command */
11719           FreezeUI();
11720           DisplayMessage("", _("Starting second chess program"));
11721           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11722         }
11723         return;
11724     }
11725     DisplayMessage("", "");
11726     InitChessProgram(&second, FALSE);
11727     SendToProgram("force\n", &second);
11728     if (startedFromSetupPosition) {
11729         SendBoard(&second, backwardMostMove);
11730     if (appData.debugMode) {
11731         fprintf(debugFP, "Two Machines\n");
11732     }
11733     }
11734     for (i = backwardMostMove; i < forwardMostMove; i++) {
11735         SendMoveToProgram(i, &second);
11736     }
11737
11738     gameMode = TwoMachinesPlay;
11739     pausing = FALSE;
11740     ModeHighlight();
11741     SetGameInfo();
11742     DisplayTwoMachinesTitle();
11743     firstMove = TRUE;
11744     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11745         onmove = &first;
11746     } else {
11747         onmove = &second;
11748     }
11749
11750     SendToProgram(first.computerString, &first);
11751     if (first.sendName) {
11752       sprintf(buf, "name %s\n", second.tidy);
11753       SendToProgram(buf, &first);
11754     }
11755     SendToProgram(second.computerString, &second);
11756     if (second.sendName) {
11757       sprintf(buf, "name %s\n", first.tidy);
11758       SendToProgram(buf, &second);
11759     }
11760
11761     ResetClocks();
11762     if (!first.sendTime || !second.sendTime) {
11763         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11764         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11765     }
11766     if (onmove->sendTime) {
11767       if (onmove->useColors) {
11768         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11769       }
11770       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11771     }
11772     if (onmove->useColors) {
11773       SendToProgram(onmove->twoMachinesColor, onmove);
11774     }
11775     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11776 //    SendToProgram("go\n", onmove);
11777     onmove->maybeThinking = TRUE;
11778     SetMachineThinkingEnables();
11779
11780     StartClocks();
11781
11782     if(bookHit) { // [HGM] book: simulate book reply
11783         static char bookMove[MSG_SIZ]; // a bit generous?
11784
11785         programStats.nodes = programStats.depth = programStats.time = 
11786         programStats.score = programStats.got_only_move = 0;
11787         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11788
11789         strcpy(bookMove, "move ");
11790         strcat(bookMove, bookHit);
11791         savedMessage = bookMove; // args for deferred call
11792         savedState = onmove;
11793         ScheduleDelayedEvent(DeferredBookMove, 1);
11794     }
11795 }
11796
11797 void
11798 TrainingEvent()
11799 {
11800     if (gameMode == Training) {
11801       SetTrainingModeOff();
11802       gameMode = PlayFromGameFile;
11803       DisplayMessage("", _("Training mode off"));
11804     } else {
11805       gameMode = Training;
11806       animateTraining = appData.animate;
11807
11808       /* make sure we are not already at the end of the game */
11809       if (currentMove < forwardMostMove) {
11810         SetTrainingModeOn();
11811         DisplayMessage("", _("Training mode on"));
11812       } else {
11813         gameMode = PlayFromGameFile;
11814         DisplayError(_("Already at end of game"), 0);
11815       }
11816     }
11817     ModeHighlight();
11818 }
11819
11820 void
11821 IcsClientEvent()
11822 {
11823     if (!appData.icsActive) return;
11824     switch (gameMode) {
11825       case IcsPlayingWhite:
11826       case IcsPlayingBlack:
11827       case IcsObserving:
11828       case IcsIdle:
11829       case BeginningOfGame:
11830       case IcsExamining:
11831         return;
11832
11833       case EditGame:
11834         break;
11835
11836       case EditPosition:
11837         EditPositionDone(TRUE);
11838         break;
11839
11840       case AnalyzeMode:
11841       case AnalyzeFile:
11842         ExitAnalyzeMode();
11843         break;
11844         
11845       default:
11846         EditGameEvent();
11847         break;
11848     }
11849
11850     gameMode = IcsIdle;
11851     ModeHighlight();
11852     return;
11853 }
11854
11855
11856 void
11857 EditGameEvent()
11858 {
11859     int i;
11860
11861     switch (gameMode) {
11862       case Training:
11863         SetTrainingModeOff();
11864         break;
11865       case MachinePlaysWhite:
11866       case MachinePlaysBlack:
11867       case BeginningOfGame:
11868         SendToProgram("force\n", &first);
11869         SetUserThinkingEnables();
11870         break;
11871       case PlayFromGameFile:
11872         (void) StopLoadGameTimer();
11873         if (gameFileFP != NULL) {
11874             gameFileFP = NULL;
11875         }
11876         break;
11877       case EditPosition:
11878         EditPositionDone(TRUE);
11879         break;
11880       case AnalyzeMode:
11881       case AnalyzeFile:
11882         ExitAnalyzeMode();
11883         SendToProgram("force\n", &first);
11884         break;
11885       case TwoMachinesPlay:
11886         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11887         ResurrectChessProgram();
11888         SetUserThinkingEnables();
11889         break;
11890       case EndOfGame:
11891         ResurrectChessProgram();
11892         break;
11893       case IcsPlayingBlack:
11894       case IcsPlayingWhite:
11895         DisplayError(_("Warning: You are still playing a game"), 0);
11896         break;
11897       case IcsObserving:
11898         DisplayError(_("Warning: You are still observing a game"), 0);
11899         break;
11900       case IcsExamining:
11901         DisplayError(_("Warning: You are still examining a game"), 0);
11902         break;
11903       case IcsIdle:
11904         break;
11905       case EditGame:
11906       default:
11907         return;
11908     }
11909     
11910     pausing = FALSE;
11911     StopClocks();
11912     first.offeredDraw = second.offeredDraw = 0;
11913
11914     if (gameMode == PlayFromGameFile) {
11915         whiteTimeRemaining = timeRemaining[0][currentMove];
11916         blackTimeRemaining = timeRemaining[1][currentMove];
11917         DisplayTitle("");
11918     }
11919
11920     if (gameMode == MachinePlaysWhite ||
11921         gameMode == MachinePlaysBlack ||
11922         gameMode == TwoMachinesPlay ||
11923         gameMode == EndOfGame) {
11924         i = forwardMostMove;
11925         while (i > currentMove) {
11926             SendToProgram("undo\n", &first);
11927             i--;
11928         }
11929         whiteTimeRemaining = timeRemaining[0][currentMove];
11930         blackTimeRemaining = timeRemaining[1][currentMove];
11931         DisplayBothClocks();
11932         if (whiteFlag || blackFlag) {
11933             whiteFlag = blackFlag = 0;
11934         }
11935         DisplayTitle("");
11936     }           
11937     
11938     gameMode = EditGame;
11939     ModeHighlight();
11940     SetGameInfo();
11941 }
11942
11943
11944 void
11945 EditPositionEvent()
11946 {
11947     if (gameMode == EditPosition) {
11948         EditGameEvent();
11949         return;
11950     }
11951     
11952     EditGameEvent();
11953     if (gameMode != EditGame) return;
11954     
11955     gameMode = EditPosition;
11956     ModeHighlight();
11957     SetGameInfo();
11958     if (currentMove > 0)
11959       CopyBoard(boards[0], boards[currentMove]);
11960     
11961     blackPlaysFirst = !WhiteOnMove(currentMove);
11962     ResetClocks();
11963     currentMove = forwardMostMove = backwardMostMove = 0;
11964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11965     DisplayMove(-1);
11966 }
11967
11968 void
11969 ExitAnalyzeMode()
11970 {
11971     /* [DM] icsEngineAnalyze - possible call from other functions */
11972     if (appData.icsEngineAnalyze) {
11973         appData.icsEngineAnalyze = FALSE;
11974
11975         DisplayMessage("",_("Close ICS engine analyze..."));
11976     }
11977     if (first.analysisSupport && first.analyzing) {
11978       SendToProgram("exit\n", &first);
11979       first.analyzing = FALSE;
11980     }
11981     thinkOutput[0] = NULLCHAR;
11982 }
11983
11984 void
11985 EditPositionDone(Boolean fakeRights)
11986 {
11987     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11988
11989     startedFromSetupPosition = TRUE;
11990     InitChessProgram(&first, FALSE);
11991     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11992       boards[0][EP_STATUS] = EP_NONE;
11993       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11994     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11995         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11996         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11997       } else boards[0][CASTLING][2] = NoRights;
11998     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11999         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12000         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12001       } else boards[0][CASTLING][5] = NoRights;
12002     }
12003     SendToProgram("force\n", &first);
12004     if (blackPlaysFirst) {
12005         strcpy(moveList[0], "");
12006         strcpy(parseList[0], "");
12007         currentMove = forwardMostMove = backwardMostMove = 1;
12008         CopyBoard(boards[1], boards[0]);
12009     } else {
12010         currentMove = forwardMostMove = backwardMostMove = 0;
12011     }
12012     SendBoard(&first, forwardMostMove);
12013     if (appData.debugMode) {
12014         fprintf(debugFP, "EditPosDone\n");
12015     }
12016     DisplayTitle("");
12017     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12018     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12019     gameMode = EditGame;
12020     ModeHighlight();
12021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12022     ClearHighlights(); /* [AS] */
12023 }
12024
12025 /* Pause for `ms' milliseconds */
12026 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12027 void
12028 TimeDelay(ms)
12029      long ms;
12030 {
12031     TimeMark m1, m2;
12032
12033     GetTimeMark(&m1);
12034     do {
12035         GetTimeMark(&m2);
12036     } while (SubtractTimeMarks(&m2, &m1) < ms);
12037 }
12038
12039 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12040 void
12041 SendMultiLineToICS(buf)
12042      char *buf;
12043 {
12044     char temp[MSG_SIZ+1], *p;
12045     int len;
12046
12047     len = strlen(buf);
12048     if (len > MSG_SIZ)
12049       len = MSG_SIZ;
12050   
12051     strncpy(temp, buf, len);
12052     temp[len] = 0;
12053
12054     p = temp;
12055     while (*p) {
12056         if (*p == '\n' || *p == '\r')
12057           *p = ' ';
12058         ++p;
12059     }
12060
12061     strcat(temp, "\n");
12062     SendToICS(temp);
12063     SendToPlayer(temp, strlen(temp));
12064 }
12065
12066 void
12067 SetWhiteToPlayEvent()
12068 {
12069     if (gameMode == EditPosition) {
12070         blackPlaysFirst = FALSE;
12071         DisplayBothClocks();    /* works because currentMove is 0 */
12072     } else if (gameMode == IcsExamining) {
12073         SendToICS(ics_prefix);
12074         SendToICS("tomove white\n");
12075     }
12076 }
12077
12078 void
12079 SetBlackToPlayEvent()
12080 {
12081     if (gameMode == EditPosition) {
12082         blackPlaysFirst = TRUE;
12083         currentMove = 1;        /* kludge */
12084         DisplayBothClocks();
12085         currentMove = 0;
12086     } else if (gameMode == IcsExamining) {
12087         SendToICS(ics_prefix);
12088         SendToICS("tomove black\n");
12089     }
12090 }
12091
12092 void
12093 EditPositionMenuEvent(selection, x, y)
12094      ChessSquare selection;
12095      int x, y;
12096 {
12097     char buf[MSG_SIZ];
12098     ChessSquare piece = boards[0][y][x];
12099
12100     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12101
12102     switch (selection) {
12103       case ClearBoard:
12104         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12105             SendToICS(ics_prefix);
12106             SendToICS("bsetup clear\n");
12107         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12108             SendToICS(ics_prefix);
12109             SendToICS("clearboard\n");
12110         } else {
12111             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12112                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12113                 for (y = 0; y < BOARD_HEIGHT; y++) {
12114                     if (gameMode == IcsExamining) {
12115                         if (boards[currentMove][y][x] != EmptySquare) {
12116                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12117                                     AAA + x, ONE + y);
12118                             SendToICS(buf);
12119                         }
12120                     } else {
12121                         boards[0][y][x] = p;
12122                     }
12123                 }
12124             }
12125         }
12126         if (gameMode == EditPosition) {
12127             DrawPosition(FALSE, boards[0]);
12128         }
12129         break;
12130
12131       case WhitePlay:
12132         SetWhiteToPlayEvent();
12133         break;
12134
12135       case BlackPlay:
12136         SetBlackToPlayEvent();
12137         break;
12138
12139       case EmptySquare:
12140         if (gameMode == IcsExamining) {
12141             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12142             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12143             SendToICS(buf);
12144         } else {
12145             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12146                 if(x == BOARD_LEFT-2) {
12147                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12148                     boards[0][y][1] = 0;
12149                 } else
12150                 if(x == BOARD_RGHT+1) {
12151                     if(y >= gameInfo.holdingsSize) break;
12152                     boards[0][y][BOARD_WIDTH-2] = 0;
12153                 } else break;
12154             }
12155             boards[0][y][x] = EmptySquare;
12156             DrawPosition(FALSE, boards[0]);
12157         }
12158         break;
12159
12160       case PromotePiece:
12161         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12162            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12163             selection = (ChessSquare) (PROMOTED piece);
12164         } else if(piece == EmptySquare) selection = WhiteSilver;
12165         else selection = (ChessSquare)((int)piece - 1);
12166         goto defaultlabel;
12167
12168       case DemotePiece:
12169         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12170            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12171             selection = (ChessSquare) (DEMOTED piece);
12172         } else if(piece == EmptySquare) selection = BlackSilver;
12173         else selection = (ChessSquare)((int)piece + 1);       
12174         goto defaultlabel;
12175
12176       case WhiteQueen:
12177       case BlackQueen:
12178         if(gameInfo.variant == VariantShatranj ||
12179            gameInfo.variant == VariantXiangqi  ||
12180            gameInfo.variant == VariantCourier  ||
12181            gameInfo.variant == VariantMakruk     )
12182             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12183         goto defaultlabel;
12184
12185       case WhiteKing:
12186       case BlackKing:
12187         if(gameInfo.variant == VariantXiangqi)
12188             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12189         if(gameInfo.variant == VariantKnightmate)
12190             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12191       default:
12192         defaultlabel:
12193         if (gameMode == IcsExamining) {
12194             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12195             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12196                     PieceToChar(selection), AAA + x, ONE + y);
12197             SendToICS(buf);
12198         } else {
12199             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12200                 int n;
12201                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12202                     n = PieceToNumber(selection - BlackPawn);
12203                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12204                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12205                     boards[0][BOARD_HEIGHT-1-n][1]++;
12206                 } else
12207                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12208                     n = PieceToNumber(selection);
12209                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12210                     boards[0][n][BOARD_WIDTH-1] = selection;
12211                     boards[0][n][BOARD_WIDTH-2]++;
12212                 }
12213             } else
12214             boards[0][y][x] = selection;
12215             DrawPosition(TRUE, boards[0]);
12216         }
12217         break;
12218     }
12219 }
12220
12221
12222 void
12223 DropMenuEvent(selection, x, y)
12224      ChessSquare selection;
12225      int x, y;
12226 {
12227     ChessMove moveType;
12228
12229     switch (gameMode) {
12230       case IcsPlayingWhite:
12231       case MachinePlaysBlack:
12232         if (!WhiteOnMove(currentMove)) {
12233             DisplayMoveError(_("It is Black's turn"));
12234             return;
12235         }
12236         moveType = WhiteDrop;
12237         break;
12238       case IcsPlayingBlack:
12239       case MachinePlaysWhite:
12240         if (WhiteOnMove(currentMove)) {
12241             DisplayMoveError(_("It is White's turn"));
12242             return;
12243         }
12244         moveType = BlackDrop;
12245         break;
12246       case EditGame:
12247         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12248         break;
12249       default:
12250         return;
12251     }
12252
12253     if (moveType == BlackDrop && selection < BlackPawn) {
12254       selection = (ChessSquare) ((int) selection
12255                                  + (int) BlackPawn - (int) WhitePawn);
12256     }
12257     if (boards[currentMove][y][x] != EmptySquare) {
12258         DisplayMoveError(_("That square is occupied"));
12259         return;
12260     }
12261
12262     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12263 }
12264
12265 void
12266 AcceptEvent()
12267 {
12268     /* Accept a pending offer of any kind from opponent */
12269     
12270     if (appData.icsActive) {
12271         SendToICS(ics_prefix);
12272         SendToICS("accept\n");
12273     } else if (cmailMsgLoaded) {
12274         if (currentMove == cmailOldMove &&
12275             commentList[cmailOldMove] != NULL &&
12276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12277                    "Black offers a draw" : "White offers a draw")) {
12278             TruncateGame();
12279             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12280             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12281         } else {
12282             DisplayError(_("There is no pending offer on this move"), 0);
12283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12284         }
12285     } else {
12286         /* Not used for offers from chess program */
12287     }
12288 }
12289
12290 void
12291 DeclineEvent()
12292 {
12293     /* Decline a pending offer of any kind from opponent */
12294     
12295     if (appData.icsActive) {
12296         SendToICS(ics_prefix);
12297         SendToICS("decline\n");
12298     } else if (cmailMsgLoaded) {
12299         if (currentMove == cmailOldMove &&
12300             commentList[cmailOldMove] != NULL &&
12301             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12302                    "Black offers a draw" : "White offers a draw")) {
12303 #ifdef NOTDEF
12304             AppendComment(cmailOldMove, "Draw declined", TRUE);
12305             DisplayComment(cmailOldMove - 1, "Draw declined");
12306 #endif /*NOTDEF*/
12307         } else {
12308             DisplayError(_("There is no pending offer on this move"), 0);
12309         }
12310     } else {
12311         /* Not used for offers from chess program */
12312     }
12313 }
12314
12315 void
12316 RematchEvent()
12317 {
12318     /* Issue ICS rematch command */
12319     if (appData.icsActive) {
12320         SendToICS(ics_prefix);
12321         SendToICS("rematch\n");
12322     }
12323 }
12324
12325 void
12326 CallFlagEvent()
12327 {
12328     /* Call your opponent's flag (claim a win on time) */
12329     if (appData.icsActive) {
12330         SendToICS(ics_prefix);
12331         SendToICS("flag\n");
12332     } else {
12333         switch (gameMode) {
12334           default:
12335             return;
12336           case MachinePlaysWhite:
12337             if (whiteFlag) {
12338                 if (blackFlag)
12339                   GameEnds(GameIsDrawn, "Both players ran out of time",
12340                            GE_PLAYER);
12341                 else
12342                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12343             } else {
12344                 DisplayError(_("Your opponent is not out of time"), 0);
12345             }
12346             break;
12347           case MachinePlaysBlack:
12348             if (blackFlag) {
12349                 if (whiteFlag)
12350                   GameEnds(GameIsDrawn, "Both players ran out of time",
12351                            GE_PLAYER);
12352                 else
12353                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12354             } else {
12355                 DisplayError(_("Your opponent is not out of time"), 0);
12356             }
12357             break;
12358         }
12359     }
12360 }
12361
12362 void
12363 DrawEvent()
12364 {
12365     /* Offer draw or accept pending draw offer from opponent */
12366     
12367     if (appData.icsActive) {
12368         /* Note: tournament rules require draw offers to be
12369            made after you make your move but before you punch
12370            your clock.  Currently ICS doesn't let you do that;
12371            instead, you immediately punch your clock after making
12372            a move, but you can offer a draw at any time. */
12373         
12374         SendToICS(ics_prefix);
12375         SendToICS("draw\n");
12376         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12377     } else if (cmailMsgLoaded) {
12378         if (currentMove == cmailOldMove &&
12379             commentList[cmailOldMove] != NULL &&
12380             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12381                    "Black offers a draw" : "White offers a draw")) {
12382             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12383             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12384         } else if (currentMove == cmailOldMove + 1) {
12385             char *offer = WhiteOnMove(cmailOldMove) ?
12386               "White offers a draw" : "Black offers a draw";
12387             AppendComment(currentMove, offer, TRUE);
12388             DisplayComment(currentMove - 1, offer);
12389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12390         } else {
12391             DisplayError(_("You must make your move before offering a draw"), 0);
12392             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12393         }
12394     } else if (first.offeredDraw) {
12395         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12396     } else {
12397         if (first.sendDrawOffers) {
12398             SendToProgram("draw\n", &first);
12399             userOfferedDraw = TRUE;
12400         }
12401     }
12402 }
12403
12404 void
12405 AdjournEvent()
12406 {
12407     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12408     
12409     if (appData.icsActive) {
12410         SendToICS(ics_prefix);
12411         SendToICS("adjourn\n");
12412     } else {
12413         /* Currently GNU Chess doesn't offer or accept Adjourns */
12414     }
12415 }
12416
12417
12418 void
12419 AbortEvent()
12420 {
12421     /* Offer Abort or accept pending Abort offer from opponent */
12422     
12423     if (appData.icsActive) {
12424         SendToICS(ics_prefix);
12425         SendToICS("abort\n");
12426     } else {
12427         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12428     }
12429 }
12430
12431 void
12432 ResignEvent()
12433 {
12434     /* Resign.  You can do this even if it's not your turn. */
12435     
12436     if (appData.icsActive) {
12437         SendToICS(ics_prefix);
12438         SendToICS("resign\n");
12439     } else {
12440         switch (gameMode) {
12441           case MachinePlaysWhite:
12442             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12443             break;
12444           case MachinePlaysBlack:
12445             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12446             break;
12447           case EditGame:
12448             if (cmailMsgLoaded) {
12449                 TruncateGame();
12450                 if (WhiteOnMove(cmailOldMove)) {
12451                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12452                 } else {
12453                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12454                 }
12455                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12456             }
12457             break;
12458           default:
12459             break;
12460         }
12461     }
12462 }
12463
12464
12465 void
12466 StopObservingEvent()
12467 {
12468     /* Stop observing current games */
12469     SendToICS(ics_prefix);
12470     SendToICS("unobserve\n");
12471 }
12472
12473 void
12474 StopExaminingEvent()
12475 {
12476     /* Stop observing current game */
12477     SendToICS(ics_prefix);
12478     SendToICS("unexamine\n");
12479 }
12480
12481 void
12482 ForwardInner(target)
12483      int target;
12484 {
12485     int limit;
12486
12487     if (appData.debugMode)
12488         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12489                 target, currentMove, forwardMostMove);
12490
12491     if (gameMode == EditPosition)
12492       return;
12493
12494     if (gameMode == PlayFromGameFile && !pausing)
12495       PauseEvent();
12496     
12497     if (gameMode == IcsExamining && pausing)
12498       limit = pauseExamForwardMostMove;
12499     else
12500       limit = forwardMostMove;
12501     
12502     if (target > limit) target = limit;
12503
12504     if (target > 0 && moveList[target - 1][0]) {
12505         int fromX, fromY, toX, toY;
12506         toX = moveList[target - 1][2] - AAA;
12507         toY = moveList[target - 1][3] - ONE;
12508         if (moveList[target - 1][1] == '@') {
12509             if (appData.highlightLastMove) {
12510                 SetHighlights(-1, -1, toX, toY);
12511             }
12512         } else {
12513             fromX = moveList[target - 1][0] - AAA;
12514             fromY = moveList[target - 1][1] - ONE;
12515             if (target == currentMove + 1) {
12516                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12517             }
12518             if (appData.highlightLastMove) {
12519                 SetHighlights(fromX, fromY, toX, toY);
12520             }
12521         }
12522     }
12523     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12524         gameMode == Training || gameMode == PlayFromGameFile || 
12525         gameMode == AnalyzeFile) {
12526         while (currentMove < target) {
12527             SendMoveToProgram(currentMove++, &first);
12528         }
12529     } else {
12530         currentMove = target;
12531     }
12532     
12533     if (gameMode == EditGame || gameMode == EndOfGame) {
12534         whiteTimeRemaining = timeRemaining[0][currentMove];
12535         blackTimeRemaining = timeRemaining[1][currentMove];
12536     }
12537     DisplayBothClocks();
12538     DisplayMove(currentMove - 1);
12539     DrawPosition(FALSE, boards[currentMove]);
12540     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12541     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12542         DisplayComment(currentMove - 1, commentList[currentMove]);
12543     }
12544 }
12545
12546
12547 void
12548 ForwardEvent()
12549 {
12550     if (gameMode == IcsExamining && !pausing) {
12551         SendToICS(ics_prefix);
12552         SendToICS("forward\n");
12553     } else {
12554         ForwardInner(currentMove + 1);
12555     }
12556 }
12557
12558 void
12559 ToEndEvent()
12560 {
12561     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12562         /* to optimze, we temporarily turn off analysis mode while we feed
12563          * the remaining moves to the engine. Otherwise we get analysis output
12564          * after each move.
12565          */ 
12566         if (first.analysisSupport) {
12567           SendToProgram("exit\nforce\n", &first);
12568           first.analyzing = FALSE;
12569         }
12570     }
12571         
12572     if (gameMode == IcsExamining && !pausing) {
12573         SendToICS(ics_prefix);
12574         SendToICS("forward 999999\n");
12575     } else {
12576         ForwardInner(forwardMostMove);
12577     }
12578
12579     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12580         /* we have fed all the moves, so reactivate analysis mode */
12581         SendToProgram("analyze\n", &first);
12582         first.analyzing = TRUE;
12583         /*first.maybeThinking = TRUE;*/
12584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12585     }
12586 }
12587
12588 void
12589 BackwardInner(target)
12590      int target;
12591 {
12592     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12593
12594     if (appData.debugMode)
12595         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12596                 target, currentMove, forwardMostMove);
12597
12598     if (gameMode == EditPosition) return;
12599     if (currentMove <= backwardMostMove) {
12600         ClearHighlights();
12601         DrawPosition(full_redraw, boards[currentMove]);
12602         return;
12603     }
12604     if (gameMode == PlayFromGameFile && !pausing)
12605       PauseEvent();
12606     
12607     if (moveList[target][0]) {
12608         int fromX, fromY, toX, toY;
12609         toX = moveList[target][2] - AAA;
12610         toY = moveList[target][3] - ONE;
12611         if (moveList[target][1] == '@') {
12612             if (appData.highlightLastMove) {
12613                 SetHighlights(-1, -1, toX, toY);
12614             }
12615         } else {
12616             fromX = moveList[target][0] - AAA;
12617             fromY = moveList[target][1] - ONE;
12618             if (target == currentMove - 1) {
12619                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12620             }
12621             if (appData.highlightLastMove) {
12622                 SetHighlights(fromX, fromY, toX, toY);
12623             }
12624         }
12625     }
12626     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12627         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12628         while (currentMove > target) {
12629             SendToProgram("undo\n", &first);
12630             currentMove--;
12631         }
12632     } else {
12633         currentMove = target;
12634     }
12635     
12636     if (gameMode == EditGame || gameMode == EndOfGame) {
12637         whiteTimeRemaining = timeRemaining[0][currentMove];
12638         blackTimeRemaining = timeRemaining[1][currentMove];
12639     }
12640     DisplayBothClocks();
12641     DisplayMove(currentMove - 1);
12642     DrawPosition(full_redraw, boards[currentMove]);
12643     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12644     // [HGM] PV info: routine tests if comment empty
12645     DisplayComment(currentMove - 1, commentList[currentMove]);
12646 }
12647
12648 void
12649 BackwardEvent()
12650 {
12651     if (gameMode == IcsExamining && !pausing) {
12652         SendToICS(ics_prefix);
12653         SendToICS("backward\n");
12654     } else {
12655         BackwardInner(currentMove - 1);
12656     }
12657 }
12658
12659 void
12660 ToStartEvent()
12661 {
12662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12663         /* to optimize, we temporarily turn off analysis mode while we undo
12664          * all the moves. Otherwise we get analysis output after each undo.
12665          */ 
12666         if (first.analysisSupport) {
12667           SendToProgram("exit\nforce\n", &first);
12668           first.analyzing = FALSE;
12669         }
12670     }
12671
12672     if (gameMode == IcsExamining && !pausing) {
12673         SendToICS(ics_prefix);
12674         SendToICS("backward 999999\n");
12675     } else {
12676         BackwardInner(backwardMostMove);
12677     }
12678
12679     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12680         /* we have fed all the moves, so reactivate analysis mode */
12681         SendToProgram("analyze\n", &first);
12682         first.analyzing = TRUE;
12683         /*first.maybeThinking = TRUE;*/
12684         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12685     }
12686 }
12687
12688 void
12689 ToNrEvent(int to)
12690 {
12691   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12692   if (to >= forwardMostMove) to = forwardMostMove;
12693   if (to <= backwardMostMove) to = backwardMostMove;
12694   if (to < currentMove) {
12695     BackwardInner(to);
12696   } else {
12697     ForwardInner(to);
12698   }
12699 }
12700
12701 void
12702 RevertEvent(Boolean annotate)
12703 {
12704     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12705         return;
12706     }
12707     if (gameMode != IcsExamining) {
12708         DisplayError(_("You are not examining a game"), 0);
12709         return;
12710     }
12711     if (pausing) {
12712         DisplayError(_("You can't revert while pausing"), 0);
12713         return;
12714     }
12715     SendToICS(ics_prefix);
12716     SendToICS("revert\n");
12717 }
12718
12719 void
12720 RetractMoveEvent()
12721 {
12722     switch (gameMode) {
12723       case MachinePlaysWhite:
12724       case MachinePlaysBlack:
12725         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12726             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12727             return;
12728         }
12729         if (forwardMostMove < 2) return;
12730         currentMove = forwardMostMove = forwardMostMove - 2;
12731         whiteTimeRemaining = timeRemaining[0][currentMove];
12732         blackTimeRemaining = timeRemaining[1][currentMove];
12733         DisplayBothClocks();
12734         DisplayMove(currentMove - 1);
12735         ClearHighlights();/*!! could figure this out*/
12736         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12737         SendToProgram("remove\n", &first);
12738         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12739         break;
12740
12741       case BeginningOfGame:
12742       default:
12743         break;
12744
12745       case IcsPlayingWhite:
12746       case IcsPlayingBlack:
12747         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12748             SendToICS(ics_prefix);
12749             SendToICS("takeback 2\n");
12750         } else {
12751             SendToICS(ics_prefix);
12752             SendToICS("takeback 1\n");
12753         }
12754         break;
12755     }
12756 }
12757
12758 void
12759 MoveNowEvent()
12760 {
12761     ChessProgramState *cps;
12762
12763     switch (gameMode) {
12764       case MachinePlaysWhite:
12765         if (!WhiteOnMove(forwardMostMove)) {
12766             DisplayError(_("It is your turn"), 0);
12767             return;
12768         }
12769         cps = &first;
12770         break;
12771       case MachinePlaysBlack:
12772         if (WhiteOnMove(forwardMostMove)) {
12773             DisplayError(_("It is your turn"), 0);
12774             return;
12775         }
12776         cps = &first;
12777         break;
12778       case TwoMachinesPlay:
12779         if (WhiteOnMove(forwardMostMove) ==
12780             (first.twoMachinesColor[0] == 'w')) {
12781             cps = &first;
12782         } else {
12783             cps = &second;
12784         }
12785         break;
12786       case BeginningOfGame:
12787       default:
12788         return;
12789     }
12790     SendToProgram("?\n", cps);
12791 }
12792
12793 void
12794 TruncateGameEvent()
12795 {
12796     EditGameEvent();
12797     if (gameMode != EditGame) return;
12798     TruncateGame();
12799 }
12800
12801 void
12802 TruncateGame()
12803 {
12804     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12805     if (forwardMostMove > currentMove) {
12806         if (gameInfo.resultDetails != NULL) {
12807             free(gameInfo.resultDetails);
12808             gameInfo.resultDetails = NULL;
12809             gameInfo.result = GameUnfinished;
12810         }
12811         forwardMostMove = currentMove;
12812         HistorySet(parseList, backwardMostMove, forwardMostMove,
12813                    currentMove-1);
12814     }
12815 }
12816
12817 void
12818 HintEvent()
12819 {
12820     if (appData.noChessProgram) return;
12821     switch (gameMode) {
12822       case MachinePlaysWhite:
12823         if (WhiteOnMove(forwardMostMove)) {
12824             DisplayError(_("Wait until your turn"), 0);
12825             return;
12826         }
12827         break;
12828       case BeginningOfGame:
12829       case MachinePlaysBlack:
12830         if (!WhiteOnMove(forwardMostMove)) {
12831             DisplayError(_("Wait until your turn"), 0);
12832             return;
12833         }
12834         break;
12835       default:
12836         DisplayError(_("No hint available"), 0);
12837         return;
12838     }
12839     SendToProgram("hint\n", &first);
12840     hintRequested = TRUE;
12841 }
12842
12843 void
12844 BookEvent()
12845 {
12846     if (appData.noChessProgram) return;
12847     switch (gameMode) {
12848       case MachinePlaysWhite:
12849         if (WhiteOnMove(forwardMostMove)) {
12850             DisplayError(_("Wait until your turn"), 0);
12851             return;
12852         }
12853         break;
12854       case BeginningOfGame:
12855       case MachinePlaysBlack:
12856         if (!WhiteOnMove(forwardMostMove)) {
12857             DisplayError(_("Wait until your turn"), 0);
12858             return;
12859         }
12860         break;
12861       case EditPosition:
12862         EditPositionDone(TRUE);
12863         break;
12864       case TwoMachinesPlay:
12865         return;
12866       default:
12867         break;
12868     }
12869     SendToProgram("bk\n", &first);
12870     bookOutput[0] = NULLCHAR;
12871     bookRequested = TRUE;
12872 }
12873
12874 void
12875 AboutGameEvent()
12876 {
12877     char *tags = PGNTags(&gameInfo);
12878     TagsPopUp(tags, CmailMsg());
12879     free(tags);
12880 }
12881
12882 /* end button procedures */
12883
12884 void
12885 PrintPosition(fp, move)
12886      FILE *fp;
12887      int move;
12888 {
12889     int i, j;
12890     
12891     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12892         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12893             char c = PieceToChar(boards[move][i][j]);
12894             fputc(c == 'x' ? '.' : c, fp);
12895             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12896         }
12897     }
12898     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12899       fprintf(fp, "white to play\n");
12900     else
12901       fprintf(fp, "black to play\n");
12902 }
12903
12904 void
12905 PrintOpponents(fp)
12906      FILE *fp;
12907 {
12908     if (gameInfo.white != NULL) {
12909         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12910     } else {
12911         fprintf(fp, "\n");
12912     }
12913 }
12914
12915 /* Find last component of program's own name, using some heuristics */
12916 void
12917 TidyProgramName(prog, host, buf)
12918      char *prog, *host, buf[MSG_SIZ];
12919 {
12920     char *p, *q;
12921     int local = (strcmp(host, "localhost") == 0);
12922     while (!local && (p = strchr(prog, ';')) != NULL) {
12923         p++;
12924         while (*p == ' ') p++;
12925         prog = p;
12926     }
12927     if (*prog == '"' || *prog == '\'') {
12928         q = strchr(prog + 1, *prog);
12929     } else {
12930         q = strchr(prog, ' ');
12931     }
12932     if (q == NULL) q = prog + strlen(prog);
12933     p = q;
12934     while (p >= prog && *p != '/' && *p != '\\') p--;
12935     p++;
12936     if(p == prog && *p == '"') p++;
12937     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12938     memcpy(buf, p, q - p);
12939     buf[q - p] = NULLCHAR;
12940     if (!local) {
12941         strcat(buf, "@");
12942         strcat(buf, host);
12943     }
12944 }
12945
12946 char *
12947 TimeControlTagValue()
12948 {
12949     char buf[MSG_SIZ];
12950     if (!appData.clockMode) {
12951         strcpy(buf, "-");
12952     } else if (movesPerSession > 0) {
12953         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12954     } else if (timeIncrement == 0) {
12955         sprintf(buf, "%ld", timeControl/1000);
12956     } else {
12957         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12958     }
12959     return StrSave(buf);
12960 }
12961
12962 void
12963 SetGameInfo()
12964 {
12965     /* This routine is used only for certain modes */
12966     VariantClass v = gameInfo.variant;
12967     ChessMove r = GameUnfinished;
12968     char *p = NULL;
12969
12970     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12971         r = gameInfo.result; 
12972         p = gameInfo.resultDetails; 
12973         gameInfo.resultDetails = NULL;
12974     }
12975     ClearGameInfo(&gameInfo);
12976     gameInfo.variant = v;
12977
12978     switch (gameMode) {
12979       case MachinePlaysWhite:
12980         gameInfo.event = StrSave( appData.pgnEventHeader );
12981         gameInfo.site = StrSave(HostName());
12982         gameInfo.date = PGNDate();
12983         gameInfo.round = StrSave("-");
12984         gameInfo.white = StrSave(first.tidy);
12985         gameInfo.black = StrSave(UserName());
12986         gameInfo.timeControl = TimeControlTagValue();
12987         break;
12988
12989       case MachinePlaysBlack:
12990         gameInfo.event = StrSave( appData.pgnEventHeader );
12991         gameInfo.site = StrSave(HostName());
12992         gameInfo.date = PGNDate();
12993         gameInfo.round = StrSave("-");
12994         gameInfo.white = StrSave(UserName());
12995         gameInfo.black = StrSave(first.tidy);
12996         gameInfo.timeControl = TimeControlTagValue();
12997         break;
12998
12999       case TwoMachinesPlay:
13000         gameInfo.event = StrSave( appData.pgnEventHeader );
13001         gameInfo.site = StrSave(HostName());
13002         gameInfo.date = PGNDate();
13003         if (matchGame > 0) {
13004             char buf[MSG_SIZ];
13005             sprintf(buf, "%d", matchGame);
13006             gameInfo.round = StrSave(buf);
13007         } else {
13008             gameInfo.round = StrSave("-");
13009         }
13010         if (first.twoMachinesColor[0] == 'w') {
13011             gameInfo.white = StrSave(first.tidy);
13012             gameInfo.black = StrSave(second.tidy);
13013         } else {
13014             gameInfo.white = StrSave(second.tidy);
13015             gameInfo.black = StrSave(first.tidy);
13016         }
13017         gameInfo.timeControl = TimeControlTagValue();
13018         break;
13019
13020       case EditGame:
13021         gameInfo.event = StrSave("Edited game");
13022         gameInfo.site = StrSave(HostName());
13023         gameInfo.date = PGNDate();
13024         gameInfo.round = StrSave("-");
13025         gameInfo.white = StrSave("-");
13026         gameInfo.black = StrSave("-");
13027         gameInfo.result = r;
13028         gameInfo.resultDetails = p;
13029         break;
13030
13031       case EditPosition:
13032         gameInfo.event = StrSave("Edited position");
13033         gameInfo.site = StrSave(HostName());
13034         gameInfo.date = PGNDate();
13035         gameInfo.round = StrSave("-");
13036         gameInfo.white = StrSave("-");
13037         gameInfo.black = StrSave("-");
13038         break;
13039
13040       case IcsPlayingWhite:
13041       case IcsPlayingBlack:
13042       case IcsObserving:
13043       case IcsExamining:
13044         break;
13045
13046       case PlayFromGameFile:
13047         gameInfo.event = StrSave("Game from non-PGN file");
13048         gameInfo.site = StrSave(HostName());
13049         gameInfo.date = PGNDate();
13050         gameInfo.round = StrSave("-");
13051         gameInfo.white = StrSave("?");
13052         gameInfo.black = StrSave("?");
13053         break;
13054
13055       default:
13056         break;
13057     }
13058 }
13059
13060 void
13061 ReplaceComment(index, text)
13062      int index;
13063      char *text;
13064 {
13065     int len;
13066
13067     while (*text == '\n') text++;
13068     len = strlen(text);
13069     while (len > 0 && text[len - 1] == '\n') len--;
13070
13071     if (commentList[index] != NULL)
13072       free(commentList[index]);
13073
13074     if (len == 0) {
13075         commentList[index] = NULL;
13076         return;
13077     }
13078   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13079       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13080       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13081     commentList[index] = (char *) malloc(len + 2);
13082     strncpy(commentList[index], text, len);
13083     commentList[index][len] = '\n';
13084     commentList[index][len + 1] = NULLCHAR;
13085   } else { 
13086     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13087     char *p;
13088     commentList[index] = (char *) malloc(len + 6);
13089     strcpy(commentList[index], "{\n");
13090     strncpy(commentList[index]+2, text, len);
13091     commentList[index][len+2] = NULLCHAR;
13092     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13093     strcat(commentList[index], "\n}\n");
13094   }
13095 }
13096
13097 void
13098 CrushCRs(text)
13099      char *text;
13100 {
13101   char *p = text;
13102   char *q = text;
13103   char ch;
13104
13105   do {
13106     ch = *p++;
13107     if (ch == '\r') continue;
13108     *q++ = ch;
13109   } while (ch != '\0');
13110 }
13111
13112 void
13113 AppendComment(index, text, addBraces)
13114      int index;
13115      char *text;
13116      Boolean addBraces; // [HGM] braces: tells if we should add {}
13117 {
13118     int oldlen, len;
13119     char *old;
13120
13121 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13122     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13123
13124     CrushCRs(text);
13125     while (*text == '\n') text++;
13126     len = strlen(text);
13127     while (len > 0 && text[len - 1] == '\n') len--;
13128
13129     if (len == 0) return;
13130
13131     if (commentList[index] != NULL) {
13132         old = commentList[index];
13133         oldlen = strlen(old);
13134         while(commentList[index][oldlen-1] ==  '\n')
13135           commentList[index][--oldlen] = NULLCHAR;
13136         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13137         strcpy(commentList[index], old);
13138         free(old);
13139         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13140         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13141           if(addBraces) addBraces = FALSE; else { text++; len--; }
13142           while (*text == '\n') { text++; len--; }
13143           commentList[index][--oldlen] = NULLCHAR;
13144       }
13145         if(addBraces) strcat(commentList[index], "\n{\n");
13146         else          strcat(commentList[index], "\n");
13147         strcat(commentList[index], text);
13148         if(addBraces) strcat(commentList[index], "\n}\n");
13149         else          strcat(commentList[index], "\n");
13150     } else {
13151         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13152         if(addBraces)
13153              strcpy(commentList[index], "{\n");
13154         else commentList[index][0] = NULLCHAR;
13155         strcat(commentList[index], text);
13156         strcat(commentList[index], "\n");
13157         if(addBraces) strcat(commentList[index], "}\n");
13158     }
13159 }
13160
13161 static char * FindStr( char * text, char * sub_text )
13162 {
13163     char * result = strstr( text, sub_text );
13164
13165     if( result != NULL ) {
13166         result += strlen( sub_text );
13167     }
13168
13169     return result;
13170 }
13171
13172 /* [AS] Try to extract PV info from PGN comment */
13173 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13174 char *GetInfoFromComment( int index, char * text )
13175 {
13176     char * sep = text;
13177
13178     if( text != NULL && index > 0 ) {
13179         int score = 0;
13180         int depth = 0;
13181         int time = -1, sec = 0, deci;
13182         char * s_eval = FindStr( text, "[%eval " );
13183         char * s_emt = FindStr( text, "[%emt " );
13184
13185         if( s_eval != NULL || s_emt != NULL ) {
13186             /* New style */
13187             char delim;
13188
13189             if( s_eval != NULL ) {
13190                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13191                     return text;
13192                 }
13193
13194                 if( delim != ']' ) {
13195                     return text;
13196                 }
13197             }
13198
13199             if( s_emt != NULL ) {
13200             }
13201                 return text;
13202         }
13203         else {
13204             /* We expect something like: [+|-]nnn.nn/dd */
13205             int score_lo = 0;
13206
13207             if(*text != '{') return text; // [HGM] braces: must be normal comment
13208
13209             sep = strchr( text, '/' );
13210             if( sep == NULL || sep < (text+4) ) {
13211                 return text;
13212             }
13213
13214             time = -1; sec = -1; deci = -1;
13215             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13216                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13217                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13218                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13219                 return text;
13220             }
13221
13222             if( score_lo < 0 || score_lo >= 100 ) {
13223                 return text;
13224             }
13225
13226             if(sec >= 0) time = 600*time + 10*sec; else
13227             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13228
13229             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13230
13231             /* [HGM] PV time: now locate end of PV info */
13232             while( *++sep >= '0' && *sep <= '9'); // strip depth
13233             if(time >= 0)
13234             while( *++sep >= '0' && *sep <= '9'); // strip time
13235             if(sec >= 0)
13236             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13237             if(deci >= 0)
13238             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13239             while(*sep == ' ') sep++;
13240         }
13241
13242         if( depth <= 0 ) {
13243             return text;
13244         }
13245
13246         if( time < 0 ) {
13247             time = -1;
13248         }
13249
13250         pvInfoList[index-1].depth = depth;
13251         pvInfoList[index-1].score = score;
13252         pvInfoList[index-1].time  = 10*time; // centi-sec
13253         if(*sep == '}') *sep = 0; else *--sep = '{';
13254     }
13255     return sep;
13256 }
13257
13258 void
13259 SendToProgram(message, cps)
13260      char *message;
13261      ChessProgramState *cps;
13262 {
13263     int count, outCount, error;
13264     char buf[MSG_SIZ];
13265
13266     if (cps->pr == NULL) return;
13267     Attention(cps);
13268     
13269     if (appData.debugMode) {
13270         TimeMark now;
13271         GetTimeMark(&now);
13272         fprintf(debugFP, "%ld >%-6s: %s", 
13273                 SubtractTimeMarks(&now, &programStartTime),
13274                 cps->which, message);
13275     }
13276     
13277     count = strlen(message);
13278     outCount = OutputToProcess(cps->pr, message, count, &error);
13279     if (outCount < count && !exiting 
13280                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13281         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13282         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13283             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13284                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13285                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13286             } else {
13287                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13288             }
13289             gameInfo.resultDetails = StrSave(buf);
13290         }
13291         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13292     }
13293 }
13294
13295 void
13296 ReceiveFromProgram(isr, closure, message, count, error)
13297      InputSourceRef isr;
13298      VOIDSTAR closure;
13299      char *message;
13300      int count;
13301      int error;
13302 {
13303     char *end_str;
13304     char buf[MSG_SIZ];
13305     ChessProgramState *cps = (ChessProgramState *)closure;
13306
13307     if (isr != cps->isr) return; /* Killed intentionally */
13308     if (count <= 0) {
13309         if (count == 0) {
13310             sprintf(buf,
13311                     _("Error: %s chess program (%s) exited unexpectedly"),
13312                     cps->which, cps->program);
13313         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13314                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13315                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13316                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13317                 } else {
13318                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13319                 }
13320                 gameInfo.resultDetails = StrSave(buf);
13321             }
13322             RemoveInputSource(cps->isr);
13323             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13324         } else {
13325             sprintf(buf,
13326                     _("Error reading from %s chess program (%s)"),
13327                     cps->which, cps->program);
13328             RemoveInputSource(cps->isr);
13329
13330             /* [AS] Program is misbehaving badly... kill it */
13331             if( count == -2 ) {
13332                 DestroyChildProcess( cps->pr, 9 );
13333                 cps->pr = NoProc;
13334             }
13335
13336             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13337         }
13338         return;
13339     }
13340     
13341     if ((end_str = strchr(message, '\r')) != NULL)
13342       *end_str = NULLCHAR;
13343     if ((end_str = strchr(message, '\n')) != NULL)
13344       *end_str = NULLCHAR;
13345     
13346     if (appData.debugMode) {
13347         TimeMark now; int print = 1;
13348         char *quote = ""; char c; int i;
13349
13350         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13351                 char start = message[0];
13352                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13353                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13354                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13355                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13356                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13357                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13358                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13359                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13360                         { quote = "# "; print = (appData.engineComments == 2); }
13361                 message[0] = start; // restore original message
13362         }
13363         if(print) {
13364                 GetTimeMark(&now);
13365                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13366                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13367                         quote,
13368                         message);
13369         }
13370     }
13371
13372     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13373     if (appData.icsEngineAnalyze) {
13374         if (strstr(message, "whisper") != NULL ||
13375              strstr(message, "kibitz") != NULL || 
13376             strstr(message, "tellics") != NULL) return;
13377     }
13378
13379     HandleMachineMove(message, cps);
13380 }
13381
13382
13383 void
13384 SendTimeControl(cps, mps, tc, inc, sd, st)
13385      ChessProgramState *cps;
13386      int mps, inc, sd, st;
13387      long tc;
13388 {
13389     char buf[MSG_SIZ];
13390     int seconds;
13391
13392     if( timeControl_2 > 0 ) {
13393         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13394             tc = timeControl_2;
13395         }
13396     }
13397     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13398     inc /= cps->timeOdds;
13399     st  /= cps->timeOdds;
13400
13401     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13402
13403     if (st > 0) {
13404       /* Set exact time per move, normally using st command */
13405       if (cps->stKludge) {
13406         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13407         seconds = st % 60;
13408         if (seconds == 0) {
13409           sprintf(buf, "level 1 %d\n", st/60);
13410         } else {
13411           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13412         }
13413       } else {
13414         sprintf(buf, "st %d\n", st);
13415       }
13416     } else {
13417       /* Set conventional or incremental time control, using level command */
13418       if (seconds == 0) {
13419         /* Note old gnuchess bug -- minutes:seconds used to not work.
13420            Fixed in later versions, but still avoid :seconds
13421            when seconds is 0. */
13422         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13423       } else {
13424         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13425                 seconds, inc/1000);
13426       }
13427     }
13428     SendToProgram(buf, cps);
13429
13430     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13431     /* Orthogonally, limit search to given depth */
13432     if (sd > 0) {
13433       if (cps->sdKludge) {
13434         sprintf(buf, "depth\n%d\n", sd);
13435       } else {
13436         sprintf(buf, "sd %d\n", sd);
13437       }
13438       SendToProgram(buf, cps);
13439     }
13440
13441     if(cps->nps > 0) { /* [HGM] nps */
13442         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13443         else {
13444                 sprintf(buf, "nps %d\n", cps->nps);
13445               SendToProgram(buf, cps);
13446         }
13447     }
13448 }
13449
13450 ChessProgramState *WhitePlayer()
13451 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13452 {
13453     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13454        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13455         return &second;
13456     return &first;
13457 }
13458
13459 void
13460 SendTimeRemaining(cps, machineWhite)
13461      ChessProgramState *cps;
13462      int /*boolean*/ machineWhite;
13463 {
13464     char message[MSG_SIZ];
13465     long time, otime;
13466
13467     /* Note: this routine must be called when the clocks are stopped
13468        or when they have *just* been set or switched; otherwise
13469        it will be off by the time since the current tick started.
13470     */
13471     if (machineWhite) {
13472         time = whiteTimeRemaining / 10;
13473         otime = blackTimeRemaining / 10;
13474     } else {
13475         time = blackTimeRemaining / 10;
13476         otime = whiteTimeRemaining / 10;
13477     }
13478     /* [HGM] translate opponent's time by time-odds factor */
13479     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13480     if (appData.debugMode) {
13481         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13482     }
13483
13484     if (time <= 0) time = 1;
13485     if (otime <= 0) otime = 1;
13486     
13487     sprintf(message, "time %ld\n", time);
13488     SendToProgram(message, cps);
13489
13490     sprintf(message, "otim %ld\n", otime);
13491     SendToProgram(message, cps);
13492 }
13493
13494 int
13495 BoolFeature(p, name, loc, cps)
13496      char **p;
13497      char *name;
13498      int *loc;
13499      ChessProgramState *cps;
13500 {
13501   char buf[MSG_SIZ];
13502   int len = strlen(name);
13503   int val;
13504   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13505     (*p) += len + 1;
13506     sscanf(*p, "%d", &val);
13507     *loc = (val != 0);
13508     while (**p && **p != ' ') (*p)++;
13509     sprintf(buf, "accepted %s\n", name);
13510     SendToProgram(buf, cps);
13511     return TRUE;
13512   }
13513   return FALSE;
13514 }
13515
13516 int
13517 IntFeature(p, name, loc, cps)
13518      char **p;
13519      char *name;
13520      int *loc;
13521      ChessProgramState *cps;
13522 {
13523   char buf[MSG_SIZ];
13524   int len = strlen(name);
13525   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13526     (*p) += len + 1;
13527     sscanf(*p, "%d", loc);
13528     while (**p && **p != ' ') (*p)++;
13529     sprintf(buf, "accepted %s\n", name);
13530     SendToProgram(buf, cps);
13531     return TRUE;
13532   }
13533   return FALSE;
13534 }
13535
13536 int
13537 StringFeature(p, name, loc, cps)
13538      char **p;
13539      char *name;
13540      char loc[];
13541      ChessProgramState *cps;
13542 {
13543   char buf[MSG_SIZ];
13544   int len = strlen(name);
13545   if (strncmp((*p), name, len) == 0
13546       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13547     (*p) += len + 2;
13548     sscanf(*p, "%[^\"]", loc);
13549     while (**p && **p != '\"') (*p)++;
13550     if (**p == '\"') (*p)++;
13551     sprintf(buf, "accepted %s\n", name);
13552     SendToProgram(buf, cps);
13553     return TRUE;
13554   }
13555   return FALSE;
13556 }
13557
13558 int 
13559 ParseOption(Option *opt, ChessProgramState *cps)
13560 // [HGM] options: process the string that defines an engine option, and determine
13561 // name, type, default value, and allowed value range
13562 {
13563         char *p, *q, buf[MSG_SIZ];
13564         int n, min = (-1)<<31, max = 1<<31, def;
13565
13566         if(p = strstr(opt->name, " -spin ")) {
13567             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13568             if(max < min) max = min; // enforce consistency
13569             if(def < min) def = min;
13570             if(def > max) def = max;
13571             opt->value = def;
13572             opt->min = min;
13573             opt->max = max;
13574             opt->type = Spin;
13575         } else if((p = strstr(opt->name, " -slider "))) {
13576             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13577             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13578             if(max < min) max = min; // enforce consistency
13579             if(def < min) def = min;
13580             if(def > max) def = max;
13581             opt->value = def;
13582             opt->min = min;
13583             opt->max = max;
13584             opt->type = Spin; // Slider;
13585         } else if((p = strstr(opt->name, " -string "))) {
13586             opt->textValue = p+9;
13587             opt->type = TextBox;
13588         } else if((p = strstr(opt->name, " -file "))) {
13589             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13590             opt->textValue = p+7;
13591             opt->type = TextBox; // FileName;
13592         } else if((p = strstr(opt->name, " -path "))) {
13593             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13594             opt->textValue = p+7;
13595             opt->type = TextBox; // PathName;
13596         } else if(p = strstr(opt->name, " -check ")) {
13597             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13598             opt->value = (def != 0);
13599             opt->type = CheckBox;
13600         } else if(p = strstr(opt->name, " -combo ")) {
13601             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13602             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13603             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13604             opt->value = n = 0;
13605             while(q = StrStr(q, " /// ")) {
13606                 n++; *q = 0;    // count choices, and null-terminate each of them
13607                 q += 5;
13608                 if(*q == '*') { // remember default, which is marked with * prefix
13609                     q++;
13610                     opt->value = n;
13611                 }
13612                 cps->comboList[cps->comboCnt++] = q;
13613             }
13614             cps->comboList[cps->comboCnt++] = NULL;
13615             opt->max = n + 1;
13616             opt->type = ComboBox;
13617         } else if(p = strstr(opt->name, " -button")) {
13618             opt->type = Button;
13619         } else if(p = strstr(opt->name, " -save")) {
13620             opt->type = SaveButton;
13621         } else return FALSE;
13622         *p = 0; // terminate option name
13623         // now look if the command-line options define a setting for this engine option.
13624         if(cps->optionSettings && cps->optionSettings[0])
13625             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13626         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13627                 sprintf(buf, "option %s", p);
13628                 if(p = strstr(buf, ",")) *p = 0;
13629                 strcat(buf, "\n");
13630                 SendToProgram(buf, cps);
13631         }
13632         return TRUE;
13633 }
13634
13635 void
13636 FeatureDone(cps, val)
13637      ChessProgramState* cps;
13638      int val;
13639 {
13640   DelayedEventCallback cb = GetDelayedEvent();
13641   if ((cb == InitBackEnd3 && cps == &first) ||
13642       (cb == TwoMachinesEventIfReady && cps == &second)) {
13643     CancelDelayedEvent();
13644     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13645   }
13646   cps->initDone = val;
13647 }
13648
13649 /* Parse feature command from engine */
13650 void
13651 ParseFeatures(args, cps)
13652      char* args;
13653      ChessProgramState *cps;  
13654 {
13655   char *p = args;
13656   char *q;
13657   int val;
13658   char buf[MSG_SIZ];
13659
13660   for (;;) {
13661     while (*p == ' ') p++;
13662     if (*p == NULLCHAR) return;
13663
13664     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13665     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13666     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13667     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13668     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13669     if (BoolFeature(&p, "reuse", &val, cps)) {
13670       /* Engine can disable reuse, but can't enable it if user said no */
13671       if (!val) cps->reuse = FALSE;
13672       continue;
13673     }
13674     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13675     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13676       if (gameMode == TwoMachinesPlay) {
13677         DisplayTwoMachinesTitle();
13678       } else {
13679         DisplayTitle("");
13680       }
13681       continue;
13682     }
13683     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13684     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13685     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13686     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13687     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13688     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13689     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13690     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13691     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13692     if (IntFeature(&p, "done", &val, cps)) {
13693       FeatureDone(cps, val);
13694       continue;
13695     }
13696     /* Added by Tord: */
13697     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13698     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13699     /* End of additions by Tord */
13700
13701     /* [HGM] added features: */
13702     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13703     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13704     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13705     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13706     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13707     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13708     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13709         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13710             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13711             SendToProgram(buf, cps);
13712             continue;
13713         }
13714         if(cps->nrOptions >= MAX_OPTIONS) {
13715             cps->nrOptions--;
13716             sprintf(buf, "%s engine has too many options\n", cps->which);
13717             DisplayError(buf, 0);
13718         }
13719         continue;
13720     }
13721     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13722     /* End of additions by HGM */
13723
13724     /* unknown feature: complain and skip */
13725     q = p;
13726     while (*q && *q != '=') q++;
13727     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13728     SendToProgram(buf, cps);
13729     p = q;
13730     if (*p == '=') {
13731       p++;
13732       if (*p == '\"') {
13733         p++;
13734         while (*p && *p != '\"') p++;
13735         if (*p == '\"') p++;
13736       } else {
13737         while (*p && *p != ' ') p++;
13738       }
13739     }
13740   }
13741
13742 }
13743
13744 void
13745 PeriodicUpdatesEvent(newState)
13746      int newState;
13747 {
13748     if (newState == appData.periodicUpdates)
13749       return;
13750
13751     appData.periodicUpdates=newState;
13752
13753     /* Display type changes, so update it now */
13754 //    DisplayAnalysis();
13755
13756     /* Get the ball rolling again... */
13757     if (newState) {
13758         AnalysisPeriodicEvent(1);
13759         StartAnalysisClock();
13760     }
13761 }
13762
13763 void
13764 PonderNextMoveEvent(newState)
13765      int newState;
13766 {
13767     if (newState == appData.ponderNextMove) return;
13768     if (gameMode == EditPosition) EditPositionDone(TRUE);
13769     if (newState) {
13770         SendToProgram("hard\n", &first);
13771         if (gameMode == TwoMachinesPlay) {
13772             SendToProgram("hard\n", &second);
13773         }
13774     } else {
13775         SendToProgram("easy\n", &first);
13776         thinkOutput[0] = NULLCHAR;
13777         if (gameMode == TwoMachinesPlay) {
13778             SendToProgram("easy\n", &second);
13779         }
13780     }
13781     appData.ponderNextMove = newState;
13782 }
13783
13784 void
13785 NewSettingEvent(option, command, value)
13786      char *command;
13787      int option, value;
13788 {
13789     char buf[MSG_SIZ];
13790
13791     if (gameMode == EditPosition) EditPositionDone(TRUE);
13792     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13793     SendToProgram(buf, &first);
13794     if (gameMode == TwoMachinesPlay) {
13795         SendToProgram(buf, &second);
13796     }
13797 }
13798
13799 void
13800 ShowThinkingEvent()
13801 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13802 {
13803     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13804     int newState = appData.showThinking
13805         // [HGM] thinking: other features now need thinking output as well
13806         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13807     
13808     if (oldState == newState) return;
13809     oldState = newState;
13810     if (gameMode == EditPosition) EditPositionDone(TRUE);
13811     if (oldState) {
13812         SendToProgram("post\n", &first);
13813         if (gameMode == TwoMachinesPlay) {
13814             SendToProgram("post\n", &second);
13815         }
13816     } else {
13817         SendToProgram("nopost\n", &first);
13818         thinkOutput[0] = NULLCHAR;
13819         if (gameMode == TwoMachinesPlay) {
13820             SendToProgram("nopost\n", &second);
13821         }
13822     }
13823 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13824 }
13825
13826 void
13827 AskQuestionEvent(title, question, replyPrefix, which)
13828      char *title; char *question; char *replyPrefix; char *which;
13829 {
13830   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13831   if (pr == NoProc) return;
13832   AskQuestion(title, question, replyPrefix, pr);
13833 }
13834
13835 void
13836 DisplayMove(moveNumber)
13837      int moveNumber;
13838 {
13839     char message[MSG_SIZ];
13840     char res[MSG_SIZ];
13841     char cpThinkOutput[MSG_SIZ];
13842
13843     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13844     
13845     if (moveNumber == forwardMostMove - 1 || 
13846         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13847
13848         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13849
13850         if (strchr(cpThinkOutput, '\n')) {
13851             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13852         }
13853     } else {
13854         *cpThinkOutput = NULLCHAR;
13855     }
13856
13857     /* [AS] Hide thinking from human user */
13858     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13859         *cpThinkOutput = NULLCHAR;
13860         if( thinkOutput[0] != NULLCHAR ) {
13861             int i;
13862
13863             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13864                 cpThinkOutput[i] = '.';
13865             }
13866             cpThinkOutput[i] = NULLCHAR;
13867             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13868         }
13869     }
13870
13871     if (moveNumber == forwardMostMove - 1 &&
13872         gameInfo.resultDetails != NULL) {
13873         if (gameInfo.resultDetails[0] == NULLCHAR) {
13874             sprintf(res, " %s", PGNResult(gameInfo.result));
13875         } else {
13876             sprintf(res, " {%s} %s",
13877                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13878         }
13879     } else {
13880         res[0] = NULLCHAR;
13881     }
13882
13883     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13884         DisplayMessage(res, cpThinkOutput);
13885     } else {
13886         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13887                 WhiteOnMove(moveNumber) ? " " : ".. ",
13888                 parseList[moveNumber], res);
13889         DisplayMessage(message, cpThinkOutput);
13890     }
13891 }
13892
13893 void
13894 DisplayComment(moveNumber, text)
13895      int moveNumber;
13896      char *text;
13897 {
13898     char title[MSG_SIZ];
13899     char buf[8000]; // comment can be long!
13900     int score, depth;
13901     
13902     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13903       strcpy(title, "Comment");
13904     } else {
13905       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13906               WhiteOnMove(moveNumber) ? " " : ".. ",
13907               parseList[moveNumber]);
13908     }
13909     // [HGM] PV info: display PV info together with (or as) comment
13910     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13911       if(text == NULL) text = "";                                           
13912       score = pvInfoList[moveNumber].score;
13913       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13914               depth, (pvInfoList[moveNumber].time+50)/100, text);
13915       text = buf;
13916     }
13917     if (text != NULL && (appData.autoDisplayComment || commentUp))
13918         CommentPopUp(title, text);
13919 }
13920
13921 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13922  * might be busy thinking or pondering.  It can be omitted if your
13923  * gnuchess is configured to stop thinking immediately on any user
13924  * input.  However, that gnuchess feature depends on the FIONREAD
13925  * ioctl, which does not work properly on some flavors of Unix.
13926  */
13927 void
13928 Attention(cps)
13929      ChessProgramState *cps;
13930 {
13931 #if ATTENTION
13932     if (!cps->useSigint) return;
13933     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13934     switch (gameMode) {
13935       case MachinePlaysWhite:
13936       case MachinePlaysBlack:
13937       case TwoMachinesPlay:
13938       case IcsPlayingWhite:
13939       case IcsPlayingBlack:
13940       case AnalyzeMode:
13941       case AnalyzeFile:
13942         /* Skip if we know it isn't thinking */
13943         if (!cps->maybeThinking) return;
13944         if (appData.debugMode)
13945           fprintf(debugFP, "Interrupting %s\n", cps->which);
13946         InterruptChildProcess(cps->pr);
13947         cps->maybeThinking = FALSE;
13948         break;
13949       default:
13950         break;
13951     }
13952 #endif /*ATTENTION*/
13953 }
13954
13955 int
13956 CheckFlags()
13957 {
13958     if (whiteTimeRemaining <= 0) {
13959         if (!whiteFlag) {
13960             whiteFlag = TRUE;
13961             if (appData.icsActive) {
13962                 if (appData.autoCallFlag &&
13963                     gameMode == IcsPlayingBlack && !blackFlag) {
13964                   SendToICS(ics_prefix);
13965                   SendToICS("flag\n");
13966                 }
13967             } else {
13968                 if (blackFlag) {
13969                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13970                 } else {
13971                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13972                     if (appData.autoCallFlag) {
13973                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13974                         return TRUE;
13975                     }
13976                 }
13977             }
13978         }
13979     }
13980     if (blackTimeRemaining <= 0) {
13981         if (!blackFlag) {
13982             blackFlag = TRUE;
13983             if (appData.icsActive) {
13984                 if (appData.autoCallFlag &&
13985                     gameMode == IcsPlayingWhite && !whiteFlag) {
13986                   SendToICS(ics_prefix);
13987                   SendToICS("flag\n");
13988                 }
13989             } else {
13990                 if (whiteFlag) {
13991                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13992                 } else {
13993                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13994                     if (appData.autoCallFlag) {
13995                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13996                         return TRUE;
13997                     }
13998                 }
13999             }
14000         }
14001     }
14002     return FALSE;
14003 }
14004
14005 void
14006 CheckTimeControl()
14007 {
14008     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14009         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14010
14011     /*
14012      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14013      */
14014     if ( !WhiteOnMove(forwardMostMove) )
14015         /* White made time control */
14016         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14017         /* [HGM] time odds: correct new time quota for time odds! */
14018                                             / WhitePlayer()->timeOdds;
14019       else
14020         /* Black made time control */
14021         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14022                                             / WhitePlayer()->other->timeOdds;
14023 }
14024
14025 void
14026 DisplayBothClocks()
14027 {
14028     int wom = gameMode == EditPosition ?
14029       !blackPlaysFirst : WhiteOnMove(currentMove);
14030     DisplayWhiteClock(whiteTimeRemaining, wom);
14031     DisplayBlackClock(blackTimeRemaining, !wom);
14032 }
14033
14034
14035 /* Timekeeping seems to be a portability nightmare.  I think everyone
14036    has ftime(), but I'm really not sure, so I'm including some ifdefs
14037    to use other calls if you don't.  Clocks will be less accurate if
14038    you have neither ftime nor gettimeofday.
14039 */
14040
14041 /* VS 2008 requires the #include outside of the function */
14042 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14043 #include <sys/timeb.h>
14044 #endif
14045
14046 /* Get the current time as a TimeMark */
14047 void
14048 GetTimeMark(tm)
14049      TimeMark *tm;
14050 {
14051 #if HAVE_GETTIMEOFDAY
14052
14053     struct timeval timeVal;
14054     struct timezone timeZone;
14055
14056     gettimeofday(&timeVal, &timeZone);
14057     tm->sec = (long) timeVal.tv_sec; 
14058     tm->ms = (int) (timeVal.tv_usec / 1000L);
14059
14060 #else /*!HAVE_GETTIMEOFDAY*/
14061 #if HAVE_FTIME
14062
14063 // include <sys/timeb.h> / moved to just above start of function
14064     struct timeb timeB;
14065
14066     ftime(&timeB);
14067     tm->sec = (long) timeB.time;
14068     tm->ms = (int) timeB.millitm;
14069
14070 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14071     tm->sec = (long) time(NULL);
14072     tm->ms = 0;
14073 #endif
14074 #endif
14075 }
14076
14077 /* Return the difference in milliseconds between two
14078    time marks.  We assume the difference will fit in a long!
14079 */
14080 long
14081 SubtractTimeMarks(tm2, tm1)
14082      TimeMark *tm2, *tm1;
14083 {
14084     return 1000L*(tm2->sec - tm1->sec) +
14085            (long) (tm2->ms - tm1->ms);
14086 }
14087
14088
14089 /*
14090  * Code to manage the game clocks.
14091  *
14092  * In tournament play, black starts the clock and then white makes a move.
14093  * We give the human user a slight advantage if he is playing white---the
14094  * clocks don't run until he makes his first move, so it takes zero time.
14095  * Also, we don't account for network lag, so we could get out of sync
14096  * with GNU Chess's clock -- but then, referees are always right.  
14097  */
14098
14099 static TimeMark tickStartTM;
14100 static long intendedTickLength;
14101
14102 long
14103 NextTickLength(timeRemaining)
14104      long timeRemaining;
14105 {
14106     long nominalTickLength, nextTickLength;
14107
14108     if (timeRemaining > 0L && timeRemaining <= 10000L)
14109       nominalTickLength = 100L;
14110     else
14111       nominalTickLength = 1000L;
14112     nextTickLength = timeRemaining % nominalTickLength;
14113     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14114
14115     return nextTickLength;
14116 }
14117
14118 /* Adjust clock one minute up or down */
14119 void
14120 AdjustClock(Boolean which, int dir)
14121 {
14122     if(which) blackTimeRemaining += 60000*dir;
14123     else      whiteTimeRemaining += 60000*dir;
14124     DisplayBothClocks();
14125 }
14126
14127 /* Stop clocks and reset to a fresh time control */
14128 void
14129 ResetClocks() 
14130 {
14131     (void) StopClockTimer();
14132     if (appData.icsActive) {
14133         whiteTimeRemaining = blackTimeRemaining = 0;
14134     } else if (searchTime) {
14135         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14136         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14137     } else { /* [HGM] correct new time quote for time odds */
14138         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14139         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14140     }
14141     if (whiteFlag || blackFlag) {
14142         DisplayTitle("");
14143         whiteFlag = blackFlag = FALSE;
14144     }
14145     DisplayBothClocks();
14146 }
14147
14148 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14149
14150 /* Decrement running clock by amount of time that has passed */
14151 void
14152 DecrementClocks()
14153 {
14154     long timeRemaining;
14155     long lastTickLength, fudge;
14156     TimeMark now;
14157
14158     if (!appData.clockMode) return;
14159     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14160         
14161     GetTimeMark(&now);
14162
14163     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14164
14165     /* Fudge if we woke up a little too soon */
14166     fudge = intendedTickLength - lastTickLength;
14167     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14168
14169     if (WhiteOnMove(forwardMostMove)) {
14170         if(whiteNPS >= 0) lastTickLength = 0;
14171         timeRemaining = whiteTimeRemaining -= lastTickLength;
14172         DisplayWhiteClock(whiteTimeRemaining - fudge,
14173                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14174     } else {
14175         if(blackNPS >= 0) lastTickLength = 0;
14176         timeRemaining = blackTimeRemaining -= lastTickLength;
14177         DisplayBlackClock(blackTimeRemaining - fudge,
14178                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14179     }
14180
14181     if (CheckFlags()) return;
14182         
14183     tickStartTM = now;
14184     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14185     StartClockTimer(intendedTickLength);
14186
14187     /* if the time remaining has fallen below the alarm threshold, sound the
14188      * alarm. if the alarm has sounded and (due to a takeback or time control
14189      * with increment) the time remaining has increased to a level above the
14190      * threshold, reset the alarm so it can sound again. 
14191      */
14192     
14193     if (appData.icsActive && appData.icsAlarm) {
14194
14195         /* make sure we are dealing with the user's clock */
14196         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14197                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14198            )) return;
14199
14200         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14201             alarmSounded = FALSE;
14202         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14203             PlayAlarmSound();
14204             alarmSounded = TRUE;
14205         }
14206     }
14207 }
14208
14209
14210 /* A player has just moved, so stop the previously running
14211    clock and (if in clock mode) start the other one.
14212    We redisplay both clocks in case we're in ICS mode, because
14213    ICS gives us an update to both clocks after every move.
14214    Note that this routine is called *after* forwardMostMove
14215    is updated, so the last fractional tick must be subtracted
14216    from the color that is *not* on move now.
14217 */
14218 void
14219 SwitchClocks(int newMoveNr)
14220 {
14221     long lastTickLength;
14222     TimeMark now;
14223     int flagged = FALSE;
14224
14225     GetTimeMark(&now);
14226
14227     if (StopClockTimer() && appData.clockMode) {
14228         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14229         if (!WhiteOnMove(forwardMostMove)) {
14230             if(blackNPS >= 0) lastTickLength = 0;
14231             blackTimeRemaining -= lastTickLength;
14232            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14233 //         if(pvInfoList[forwardMostMove-1].time == -1)
14234                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14235                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14236         } else {
14237            if(whiteNPS >= 0) lastTickLength = 0;
14238            whiteTimeRemaining -= lastTickLength;
14239            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14240 //         if(pvInfoList[forwardMostMove-1].time == -1)
14241                  pvInfoList[forwardMostMove-1].time = 
14242                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14243         }
14244         flagged = CheckFlags();
14245     }
14246     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14247     CheckTimeControl();
14248
14249     if (flagged || !appData.clockMode) return;
14250
14251     switch (gameMode) {
14252       case MachinePlaysBlack:
14253       case MachinePlaysWhite:
14254       case BeginningOfGame:
14255         if (pausing) return;
14256         break;
14257
14258       case EditGame:
14259       case PlayFromGameFile:
14260       case IcsExamining:
14261         return;
14262
14263       default:
14264         break;
14265     }
14266
14267     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14268         if(WhiteOnMove(forwardMostMove))
14269              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14270         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14271     }
14272
14273     tickStartTM = now;
14274     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14275       whiteTimeRemaining : blackTimeRemaining);
14276     StartClockTimer(intendedTickLength);
14277 }
14278         
14279
14280 /* Stop both clocks */
14281 void
14282 StopClocks()
14283 {       
14284     long lastTickLength;
14285     TimeMark now;
14286
14287     if (!StopClockTimer()) return;
14288     if (!appData.clockMode) return;
14289
14290     GetTimeMark(&now);
14291
14292     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14293     if (WhiteOnMove(forwardMostMove)) {
14294         if(whiteNPS >= 0) lastTickLength = 0;
14295         whiteTimeRemaining -= lastTickLength;
14296         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14297     } else {
14298         if(blackNPS >= 0) lastTickLength = 0;
14299         blackTimeRemaining -= lastTickLength;
14300         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14301     }
14302     CheckFlags();
14303 }
14304         
14305 /* Start clock of player on move.  Time may have been reset, so
14306    if clock is already running, stop and restart it. */
14307 void
14308 StartClocks()
14309 {
14310     (void) StopClockTimer(); /* in case it was running already */
14311     DisplayBothClocks();
14312     if (CheckFlags()) return;
14313
14314     if (!appData.clockMode) return;
14315     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14316
14317     GetTimeMark(&tickStartTM);
14318     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14319       whiteTimeRemaining : blackTimeRemaining);
14320
14321    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14322     whiteNPS = blackNPS = -1; 
14323     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14324        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14325         whiteNPS = first.nps;
14326     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14327        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14328         blackNPS = first.nps;
14329     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14330         whiteNPS = second.nps;
14331     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14332         blackNPS = second.nps;
14333     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14334
14335     StartClockTimer(intendedTickLength);
14336 }
14337
14338 char *
14339 TimeString(ms)
14340      long ms;
14341 {
14342     long second, minute, hour, day;
14343     char *sign = "";
14344     static char buf[32];
14345     
14346     if (ms > 0 && ms <= 9900) {
14347       /* convert milliseconds to tenths, rounding up */
14348       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14349
14350       sprintf(buf, " %03.1f ", tenths/10.0);
14351       return buf;
14352     }
14353
14354     /* convert milliseconds to seconds, rounding up */
14355     /* use floating point to avoid strangeness of integer division
14356        with negative dividends on many machines */
14357     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14358
14359     if (second < 0) {
14360         sign = "-";
14361         second = -second;
14362     }
14363     
14364     day = second / (60 * 60 * 24);
14365     second = second % (60 * 60 * 24);
14366     hour = second / (60 * 60);
14367     second = second % (60 * 60);
14368     minute = second / 60;
14369     second = second % 60;
14370     
14371     if (day > 0)
14372       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14373               sign, day, hour, minute, second);
14374     else if (hour > 0)
14375       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14376     else
14377       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14378     
14379     return buf;
14380 }
14381
14382
14383 /*
14384  * This is necessary because some C libraries aren't ANSI C compliant yet.
14385  */
14386 char *
14387 StrStr(string, match)
14388      char *string, *match;
14389 {
14390     int i, length;
14391     
14392     length = strlen(match);
14393     
14394     for (i = strlen(string) - length; i >= 0; i--, string++)
14395       if (!strncmp(match, string, length))
14396         return string;
14397     
14398     return NULL;
14399 }
14400
14401 char *
14402 StrCaseStr(string, match)
14403      char *string, *match;
14404 {
14405     int i, j, length;
14406     
14407     length = strlen(match);
14408     
14409     for (i = strlen(string) - length; i >= 0; i--, string++) {
14410         for (j = 0; j < length; j++) {
14411             if (ToLower(match[j]) != ToLower(string[j]))
14412               break;
14413         }
14414         if (j == length) return string;
14415     }
14416
14417     return NULL;
14418 }
14419
14420 #ifndef _amigados
14421 int
14422 StrCaseCmp(s1, s2)
14423      char *s1, *s2;
14424 {
14425     char c1, c2;
14426     
14427     for (;;) {
14428         c1 = ToLower(*s1++);
14429         c2 = ToLower(*s2++);
14430         if (c1 > c2) return 1;
14431         if (c1 < c2) return -1;
14432         if (c1 == NULLCHAR) return 0;
14433     }
14434 }
14435
14436
14437 int
14438 ToLower(c)
14439      int c;
14440 {
14441     return isupper(c) ? tolower(c) : c;
14442 }
14443
14444
14445 int
14446 ToUpper(c)
14447      int c;
14448 {
14449     return islower(c) ? toupper(c) : c;
14450 }
14451 #endif /* !_amigados    */
14452
14453 char *
14454 StrSave(s)
14455      char *s;
14456 {
14457     char *ret;
14458
14459     if ((ret = (char *) malloc(strlen(s) + 1))) {
14460         strcpy(ret, s);
14461     }
14462     return ret;
14463 }
14464
14465 char *
14466 StrSavePtr(s, savePtr)
14467      char *s, **savePtr;
14468 {
14469     if (*savePtr) {
14470         free(*savePtr);
14471     }
14472     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14473         strcpy(*savePtr, s);
14474     }
14475     return(*savePtr);
14476 }
14477
14478 char *
14479 PGNDate()
14480 {
14481     time_t clock;
14482     struct tm *tm;
14483     char buf[MSG_SIZ];
14484
14485     clock = time((time_t *)NULL);
14486     tm = localtime(&clock);
14487     sprintf(buf, "%04d.%02d.%02d",
14488             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14489     return StrSave(buf);
14490 }
14491
14492
14493 char *
14494 PositionToFEN(move, overrideCastling)
14495      int move;
14496      char *overrideCastling;
14497 {
14498     int i, j, fromX, fromY, toX, toY;
14499     int whiteToPlay;
14500     char buf[128];
14501     char *p, *q;
14502     int emptycount;
14503     ChessSquare piece;
14504
14505     whiteToPlay = (gameMode == EditPosition) ?
14506       !blackPlaysFirst : (move % 2 == 0);
14507     p = buf;
14508
14509     /* Piece placement data */
14510     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14511         emptycount = 0;
14512         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14513             if (boards[move][i][j] == EmptySquare) {
14514                 emptycount++;
14515             } else { ChessSquare piece = boards[move][i][j];
14516                 if (emptycount > 0) {
14517                     if(emptycount<10) /* [HGM] can be >= 10 */
14518                         *p++ = '0' + emptycount;
14519                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14520                     emptycount = 0;
14521                 }
14522                 if(PieceToChar(piece) == '+') {
14523                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14524                     *p++ = '+';
14525                     piece = (ChessSquare)(DEMOTED piece);
14526                 } 
14527                 *p++ = PieceToChar(piece);
14528                 if(p[-1] == '~') {
14529                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14530                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14531                     *p++ = '~';
14532                 }
14533             }
14534         }
14535         if (emptycount > 0) {
14536             if(emptycount<10) /* [HGM] can be >= 10 */
14537                 *p++ = '0' + emptycount;
14538             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14539             emptycount = 0;
14540         }
14541         *p++ = '/';
14542     }
14543     *(p - 1) = ' ';
14544
14545     /* [HGM] print Crazyhouse or Shogi holdings */
14546     if( gameInfo.holdingsWidth ) {
14547         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14548         q = p;
14549         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14550             piece = boards[move][i][BOARD_WIDTH-1];
14551             if( piece != EmptySquare )
14552               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14553                   *p++ = PieceToChar(piece);
14554         }
14555         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14556             piece = boards[move][BOARD_HEIGHT-i-1][0];
14557             if( piece != EmptySquare )
14558               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14559                   *p++ = PieceToChar(piece);
14560         }
14561
14562         if( q == p ) *p++ = '-';
14563         *p++ = ']';
14564         *p++ = ' ';
14565     }
14566
14567     /* Active color */
14568     *p++ = whiteToPlay ? 'w' : 'b';
14569     *p++ = ' ';
14570
14571   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14572     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14573   } else {
14574   if(nrCastlingRights) {
14575      q = p;
14576      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14577        /* [HGM] write directly from rights */
14578            if(boards[move][CASTLING][2] != NoRights &&
14579               boards[move][CASTLING][0] != NoRights   )
14580                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14581            if(boards[move][CASTLING][2] != NoRights &&
14582               boards[move][CASTLING][1] != NoRights   )
14583                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14584            if(boards[move][CASTLING][5] != NoRights &&
14585               boards[move][CASTLING][3] != NoRights   )
14586                 *p++ = boards[move][CASTLING][3] + AAA;
14587            if(boards[move][CASTLING][5] != NoRights &&
14588               boards[move][CASTLING][4] != NoRights   )
14589                 *p++ = boards[move][CASTLING][4] + AAA;
14590      } else {
14591
14592         /* [HGM] write true castling rights */
14593         if( nrCastlingRights == 6 ) {
14594             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14595                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14596             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14597                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14598             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14599                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14600             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14601                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14602         }
14603      }
14604      if (q == p) *p++ = '-'; /* No castling rights */
14605      *p++ = ' ';
14606   }
14607
14608   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14609      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14610     /* En passant target square */
14611     if (move > backwardMostMove) {
14612         fromX = moveList[move - 1][0] - AAA;
14613         fromY = moveList[move - 1][1] - ONE;
14614         toX = moveList[move - 1][2] - AAA;
14615         toY = moveList[move - 1][3] - ONE;
14616         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14617             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14618             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14619             fromX == toX) {
14620             /* 2-square pawn move just happened */
14621             *p++ = toX + AAA;
14622             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14623         } else {
14624             *p++ = '-';
14625         }
14626     } else if(move == backwardMostMove) {
14627         // [HGM] perhaps we should always do it like this, and forget the above?
14628         if((signed char)boards[move][EP_STATUS] >= 0) {
14629             *p++ = boards[move][EP_STATUS] + AAA;
14630             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14631         } else {
14632             *p++ = '-';
14633         }
14634     } else {
14635         *p++ = '-';
14636     }
14637     *p++ = ' ';
14638   }
14639   }
14640
14641     /* [HGM] find reversible plies */
14642     {   int i = 0, j=move;
14643
14644         if (appData.debugMode) { int k;
14645             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14646             for(k=backwardMostMove; k<=forwardMostMove; k++)
14647                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14648
14649         }
14650
14651         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14652         if( j == backwardMostMove ) i += initialRulePlies;
14653         sprintf(p, "%d ", i);
14654         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14655     }
14656     /* Fullmove number */
14657     sprintf(p, "%d", (move / 2) + 1);
14658     
14659     return StrSave(buf);
14660 }
14661
14662 Boolean
14663 ParseFEN(board, blackPlaysFirst, fen)
14664     Board board;
14665      int *blackPlaysFirst;
14666      char *fen;
14667 {
14668     int i, j;
14669     char *p;
14670     int emptycount;
14671     ChessSquare piece;
14672
14673     p = fen;
14674
14675     /* [HGM] by default clear Crazyhouse holdings, if present */
14676     if(gameInfo.holdingsWidth) {
14677        for(i=0; i<BOARD_HEIGHT; i++) {
14678            board[i][0]             = EmptySquare; /* black holdings */
14679            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14680            board[i][1]             = (ChessSquare) 0; /* black counts */
14681            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14682        }
14683     }
14684
14685     /* Piece placement data */
14686     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14687         j = 0;
14688         for (;;) {
14689             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14690                 if (*p == '/') p++;
14691                 emptycount = gameInfo.boardWidth - j;
14692                 while (emptycount--)
14693                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14694                 break;
14695 #if(BOARD_FILES >= 10)
14696             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14697                 p++; emptycount=10;
14698                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14699                 while (emptycount--)
14700                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14701 #endif
14702             } else if (isdigit(*p)) {
14703                 emptycount = *p++ - '0';
14704                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14705                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14706                 while (emptycount--)
14707                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14708             } else if (*p == '+' || isalpha(*p)) {
14709                 if (j >= gameInfo.boardWidth) return FALSE;
14710                 if(*p=='+') {
14711                     piece = CharToPiece(*++p);
14712                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14713                     piece = (ChessSquare) (PROMOTED piece ); p++;
14714                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14715                 } else piece = CharToPiece(*p++);
14716
14717                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14718                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14719                     piece = (ChessSquare) (PROMOTED piece);
14720                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14721                     p++;
14722                 }
14723                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14724             } else {
14725                 return FALSE;
14726             }
14727         }
14728     }
14729     while (*p == '/' || *p == ' ') p++;
14730
14731     /* [HGM] look for Crazyhouse holdings here */
14732     while(*p==' ') p++;
14733     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14734         if(*p == '[') p++;
14735         if(*p == '-' ) *p++; /* empty holdings */ else {
14736             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14737             /* if we would allow FEN reading to set board size, we would   */
14738             /* have to add holdings and shift the board read so far here   */
14739             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14740                 *p++;
14741                 if((int) piece >= (int) BlackPawn ) {
14742                     i = (int)piece - (int)BlackPawn;
14743                     i = PieceToNumber((ChessSquare)i);
14744                     if( i >= gameInfo.holdingsSize ) return FALSE;
14745                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14746                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14747                 } else {
14748                     i = (int)piece - (int)WhitePawn;
14749                     i = PieceToNumber((ChessSquare)i);
14750                     if( i >= gameInfo.holdingsSize ) return FALSE;
14751                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14752                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14753                 }
14754             }
14755         }
14756         if(*p == ']') *p++;
14757     }
14758
14759     while(*p == ' ') p++;
14760
14761     /* Active color */
14762     switch (*p++) {
14763       case 'w':
14764         *blackPlaysFirst = FALSE;
14765         break;
14766       case 'b': 
14767         *blackPlaysFirst = TRUE;
14768         break;
14769       default:
14770         return FALSE;
14771     }
14772
14773     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14774     /* return the extra info in global variiables             */
14775
14776     /* set defaults in case FEN is incomplete */
14777     board[EP_STATUS] = EP_UNKNOWN;
14778     for(i=0; i<nrCastlingRights; i++ ) {
14779         board[CASTLING][i] =
14780             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14781     }   /* assume possible unless obviously impossible */
14782     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14783     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14784     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14785                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14786     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14787     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14788     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14789                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14790     FENrulePlies = 0;
14791
14792     while(*p==' ') p++;
14793     if(nrCastlingRights) {
14794       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14795           /* castling indicator present, so default becomes no castlings */
14796           for(i=0; i<nrCastlingRights; i++ ) {
14797                  board[CASTLING][i] = NoRights;
14798           }
14799       }
14800       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14801              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14802              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14803              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14804         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14805
14806         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14807             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14808             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14809         }
14810         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14811             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14812         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14813                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14814         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14815                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14816         switch(c) {
14817           case'K':
14818               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14819               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14820               board[CASTLING][2] = whiteKingFile;
14821               break;
14822           case'Q':
14823               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14824               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14825               board[CASTLING][2] = whiteKingFile;
14826               break;
14827           case'k':
14828               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14829               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14830               board[CASTLING][5] = blackKingFile;
14831               break;
14832           case'q':
14833               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14834               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14835               board[CASTLING][5] = blackKingFile;
14836           case '-':
14837               break;
14838           default: /* FRC castlings */
14839               if(c >= 'a') { /* black rights */
14840                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14841                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14842                   if(i == BOARD_RGHT) break;
14843                   board[CASTLING][5] = i;
14844                   c -= AAA;
14845                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14846                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14847                   if(c > i)
14848                       board[CASTLING][3] = c;
14849                   else
14850                       board[CASTLING][4] = c;
14851               } else { /* white rights */
14852                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14853                     if(board[0][i] == WhiteKing) break;
14854                   if(i == BOARD_RGHT) break;
14855                   board[CASTLING][2] = i;
14856                   c -= AAA - 'a' + 'A';
14857                   if(board[0][c] >= WhiteKing) break;
14858                   if(c > i)
14859                       board[CASTLING][0] = c;
14860                   else
14861                       board[CASTLING][1] = c;
14862               }
14863         }
14864       }
14865       for(i=0; i<nrCastlingRights; i++)
14866         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14867     if (appData.debugMode) {
14868         fprintf(debugFP, "FEN castling rights:");
14869         for(i=0; i<nrCastlingRights; i++)
14870         fprintf(debugFP, " %d", board[CASTLING][i]);
14871         fprintf(debugFP, "\n");
14872     }
14873
14874       while(*p==' ') p++;
14875     }
14876
14877     /* read e.p. field in games that know e.p. capture */
14878     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14879        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14880       if(*p=='-') {
14881         p++; board[EP_STATUS] = EP_NONE;
14882       } else {
14883          char c = *p++ - AAA;
14884
14885          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14886          if(*p >= '0' && *p <='9') *p++;
14887          board[EP_STATUS] = c;
14888       }
14889     }
14890
14891
14892     if(sscanf(p, "%d", &i) == 1) {
14893         FENrulePlies = i; /* 50-move ply counter */
14894         /* (The move number is still ignored)    */
14895     }
14896
14897     return TRUE;
14898 }
14899       
14900 void
14901 EditPositionPasteFEN(char *fen)
14902 {
14903   if (fen != NULL) {
14904     Board initial_position;
14905
14906     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14907       DisplayError(_("Bad FEN position in clipboard"), 0);
14908       return ;
14909     } else {
14910       int savedBlackPlaysFirst = blackPlaysFirst;
14911       EditPositionEvent();
14912       blackPlaysFirst = savedBlackPlaysFirst;
14913       CopyBoard(boards[0], initial_position);
14914       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14915       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14916       DisplayBothClocks();
14917       DrawPosition(FALSE, boards[currentMove]);
14918     }
14919   }
14920 }
14921
14922 static char cseq[12] = "\\   ";
14923
14924 Boolean set_cont_sequence(char *new_seq)
14925 {
14926     int len;
14927     Boolean ret;
14928
14929     // handle bad attempts to set the sequence
14930         if (!new_seq)
14931                 return 0; // acceptable error - no debug
14932
14933     len = strlen(new_seq);
14934     ret = (len > 0) && (len < sizeof(cseq));
14935     if (ret)
14936         strcpy(cseq, new_seq);
14937     else if (appData.debugMode)
14938         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14939     return ret;
14940 }
14941
14942 /*
14943     reformat a source message so words don't cross the width boundary.  internal
14944     newlines are not removed.  returns the wrapped size (no null character unless
14945     included in source message).  If dest is NULL, only calculate the size required
14946     for the dest buffer.  lp argument indicats line position upon entry, and it's
14947     passed back upon exit.
14948 */
14949 int wrap(char *dest, char *src, int count, int width, int *lp)
14950 {
14951     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14952
14953     cseq_len = strlen(cseq);
14954     old_line = line = *lp;
14955     ansi = len = clen = 0;
14956
14957     for (i=0; i < count; i++)
14958     {
14959         if (src[i] == '\033')
14960             ansi = 1;
14961
14962         // if we hit the width, back up
14963         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14964         {
14965             // store i & len in case the word is too long
14966             old_i = i, old_len = len;
14967
14968             // find the end of the last word
14969             while (i && src[i] != ' ' && src[i] != '\n')
14970             {
14971                 i--;
14972                 len--;
14973             }
14974
14975             // word too long?  restore i & len before splitting it
14976             if ((old_i-i+clen) >= width)
14977             {
14978                 i = old_i;
14979                 len = old_len;
14980             }
14981
14982             // extra space?
14983             if (i && src[i-1] == ' ')
14984                 len--;
14985
14986             if (src[i] != ' ' && src[i] != '\n')
14987             {
14988                 i--;
14989                 if (len)
14990                     len--;
14991             }
14992
14993             // now append the newline and continuation sequence
14994             if (dest)
14995                 dest[len] = '\n';
14996             len++;
14997             if (dest)
14998                 strncpy(dest+len, cseq, cseq_len);
14999             len += cseq_len;
15000             line = cseq_len;
15001             clen = cseq_len;
15002             continue;
15003         }
15004
15005         if (dest)
15006             dest[len] = src[i];
15007         len++;
15008         if (!ansi)
15009             line++;
15010         if (src[i] == '\n')
15011             line = 0;
15012         if (src[i] == 'm')
15013             ansi = 0;
15014     }
15015     if (dest && appData.debugMode)
15016     {
15017         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15018             count, width, line, len, *lp);
15019         show_bytes(debugFP, src, count);
15020         fprintf(debugFP, "\ndest: ");
15021         show_bytes(debugFP, dest, len);
15022         fprintf(debugFP, "\n");
15023     }
15024     *lp = dest ? line : old_line;
15025
15026     return len;
15027 }
15028
15029 // [HGM] vari: routines for shelving variations
15030
15031 void 
15032 PushTail(int firstMove, int lastMove)
15033 {
15034         int i, j, nrMoves = lastMove - firstMove;
15035
15036         if(appData.icsActive) { // only in local mode
15037                 forwardMostMove = currentMove; // mimic old ICS behavior
15038                 return;
15039         }
15040         if(storedGames >= MAX_VARIATIONS-1) return;
15041
15042         // push current tail of game on stack
15043         savedResult[storedGames] = gameInfo.result;
15044         savedDetails[storedGames] = gameInfo.resultDetails;
15045         gameInfo.resultDetails = NULL;
15046         savedFirst[storedGames] = firstMove;
15047         savedLast [storedGames] = lastMove;
15048         savedFramePtr[storedGames] = framePtr;
15049         framePtr -= nrMoves; // reserve space for the boards
15050         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15051             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15052             for(j=0; j<MOVE_LEN; j++)
15053                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15054             for(j=0; j<2*MOVE_LEN; j++)
15055                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15056             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15057             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15058             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15059             pvInfoList[firstMove+i-1].depth = 0;
15060             commentList[framePtr+i] = commentList[firstMove+i];
15061             commentList[firstMove+i] = NULL;
15062         }
15063
15064         storedGames++;
15065         forwardMostMove = firstMove; // truncate game so we can start variation
15066         if(storedGames == 1) GreyRevert(FALSE);
15067 }
15068
15069 Boolean
15070 PopTail(Boolean annotate)
15071 {
15072         int i, j, nrMoves;
15073         char buf[8000], moveBuf[20];
15074
15075         if(appData.icsActive) return FALSE; // only in local mode
15076         if(!storedGames) return FALSE; // sanity
15077         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15078
15079         storedGames--;
15080         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15081         nrMoves = savedLast[storedGames] - currentMove;
15082         if(annotate) {
15083                 int cnt = 10;
15084                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15085                 else strcpy(buf, "(");
15086                 for(i=currentMove; i<forwardMostMove; i++) {
15087                         if(WhiteOnMove(i))
15088                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15089                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15090                         strcat(buf, moveBuf);
15091                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15092                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15093                 }
15094                 strcat(buf, ")");
15095         }
15096         for(i=1; i<=nrMoves; i++) { // copy last variation back
15097             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15098             for(j=0; j<MOVE_LEN; j++)
15099                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15100             for(j=0; j<2*MOVE_LEN; j++)
15101                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15102             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15103             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15104             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15105             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15106             commentList[currentMove+i] = commentList[framePtr+i];
15107             commentList[framePtr+i] = NULL;
15108         }
15109         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15110         framePtr = savedFramePtr[storedGames];
15111         gameInfo.result = savedResult[storedGames];
15112         if(gameInfo.resultDetails != NULL) {
15113             free(gameInfo.resultDetails);
15114       }
15115         gameInfo.resultDetails = savedDetails[storedGames];
15116         forwardMostMove = currentMove + nrMoves;
15117         if(storedGames == 0) GreyRevert(TRUE);
15118         return TRUE;
15119 }
15120
15121 void 
15122 CleanupTail()
15123 {       // remove all shelved variations
15124         int i;
15125         for(i=0; i<storedGames; i++) {
15126             if(savedDetails[i])
15127                 free(savedDetails[i]);
15128             savedDetails[i] = NULL;
15129         }
15130         for(i=framePtr; i<MAX_MOVES; i++) {
15131                 if(commentList[i]) free(commentList[i]);
15132                 commentList[i] = NULL;
15133         }
15134         framePtr = MAX_MOVES-1;
15135         storedGames = 0;
15136 }
15137
15138 void
15139 LoadVariation(int index, char *text)
15140 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15141         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15142         int level = 0, move;
15143
15144         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15145         // first find outermost bracketing variation
15146         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15147             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15148                 if(*p == '{') wait = '}'; else
15149                 if(*p == '[') wait = ']'; else
15150                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15151                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15152             }
15153             if(*p == wait) wait = NULLCHAR; // closing ]} found
15154             p++;
15155         }
15156         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15157         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15158         end[1] = NULLCHAR; // clip off comment beyond variation
15159         ToNrEvent(currentMove-1);
15160         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15161         // kludge: use ParsePV() to append variation to game
15162         move = currentMove;
15163         ParsePV(start, TRUE);
15164         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15165         ClearPremoveHighlights();
15166         CommentPopDown();
15167         ToNrEvent(currentMove+1);
15168 }