Remake programVersion string after receiving engine features
[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 Boolean partnerBoardValid = 0;
247 char partnerStatus[MSG_SIZ];
248 Boolean partnerUp;
249 Boolean originalFlip;
250 Boolean twoBoards = 0;
251 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
252 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
253 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
254 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
255 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
256 int opponentKibitzes;
257 int lastSavedGame; /* [HGM] save: ID of game */
258 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
259 extern int chatCount;
260 int chattingPartner;
261 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
262
263 /* States for ics_getting_history */
264 #define H_FALSE 0
265 #define H_REQUESTED 1
266 #define H_GOT_REQ_HEADER 2
267 #define H_GOT_UNREQ_HEADER 3
268 #define H_GETTING_MOVES 4
269 #define H_GOT_UNWANTED_HEADER 5
270
271 /* whosays values for GameEnds */
272 #define GE_ICS 0
273 #define GE_ENGINE 1
274 #define GE_PLAYER 2
275 #define GE_FILE 3
276 #define GE_XBOARD 4
277 #define GE_ENGINE1 5
278 #define GE_ENGINE2 6
279
280 /* Maximum number of games in a cmail message */
281 #define CMAIL_MAX_GAMES 20
282
283 /* Different types of move when calling RegisterMove */
284 #define CMAIL_MOVE   0
285 #define CMAIL_RESIGN 1
286 #define CMAIL_DRAW   2
287 #define CMAIL_ACCEPT 3
288
289 /* Different types of result to remember for each game */
290 #define CMAIL_NOT_RESULT 0
291 #define CMAIL_OLD_RESULT 1
292 #define CMAIL_NEW_RESULT 2
293
294 /* Telnet protocol constants */
295 #define TN_WILL 0373
296 #define TN_WONT 0374
297 #define TN_DO   0375
298 #define TN_DONT 0376
299 #define TN_IAC  0377
300 #define TN_ECHO 0001
301 #define TN_SGA  0003
302 #define TN_PORT 23
303
304 /* [AS] */
305 static char * safeStrCpy( char * dst, const char * src, size_t count )
306 {
307     assert( dst != NULL );
308     assert( src != NULL );
309     assert( count > 0 );
310
311     strncpy( dst, src, count );
312     dst[ count-1 ] = '\0';
313     return dst;
314 }
315
316 /* Some compiler can't cast u64 to double
317  * This function do the job for us:
318
319  * We use the highest bit for cast, this only
320  * works if the highest bit is not
321  * in use (This should not happen)
322  *
323  * We used this for all compiler
324  */
325 double
326 u64ToDouble(u64 value)
327 {
328   double r;
329   u64 tmp = value & u64Const(0x7fffffffffffffff);
330   r = (double)(s64)tmp;
331   if (value & u64Const(0x8000000000000000))
332        r +=  9.2233720368547758080e18; /* 2^63 */
333  return r;
334 }
335
336 /* Fake up flags for now, as we aren't keeping track of castling
337    availability yet. [HGM] Change of logic: the flag now only
338    indicates the type of castlings allowed by the rule of the game.
339    The actual rights themselves are maintained in the array
340    castlingRights, as part of the game history, and are not probed
341    by this function.
342  */
343 int
344 PosFlags(index)
345 {
346   int flags = F_ALL_CASTLE_OK;
347   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
348   switch (gameInfo.variant) {
349   case VariantSuicide:
350     flags &= ~F_ALL_CASTLE_OK;
351   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
352     flags |= F_IGNORE_CHECK;
353   case VariantLosers:
354     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
355     break;
356   case VariantAtomic:
357     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
358     break;
359   case VariantKriegspiel:
360     flags |= F_KRIEGSPIEL_CAPTURE;
361     break;
362   case VariantCapaRandom: 
363   case VariantFischeRandom:
364     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
365   case VariantNoCastle:
366   case VariantShatranj:
367   case VariantCourier:
368   case VariantMakruk:
369     flags &= ~F_ALL_CASTLE_OK;
370     break;
371   default:
372     break;
373   }
374   return flags;
375 }
376
377 FILE *gameFileFP, *debugFP;
378
379 /* 
380     [AS] Note: sometimes, the sscanf() function is used to parse the input
381     into a fixed-size buffer. Because of this, we must be prepared to
382     receive strings as long as the size of the input buffer, which is currently
383     set to 4K for Windows and 8K for the rest.
384     So, we must either allocate sufficiently large buffers here, or
385     reduce the size of the input buffer in the input reading part.
386 */
387
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
390 char thinkOutput1[MSG_SIZ*10];
391
392 ChessProgramState first, second;
393
394 /* premove variables */
395 int premoveToX = 0;
396 int premoveToY = 0;
397 int premoveFromX = 0;
398 int premoveFromY = 0;
399 int premovePromoChar = 0;
400 int gotPremove = 0;
401 Boolean alarmSounded;
402 /* end premove variables */
403
404 char *ics_prefix = "$";
405 int ics_type = ICS_GENERIC;
406
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
408 int pauseExamForwardMostMove = 0;
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
415 int whiteFlag = FALSE, blackFlag = FALSE;
416 int userOfferedDraw = FALSE;
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
419 int cmailMoveType[CMAIL_MAX_GAMES];
420 long ics_clock_paused = 0;
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
423 GameMode gameMode = BeginningOfGame;
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
427 int hiddenThinkOutputState = 0; /* [AS] */
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
429 int adjudicateLossPlies = 6;
430 char white_holding[64], black_holding[64];
431 TimeMark lastNodeCountTime;
432 long lastNodeCount=0;
433 int have_sent_ICS_logon = 0;
434 int movesPerSession;
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
436 long timeControl_2; /* [AS] Allow separate time controls */
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
438 long timeRemaining[2][MAX_MOVES];
439 int matchGame = 0;
440 TimeMark programStartTime;
441 char ics_handle[MSG_SIZ];
442 int have_set_title = 0;
443
444 /* animateTraining preserves the state of appData.animate
445  * when Training mode is activated. This allows the
446  * response to be animated when appData.animate == TRUE and
447  * appData.animateDragging == TRUE.
448  */
449 Boolean animateTraining;
450
451 GameInfo gameInfo;
452
453 AppData appData;
454
455 Board boards[MAX_MOVES];
456 /* [HGM] Following 7 needed for accurate legality tests: */
457 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
458 signed char  initialRights[BOARD_FILES];
459 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
460 int   initialRulePlies, FENrulePlies;
461 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
462 int loadFlag = 0; 
463 int shuffleOpenings;
464 int mute; // mute all sounds
465
466 // [HGM] vari: next 12 to save and restore variations
467 #define MAX_VARIATIONS 10
468 int framePtr = MAX_MOVES-1; // points to free stack entry
469 int storedGames = 0;
470 int savedFirst[MAX_VARIATIONS];
471 int savedLast[MAX_VARIATIONS];
472 int savedFramePtr[MAX_VARIATIONS];
473 char *savedDetails[MAX_VARIATIONS];
474 ChessMove savedResult[MAX_VARIATIONS];
475
476 void PushTail P((int firstMove, int lastMove));
477 Boolean PopTail P((Boolean annotate));
478 void CleanupTail P((void));
479
480 ChessSquare  FIDEArray[2][BOARD_FILES] = {
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484         BlackKing, BlackBishop, BlackKnight, BlackRook }
485 };
486
487 ChessSquare twoKingsArray[2][BOARD_FILES] = {
488     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
489         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
490     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
491         BlackKing, BlackKing, BlackKnight, BlackRook }
492 };
493
494 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
495     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
496         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
497     { BlackRook, BlackMan, BlackBishop, BlackQueen,
498         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
499 };
500
501 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
505         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
506 };
507
508 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
510         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
512         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
513 };
514
515 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
516     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
517         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackMan, BlackFerz,
519         BlackKing, BlackMan, BlackKnight, BlackRook }
520 };
521
522
523 #if (BOARD_FILES>=10)
524 ChessSquare ShogiArray[2][BOARD_FILES] = {
525     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
526         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
527     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
528         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
529 };
530
531 ChessSquare XiangqiArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
533         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
534     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
535         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
536 };
537
538 ChessSquare CapablancaArray[2][BOARD_FILES] = {
539     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
540         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
541     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
542         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
543 };
544
545 ChessSquare GreatArray[2][BOARD_FILES] = {
546     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
547         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
548     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
549         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
550 };
551
552 ChessSquare JanusArray[2][BOARD_FILES] = {
553     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
554         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
555     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
556         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
557 };
558
559 #ifdef GOTHIC
560 ChessSquare GothicArray[2][BOARD_FILES] = {
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
562         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
564         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
565 };
566 #else // !GOTHIC
567 #define GothicArray CapablancaArray
568 #endif // !GOTHIC
569
570 #ifdef FALCON
571 ChessSquare FalconArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
573         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
575         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
576 };
577 #else // !FALCON
578 #define FalconArray CapablancaArray
579 #endif // !FALCON
580
581 #else // !(BOARD_FILES>=10)
582 #define XiangqiPosition FIDEArray
583 #define CapablancaArray FIDEArray
584 #define GothicArray FIDEArray
585 #define GreatArray FIDEArray
586 #endif // !(BOARD_FILES>=10)
587
588 #if (BOARD_FILES>=12)
589 ChessSquare CourierArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
591         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
593         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
594 };
595 #else // !(BOARD_FILES>=12)
596 #define CourierArray CapablancaArray
597 #endif // !(BOARD_FILES>=12)
598
599
600 Board initialPosition;
601
602
603 /* Convert str to a rating. Checks for special cases of "----",
604
605    "++++", etc. Also strips ()'s */
606 int
607 string_to_rating(str)
608   char *str;
609 {
610   while(*str && !isdigit(*str)) ++str;
611   if (!*str)
612     return 0;   /* One of the special "no rating" cases */
613   else
614     return atoi(str);
615 }
616
617 void
618 ClearProgramStats()
619 {
620     /* Init programStats */
621     programStats.movelist[0] = 0;
622     programStats.depth = 0;
623     programStats.nr_moves = 0;
624     programStats.moves_left = 0;
625     programStats.nodes = 0;
626     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
627     programStats.score = 0;
628     programStats.got_only_move = 0;
629     programStats.got_fail = 0;
630     programStats.line_is_book = 0;
631 }
632
633 void
634 InitBackEnd1()
635 {
636     int matched, min, sec;
637
638     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
639
640     GetTimeMark(&programStartTime);
641     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
642
643     ClearProgramStats();
644     programStats.ok_to_send = 1;
645     programStats.seen_stat = 0;
646
647     /*
648      * Initialize game list
649      */
650     ListNew(&gameList);
651
652
653     /*
654      * Internet chess server status
655      */
656     if (appData.icsActive) {
657         appData.matchMode = FALSE;
658         appData.matchGames = 0;
659 #if ZIPPY       
660         appData.noChessProgram = !appData.zippyPlay;
661 #else
662         appData.zippyPlay = FALSE;
663         appData.zippyTalk = FALSE;
664         appData.noChessProgram = TRUE;
665 #endif
666         if (*appData.icsHelper != NULLCHAR) {
667             appData.useTelnet = TRUE;
668             appData.telnetProgram = appData.icsHelper;
669         }
670     } else {
671         appData.zippyTalk = appData.zippyPlay = FALSE;
672     }
673
674     /* [AS] Initialize pv info list [HGM] and game state */
675     {
676         int i, j;
677
678         for( i=0; i<=framePtr; i++ ) {
679             pvInfoList[i].depth = -1;
680             boards[i][EP_STATUS] = EP_NONE;
681             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
682         }
683     }
684
685     /*
686      * Parse timeControl resource
687      */
688     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
689                           appData.movesPerSession)) {
690         char buf[MSG_SIZ];
691         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
692         DisplayFatalError(buf, 0, 2);
693     }
694
695     /*
696      * Parse searchTime resource
697      */
698     if (*appData.searchTime != NULLCHAR) {
699         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
700         if (matched == 1) {
701             searchTime = min * 60;
702         } else if (matched == 2) {
703             searchTime = min * 60 + sec;
704         } else {
705             char buf[MSG_SIZ];
706             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
707             DisplayFatalError(buf, 0, 2);
708         }
709     }
710
711     /* [AS] Adjudication threshold */
712     adjudicateLossThreshold = appData.adjudicateLossThreshold;
713     
714     first.which = "first";
715     second.which = "second";
716     first.maybeThinking = second.maybeThinking = FALSE;
717     first.pr = second.pr = NoProc;
718     first.isr = second.isr = NULL;
719     first.sendTime = second.sendTime = 2;
720     first.sendDrawOffers = 1;
721     if (appData.firstPlaysBlack) {
722         first.twoMachinesColor = "black\n";
723         second.twoMachinesColor = "white\n";
724     } else {
725         first.twoMachinesColor = "white\n";
726         second.twoMachinesColor = "black\n";
727     }
728     first.program = appData.firstChessProgram;
729     second.program = appData.secondChessProgram;
730     first.host = appData.firstHost;
731     second.host = appData.secondHost;
732     first.dir = appData.firstDirectory;
733     second.dir = appData.secondDirectory;
734     first.other = &second;
735     second.other = &first;
736     first.initString = appData.initString;
737     second.initString = appData.secondInitString;
738     first.computerString = appData.firstComputerString;
739     second.computerString = appData.secondComputerString;
740     first.useSigint = second.useSigint = TRUE;
741     first.useSigterm = second.useSigterm = TRUE;
742     first.reuse = appData.reuseFirst;
743     second.reuse = appData.reuseSecond;
744     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
745     second.nps = appData.secondNPS;
746     first.useSetboard = second.useSetboard = FALSE;
747     first.useSAN = second.useSAN = FALSE;
748     first.usePing = second.usePing = FALSE;
749     first.lastPing = second.lastPing = 0;
750     first.lastPong = second.lastPong = 0;
751     first.usePlayother = second.usePlayother = FALSE;
752     first.useColors = second.useColors = TRUE;
753     first.useUsermove = second.useUsermove = FALSE;
754     first.sendICS = second.sendICS = FALSE;
755     first.sendName = second.sendName = appData.icsActive;
756     first.sdKludge = second.sdKludge = FALSE;
757     first.stKludge = second.stKludge = FALSE;
758     TidyProgramName(first.program, first.host, first.tidy);
759     TidyProgramName(second.program, second.host, second.tidy);
760     first.matchWins = second.matchWins = 0;
761     strcpy(first.variants, appData.variant);
762     strcpy(second.variants, appData.variant);
763     first.analysisSupport = second.analysisSupport = 2; /* detect */
764     first.analyzing = second.analyzing = FALSE;
765     first.initDone = second.initDone = FALSE;
766
767     /* New features added by Tord: */
768     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
769     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
770     /* End of new features added by Tord. */
771     first.fenOverride  = appData.fenOverride1;
772     second.fenOverride = appData.fenOverride2;
773
774     /* [HGM] time odds: set factor for each machine */
775     first.timeOdds  = appData.firstTimeOdds;
776     second.timeOdds = appData.secondTimeOdds;
777     { float norm = 1;
778         if(appData.timeOddsMode) {
779             norm = first.timeOdds;
780             if(norm > second.timeOdds) norm = second.timeOdds;
781         }
782         first.timeOdds /= norm;
783         second.timeOdds /= norm;
784     }
785
786     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
787     first.accumulateTC = appData.firstAccumulateTC;
788     second.accumulateTC = appData.secondAccumulateTC;
789     first.maxNrOfSessions = second.maxNrOfSessions = 1;
790
791     /* [HGM] debug */
792     first.debug = second.debug = FALSE;
793     first.supportsNPS = second.supportsNPS = UNKNOWN;
794
795     /* [HGM] options */
796     first.optionSettings  = appData.firstOptions;
797     second.optionSettings = appData.secondOptions;
798
799     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
800     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
801     first.isUCI = appData.firstIsUCI; /* [AS] */
802     second.isUCI = appData.secondIsUCI; /* [AS] */
803     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
804     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
805
806     if (appData.firstProtocolVersion > PROTOVER ||
807         appData.firstProtocolVersion < 1) {
808       char buf[MSG_SIZ];
809       sprintf(buf, _("protocol version %d not supported"),
810               appData.firstProtocolVersion);
811       DisplayFatalError(buf, 0, 2);
812     } else {
813       first.protocolVersion = appData.firstProtocolVersion;
814     }
815
816     if (appData.secondProtocolVersion > PROTOVER ||
817         appData.secondProtocolVersion < 1) {
818       char buf[MSG_SIZ];
819       sprintf(buf, _("protocol version %d not supported"),
820               appData.secondProtocolVersion);
821       DisplayFatalError(buf, 0, 2);
822     } else {
823       second.protocolVersion = appData.secondProtocolVersion;
824     }
825
826     if (appData.icsActive) {
827         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
828 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
829     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
830         appData.clockMode = FALSE;
831         first.sendTime = second.sendTime = 0;
832     }
833     
834 #if ZIPPY
835     /* Override some settings from environment variables, for backward
836        compatibility.  Unfortunately it's not feasible to have the env
837        vars just set defaults, at least in xboard.  Ugh.
838     */
839     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
840       ZippyInit();
841     }
842 #endif
843     
844     if (appData.noChessProgram) {
845         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
846         sprintf(programVersion, "%s", PACKAGE_STRING);
847     } else {
848       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
849       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
850       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
851     }
852
853     if (!appData.icsActive) {
854       char buf[MSG_SIZ];
855       /* Check for variants that are supported only in ICS mode,
856          or not at all.  Some that are accepted here nevertheless
857          have bugs; see comments below.
858       */
859       VariantClass variant = StringToVariant(appData.variant);
860       switch (variant) {
861       case VariantBughouse:     /* need four players and two boards */
862       case VariantKriegspiel:   /* need to hide pieces and move details */
863       /* case VariantFischeRandom: (Fabien: moved below) */
864         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
865         DisplayFatalError(buf, 0, 2);
866         return;
867
868       case VariantUnknown:
869       case VariantLoadable:
870       case Variant29:
871       case Variant30:
872       case Variant31:
873       case Variant32:
874       case Variant33:
875       case Variant34:
876       case Variant35:
877       case Variant36:
878       default:
879         sprintf(buf, _("Unknown variant name %s"), appData.variant);
880         DisplayFatalError(buf, 0, 2);
881         return;
882
883       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
884       case VariantFairy:      /* [HGM] TestLegality definitely off! */
885       case VariantGothic:     /* [HGM] should work */
886       case VariantCapablanca: /* [HGM] should work */
887       case VariantCourier:    /* [HGM] initial forced moves not implemented */
888       case VariantShogi:      /* [HGM] drops not tested for legality */
889       case VariantKnightmate: /* [HGM] should work */
890       case VariantCylinder:   /* [HGM] untested */
891       case VariantFalcon:     /* [HGM] untested */
892       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
893                                  offboard interposition not understood */
894       case VariantNormal:     /* definitely works! */
895       case VariantWildCastle: /* pieces not automatically shuffled */
896       case VariantNoCastle:   /* pieces not automatically shuffled */
897       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
898       case VariantLosers:     /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantSuicide:    /* should work except for win condition,
901                                  and doesn't know captures are mandatory */
902       case VariantGiveaway:   /* should work except for win condition,
903                                  and doesn't know captures are mandatory */
904       case VariantTwoKings:   /* should work */
905       case VariantAtomic:     /* should work except for win condition */
906       case Variant3Check:     /* should work except for win condition */
907       case VariantShatranj:   /* should work except for all win conditions */
908       case VariantMakruk:     /* should work except for daw countdown */
909       case VariantBerolina:   /* might work if TestLegality is off */
910       case VariantCapaRandom: /* should work */
911       case VariantJanus:      /* should work */
912       case VariantSuper:      /* experimental */
913       case VariantGreat:      /* experimental, requires legality testing to be off */
914         break;
915       }
916     }
917
918     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
919     InitEngineUCI( installDir, &second );
920 }
921
922 int NextIntegerFromString( char ** str, long * value )
923 {
924     int result = -1;
925     char * s = *str;
926
927     while( *s == ' ' || *s == '\t' ) {
928         s++;
929     }
930
931     *value = 0;
932
933     if( *s >= '0' && *s <= '9' ) {
934         while( *s >= '0' && *s <= '9' ) {
935             *value = *value * 10 + (*s - '0');
936             s++;
937         }
938
939         result = 0;
940     }
941
942     *str = s;
943
944     return result;
945 }
946
947 int NextTimeControlFromString( char ** str, long * value )
948 {
949     long temp;
950     int result = NextIntegerFromString( str, &temp );
951
952     if( result == 0 ) {
953         *value = temp * 60; /* Minutes */
954         if( **str == ':' ) {
955             (*str)++;
956             result = NextIntegerFromString( str, &temp );
957             *value += temp; /* Seconds */
958         }
959     }
960
961     return result;
962 }
963
964 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
965 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
966     int result = -1; long temp, temp2;
967
968     if(**str != '+') return -1; // old params remain in force!
969     (*str)++;
970     if( NextTimeControlFromString( str, &temp ) ) return -1;
971
972     if(**str != '/') {
973         /* time only: incremental or sudden-death time control */
974         if(**str == '+') { /* increment follows; read it */
975             (*str)++;
976             if(result = NextIntegerFromString( str, &temp2)) return -1;
977             *inc = temp2 * 1000;
978         } else *inc = 0;
979         *moves = 0; *tc = temp * 1000; 
980         return 0;
981     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
982
983     (*str)++; /* classical time control */
984     result = NextTimeControlFromString( str, &temp2);
985     if(result == 0) {
986         *moves = temp/60;
987         *tc    = temp2 * 1000;
988         *inc   = 0;
989     }
990     return result;
991 }
992
993 int GetTimeQuota(int movenr)
994 {   /* [HGM] get time to add from the multi-session time-control string */
995     int moves=1; /* kludge to force reading of first session */
996     long time, increment;
997     char *s = fullTimeControlString;
998
999     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1000     do {
1001         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1002         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1003         if(movenr == -1) return time;    /* last move before new session     */
1004         if(!moves) return increment;     /* current session is incremental   */
1005         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1006     } while(movenr >= -1);               /* try again for next session       */
1007
1008     return 0; // no new time quota on this move
1009 }
1010
1011 int
1012 ParseTimeControl(tc, ti, mps)
1013      char *tc;
1014      int ti;
1015      int mps;
1016 {
1017   long tc1;
1018   long tc2;
1019   char buf[MSG_SIZ];
1020   
1021   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1022   if(ti > 0) {
1023     if(mps)
1024       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1025     else sprintf(buf, "+%s+%d", tc, ti);
1026   } else {
1027     if(mps)
1028              sprintf(buf, "+%d/%s", mps, tc);
1029     else sprintf(buf, "+%s", tc);
1030   }
1031   fullTimeControlString = StrSave(buf);
1032   
1033   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1034     return FALSE;
1035   }
1036   
1037   if( *tc == '/' ) {
1038     /* Parse second time control */
1039     tc++;
1040     
1041     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1042       return FALSE;
1043     }
1044     
1045     if( tc2 == 0 ) {
1046       return FALSE;
1047     }
1048     
1049     timeControl_2 = tc2 * 1000;
1050   }
1051   else {
1052     timeControl_2 = 0;
1053   }
1054   
1055   if( tc1 == 0 ) {
1056     return FALSE;
1057   }
1058   
1059   timeControl = tc1 * 1000;
1060   
1061   if (ti >= 0) {
1062     timeIncrement = ti * 1000;  /* convert to ms */
1063     movesPerSession = 0;
1064   } else {
1065     timeIncrement = 0;
1066     movesPerSession = mps;
1067   }
1068   return TRUE;
1069 }
1070
1071 void
1072 InitBackEnd2()
1073 {
1074     if (appData.debugMode) {
1075         fprintf(debugFP, "%s\n", programVersion);
1076     }
1077
1078     set_cont_sequence(appData.wrapContSeq);
1079     if (appData.matchGames > 0) {
1080         appData.matchMode = TRUE;
1081     } else if (appData.matchMode) {
1082         appData.matchGames = 1;
1083     }
1084     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1085         appData.matchGames = appData.sameColorGames;
1086     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1087         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1088         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1089     }
1090     Reset(TRUE, FALSE);
1091     if (appData.noChessProgram || first.protocolVersion == 1) {
1092       InitBackEnd3();
1093     } else {
1094       /* kludge: allow timeout for initial "feature" commands */
1095       FreezeUI();
1096       DisplayMessage("", _("Starting chess program"));
1097       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1098     }
1099 }
1100
1101 void
1102 InitBackEnd3 P((void))
1103 {
1104     GameMode initialMode;
1105     char buf[MSG_SIZ];
1106     int err;
1107
1108     InitChessProgram(&first, startedFromSetupPosition);
1109
1110     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1111         free(programVersion);
1112         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1113         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1114     }
1115
1116     if (appData.icsActive) {
1117 #ifdef WIN32
1118         /* [DM] Make a console window if needed [HGM] merged ifs */
1119         ConsoleCreate(); 
1120 #endif
1121         err = establish();
1122         if (err != 0) {
1123             if (*appData.icsCommPort != NULLCHAR) {
1124                 sprintf(buf, _("Could not open comm port %s"),  
1125                         appData.icsCommPort);
1126             } else {
1127                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1128                         appData.icsHost, appData.icsPort);
1129             }
1130             DisplayFatalError(buf, err, 1);
1131             return;
1132         }
1133         SetICSMode();
1134         telnetISR =
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136         fromUserISR =
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1139             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1140     } else if (appData.noChessProgram) {
1141         SetNCPMode();
1142     } else {
1143         SetGNUMode();
1144     }
1145
1146     if (*appData.cmailGameName != NULLCHAR) {
1147         SetCmailMode();
1148         OpenLoopback(&cmailPR);
1149         cmailISR =
1150           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151     }
1152     
1153     ThawUI();
1154     DisplayMessage("", "");
1155     if (StrCaseCmp(appData.initialMode, "") == 0) {
1156       initialMode = BeginningOfGame;
1157     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158       initialMode = TwoMachinesPlay;
1159     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160       initialMode = AnalyzeFile; 
1161     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162       initialMode = AnalyzeMode;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164       initialMode = MachinePlaysWhite;
1165     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166       initialMode = MachinePlaysBlack;
1167     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168       initialMode = EditGame;
1169     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170       initialMode = EditPosition;
1171     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172       initialMode = Training;
1173     } else {
1174       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175       DisplayFatalError(buf, 0, 2);
1176       return;
1177     }
1178
1179     if (appData.matchMode) {
1180         /* Set up machine vs. machine match */
1181         if (appData.noChessProgram) {
1182             DisplayFatalError(_("Can't have a match with no chess programs"),
1183                               0, 2);
1184             return;
1185         }
1186         matchMode = TRUE;
1187         matchGame = 1;
1188         if (*appData.loadGameFile != NULLCHAR) {
1189             int index = appData.loadGameIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadGameFromFile(appData.loadGameFile,
1192                                   index,
1193                                   appData.loadGameFile, FALSE)) {
1194                 DisplayFatalError(_("Bad game file"), 0, 1);
1195                 return;
1196             }
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             int index = appData.loadPositionIndex; // [HGM] autoinc
1199             if(index<0) lastIndex = index = 1;
1200             if (!LoadPositionFromFile(appData.loadPositionFile,
1201                                       index,
1202                                       appData.loadPositionFile)) {
1203                 DisplayFatalError(_("Bad position file"), 0, 1);
1204                 return;
1205             }
1206         }
1207         TwoMachinesEvent();
1208     } else if (*appData.cmailGameName != NULLCHAR) {
1209         /* Set up cmail mode */
1210         ReloadCmailMsgEvent(TRUE);
1211     } else {
1212         /* Set up other modes */
1213         if (initialMode == AnalyzeFile) {
1214           if (*appData.loadGameFile == NULLCHAR) {
1215             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216             return;
1217           }
1218         }
1219         if (*appData.loadGameFile != NULLCHAR) {
1220             (void) LoadGameFromFile(appData.loadGameFile,
1221                                     appData.loadGameIndex,
1222                                     appData.loadGameFile, TRUE);
1223         } else if (*appData.loadPositionFile != NULLCHAR) {
1224             (void) LoadPositionFromFile(appData.loadPositionFile,
1225                                         appData.loadPositionIndex,
1226                                         appData.loadPositionFile);
1227             /* [HGM] try to make self-starting even after FEN load */
1228             /* to allow automatic setup of fairy variants with wtm */
1229             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230                 gameMode = BeginningOfGame;
1231                 setboardSpoiledMachineBlack = 1;
1232             }
1233             /* [HGM] loadPos: make that every new game uses the setup */
1234             /* from file as long as we do not switch variant          */
1235             if(!blackPlaysFirst) {
1236                 startedFromPositionFile = TRUE;
1237                 CopyBoard(filePosition, boards[0]);
1238             }
1239         }
1240         if (initialMode == AnalyzeMode) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1247             return;
1248           }
1249           AnalyzeModeEvent();
1250         } else if (initialMode == AnalyzeFile) {
1251           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1252           ShowThinkingEvent();
1253           AnalyzeFileEvent();
1254           AnalysisPeriodicEvent(1);
1255         } else if (initialMode == MachinePlaysWhite) {
1256           if (appData.noChessProgram) {
1257             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1258                               0, 2);
1259             return;
1260           }
1261           if (appData.icsActive) {
1262             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1263                               0, 2);
1264             return;
1265           }
1266           MachineWhiteEvent();
1267         } else if (initialMode == MachinePlaysBlack) {
1268           if (appData.noChessProgram) {
1269             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1270                               0, 2);
1271             return;
1272           }
1273           if (appData.icsActive) {
1274             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1275                               0, 2);
1276             return;
1277           }
1278           MachineBlackEvent();
1279         } else if (initialMode == TwoMachinesPlay) {
1280           if (appData.noChessProgram) {
1281             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1282                               0, 2);
1283             return;
1284           }
1285           if (appData.icsActive) {
1286             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1287                               0, 2);
1288             return;
1289           }
1290           TwoMachinesEvent();
1291         } else if (initialMode == EditGame) {
1292           EditGameEvent();
1293         } else if (initialMode == EditPosition) {
1294           EditPositionEvent();
1295         } else if (initialMode == Training) {
1296           if (*appData.loadGameFile == NULLCHAR) {
1297             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1298             return;
1299           }
1300           TrainingEvent();
1301         }
1302     }
1303 }
1304
1305 /*
1306  * Establish will establish a contact to a remote host.port.
1307  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1308  *  used to talk to the host.
1309  * Returns 0 if okay, error code if not.
1310  */
1311 int
1312 establish()
1313 {
1314     char buf[MSG_SIZ];
1315
1316     if (*appData.icsCommPort != NULLCHAR) {
1317         /* Talk to the host through a serial comm port */
1318         return OpenCommPort(appData.icsCommPort, &icsPR);
1319
1320     } else if (*appData.gateway != NULLCHAR) {
1321         if (*appData.remoteShell == NULLCHAR) {
1322             /* Use the rcmd protocol to run telnet program on a gateway host */
1323             snprintf(buf, sizeof(buf), "%s %s %s",
1324                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1325             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1326
1327         } else {
1328             /* Use the rsh program to run telnet program on a gateway host */
1329             if (*appData.remoteUser == NULLCHAR) {
1330                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1331                         appData.gateway, appData.telnetProgram,
1332                         appData.icsHost, appData.icsPort);
1333             } else {
1334                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1335                         appData.remoteShell, appData.gateway, 
1336                         appData.remoteUser, appData.telnetProgram,
1337                         appData.icsHost, appData.icsPort);
1338             }
1339             return StartChildProcess(buf, "", &icsPR);
1340
1341         }
1342     } else if (appData.useTelnet) {
1343         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1344
1345     } else {
1346         /* TCP socket interface differs somewhat between
1347            Unix and NT; handle details in the front end.
1348            */
1349         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1350     }
1351 }
1352
1353 void
1354 show_bytes(fp, buf, count)
1355      FILE *fp;
1356      char *buf;
1357      int count;
1358 {
1359     while (count--) {
1360         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1361             fprintf(fp, "\\%03o", *buf & 0xff);
1362         } else {
1363             putc(*buf, fp);
1364         }
1365         buf++;
1366     }
1367     fflush(fp);
1368 }
1369
1370 /* Returns an errno value */
1371 int
1372 OutputMaybeTelnet(pr, message, count, outError)
1373      ProcRef pr;
1374      char *message;
1375      int count;
1376      int *outError;
1377 {
1378     char buf[8192], *p, *q, *buflim;
1379     int left, newcount, outcount;
1380
1381     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1382         *appData.gateway != NULLCHAR) {
1383         if (appData.debugMode) {
1384             fprintf(debugFP, ">ICS: ");
1385             show_bytes(debugFP, message, count);
1386             fprintf(debugFP, "\n");
1387         }
1388         return OutputToProcess(pr, message, count, outError);
1389     }
1390
1391     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1392     p = message;
1393     q = buf;
1394     left = count;
1395     newcount = 0;
1396     while (left) {
1397         if (q >= buflim) {
1398             if (appData.debugMode) {
1399                 fprintf(debugFP, ">ICS: ");
1400                 show_bytes(debugFP, buf, newcount);
1401                 fprintf(debugFP, "\n");
1402             }
1403             outcount = OutputToProcess(pr, buf, newcount, outError);
1404             if (outcount < newcount) return -1; /* to be sure */
1405             q = buf;
1406             newcount = 0;
1407         }
1408         if (*p == '\n') {
1409             *q++ = '\r';
1410             newcount++;
1411         } else if (((unsigned char) *p) == TN_IAC) {
1412             *q++ = (char) TN_IAC;
1413             newcount ++;
1414         }
1415         *q++ = *p++;
1416         newcount++;
1417         left--;
1418     }
1419     if (appData.debugMode) {
1420         fprintf(debugFP, ">ICS: ");
1421         show_bytes(debugFP, buf, newcount);
1422         fprintf(debugFP, "\n");
1423     }
1424     outcount = OutputToProcess(pr, buf, newcount, outError);
1425     if (outcount < newcount) return -1; /* to be sure */
1426     return count;
1427 }
1428
1429 void
1430 read_from_player(isr, closure, message, count, error)
1431      InputSourceRef isr;
1432      VOIDSTAR closure;
1433      char *message;
1434      int count;
1435      int error;
1436 {
1437     int outError, outCount;
1438     static int gotEof = 0;
1439
1440     /* Pass data read from player on to ICS */
1441     if (count > 0) {
1442         gotEof = 0;
1443         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1444         if (outCount < count) {
1445             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446         }
1447     } else if (count < 0) {
1448         RemoveInputSource(isr);
1449         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1450     } else if (gotEof++ > 0) {
1451         RemoveInputSource(isr);
1452         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1453     }
1454 }
1455
1456 void
1457 KeepAlive()
1458 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1459     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1460     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1461     SendToICS("date\n");
1462     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1463 }
1464
1465 /* added routine for printf style output to ics */
1466 void ics_printf(char *format, ...)
1467 {
1468     char buffer[MSG_SIZ];
1469     va_list args;
1470
1471     va_start(args, format);
1472     vsnprintf(buffer, sizeof(buffer), format, args);
1473     buffer[sizeof(buffer)-1] = '\0';
1474     SendToICS(buffer);
1475     va_end(args);
1476 }
1477
1478 void
1479 SendToICS(s)
1480      char *s;
1481 {
1482     int count, outCount, outError;
1483
1484     if (icsPR == NULL) return;
1485
1486     count = strlen(s);
1487     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1488     if (outCount < count) {
1489         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1490     }
1491 }
1492
1493 /* This is used for sending logon scripts to the ICS. Sending
1494    without a delay causes problems when using timestamp on ICC
1495    (at least on my machine). */
1496 void
1497 SendToICSDelayed(s,msdelay)
1498      char *s;
1499      long msdelay;
1500 {
1501     int count, outCount, outError;
1502
1503     if (icsPR == NULL) return;
1504
1505     count = strlen(s);
1506     if (appData.debugMode) {
1507         fprintf(debugFP, ">ICS: ");
1508         show_bytes(debugFP, s, count);
1509         fprintf(debugFP, "\n");
1510     }
1511     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1512                                       msdelay);
1513     if (outCount < count) {
1514         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1515     }
1516 }
1517
1518
1519 /* Remove all highlighting escape sequences in s
1520    Also deletes any suffix starting with '(' 
1521    */
1522 char *
1523 StripHighlightAndTitle(s)
1524      char *s;
1525 {
1526     static char retbuf[MSG_SIZ];
1527     char *p = retbuf;
1528
1529     while (*s != NULLCHAR) {
1530         while (*s == '\033') {
1531             while (*s != NULLCHAR && !isalpha(*s)) s++;
1532             if (*s != NULLCHAR) s++;
1533         }
1534         while (*s != NULLCHAR && *s != '\033') {
1535             if (*s == '(' || *s == '[') {
1536                 *p = NULLCHAR;
1537                 return retbuf;
1538             }
1539             *p++ = *s++;
1540         }
1541     }
1542     *p = NULLCHAR;
1543     return retbuf;
1544 }
1545
1546 /* Remove all highlighting escape sequences in s */
1547 char *
1548 StripHighlight(s)
1549      char *s;
1550 {
1551     static char retbuf[MSG_SIZ];
1552     char *p = retbuf;
1553
1554     while (*s != NULLCHAR) {
1555         while (*s == '\033') {
1556             while (*s != NULLCHAR && !isalpha(*s)) s++;
1557             if (*s != NULLCHAR) s++;
1558         }
1559         while (*s != NULLCHAR && *s != '\033') {
1560             *p++ = *s++;
1561         }
1562     }
1563     *p = NULLCHAR;
1564     return retbuf;
1565 }
1566
1567 char *variantNames[] = VARIANT_NAMES;
1568 char *
1569 VariantName(v)
1570      VariantClass v;
1571 {
1572     return variantNames[v];
1573 }
1574
1575
1576 /* Identify a variant from the strings the chess servers use or the
1577    PGN Variant tag names we use. */
1578 VariantClass
1579 StringToVariant(e)
1580      char *e;
1581 {
1582     char *p;
1583     int wnum = -1;
1584     VariantClass v = VariantNormal;
1585     int i, found = FALSE;
1586     char buf[MSG_SIZ];
1587
1588     if (!e) return v;
1589
1590     /* [HGM] skip over optional board-size prefixes */
1591     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1592         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1593         while( *e++ != '_');
1594     }
1595
1596     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1597         v = VariantNormal;
1598         found = TRUE;
1599     } else
1600     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1601       if (StrCaseStr(e, variantNames[i])) {
1602         v = (VariantClass) i;
1603         found = TRUE;
1604         break;
1605       }
1606     }
1607
1608     if (!found) {
1609       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1610           || StrCaseStr(e, "wild/fr") 
1611           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1612         v = VariantFischeRandom;
1613       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1614                  (i = 1, p = StrCaseStr(e, "w"))) {
1615         p += i;
1616         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1617         if (isdigit(*p)) {
1618           wnum = atoi(p);
1619         } else {
1620           wnum = -1;
1621         }
1622         switch (wnum) {
1623         case 0: /* FICS only, actually */
1624         case 1:
1625           /* Castling legal even if K starts on d-file */
1626           v = VariantWildCastle;
1627           break;
1628         case 2:
1629         case 3:
1630         case 4:
1631           /* Castling illegal even if K & R happen to start in
1632              normal positions. */
1633           v = VariantNoCastle;
1634           break;
1635         case 5:
1636         case 7:
1637         case 8:
1638         case 10:
1639         case 11:
1640         case 12:
1641         case 13:
1642         case 14:
1643         case 15:
1644         case 18:
1645         case 19:
1646           /* Castling legal iff K & R start in normal positions */
1647           v = VariantNormal;
1648           break;
1649         case 6:
1650         case 20:
1651         case 21:
1652           /* Special wilds for position setup; unclear what to do here */
1653           v = VariantLoadable;
1654           break;
1655         case 9:
1656           /* Bizarre ICC game */
1657           v = VariantTwoKings;
1658           break;
1659         case 16:
1660           v = VariantKriegspiel;
1661           break;
1662         case 17:
1663           v = VariantLosers;
1664           break;
1665         case 22:
1666           v = VariantFischeRandom;
1667           break;
1668         case 23:
1669           v = VariantCrazyhouse;
1670           break;
1671         case 24:
1672           v = VariantBughouse;
1673           break;
1674         case 25:
1675           v = Variant3Check;
1676           break;
1677         case 26:
1678           /* Not quite the same as FICS suicide! */
1679           v = VariantGiveaway;
1680           break;
1681         case 27:
1682           v = VariantAtomic;
1683           break;
1684         case 28:
1685           v = VariantShatranj;
1686           break;
1687
1688         /* Temporary names for future ICC types.  The name *will* change in 
1689            the next xboard/WinBoard release after ICC defines it. */
1690         case 29:
1691           v = Variant29;
1692           break;
1693         case 30:
1694           v = Variant30;
1695           break;
1696         case 31:
1697           v = Variant31;
1698           break;
1699         case 32:
1700           v = Variant32;
1701           break;
1702         case 33:
1703           v = Variant33;
1704           break;
1705         case 34:
1706           v = Variant34;
1707           break;
1708         case 35:
1709           v = Variant35;
1710           break;
1711         case 36:
1712           v = Variant36;
1713           break;
1714         case 37:
1715           v = VariantShogi;
1716           break;
1717         case 38:
1718           v = VariantXiangqi;
1719           break;
1720         case 39:
1721           v = VariantCourier;
1722           break;
1723         case 40:
1724           v = VariantGothic;
1725           break;
1726         case 41:
1727           v = VariantCapablanca;
1728           break;
1729         case 42:
1730           v = VariantKnightmate;
1731           break;
1732         case 43:
1733           v = VariantFairy;
1734           break;
1735         case 44:
1736           v = VariantCylinder;
1737           break;
1738         case 45:
1739           v = VariantFalcon;
1740           break;
1741         case 46:
1742           v = VariantCapaRandom;
1743           break;
1744         case 47:
1745           v = VariantBerolina;
1746           break;
1747         case 48:
1748           v = VariantJanus;
1749           break;
1750         case 49:
1751           v = VariantSuper;
1752           break;
1753         case 50:
1754           v = VariantGreat;
1755           break;
1756         case -1:
1757           /* Found "wild" or "w" in the string but no number;
1758              must assume it's normal chess. */
1759           v = VariantNormal;
1760           break;
1761         default:
1762           sprintf(buf, _("Unknown wild type %d"), wnum);
1763           DisplayError(buf, 0);
1764           v = VariantUnknown;
1765           break;
1766         }
1767       }
1768     }
1769     if (appData.debugMode) {
1770       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1771               e, wnum, VariantName(v));
1772     }
1773     return v;
1774 }
1775
1776 static int leftover_start = 0, leftover_len = 0;
1777 char star_match[STAR_MATCH_N][MSG_SIZ];
1778
1779 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1780    advance *index beyond it, and set leftover_start to the new value of
1781    *index; else return FALSE.  If pattern contains the character '*', it
1782    matches any sequence of characters not containing '\r', '\n', or the
1783    character following the '*' (if any), and the matched sequence(s) are
1784    copied into star_match.
1785    */
1786 int
1787 looking_at(buf, index, pattern)
1788      char *buf;
1789      int *index;
1790      char *pattern;
1791 {
1792     char *bufp = &buf[*index], *patternp = pattern;
1793     int star_count = 0;
1794     char *matchp = star_match[0];
1795     
1796     for (;;) {
1797         if (*patternp == NULLCHAR) {
1798             *index = leftover_start = bufp - buf;
1799             *matchp = NULLCHAR;
1800             return TRUE;
1801         }
1802         if (*bufp == NULLCHAR) return FALSE;
1803         if (*patternp == '*') {
1804             if (*bufp == *(patternp + 1)) {
1805                 *matchp = NULLCHAR;
1806                 matchp = star_match[++star_count];
1807                 patternp += 2;
1808                 bufp++;
1809                 continue;
1810             } else if (*bufp == '\n' || *bufp == '\r') {
1811                 patternp++;
1812                 if (*patternp == NULLCHAR)
1813                   continue;
1814                 else
1815                   return FALSE;
1816             } else {
1817                 *matchp++ = *bufp++;
1818                 continue;
1819             }
1820         }
1821         if (*patternp != *bufp) return FALSE;
1822         patternp++;
1823         bufp++;
1824     }
1825 }
1826
1827 void
1828 SendToPlayer(data, length)
1829      char *data;
1830      int length;
1831 {
1832     int error, outCount;
1833     outCount = OutputToProcess(NoProc, data, length, &error);
1834     if (outCount < length) {
1835         DisplayFatalError(_("Error writing to display"), error, 1);
1836     }
1837 }
1838
1839 void
1840 PackHolding(packed, holding)
1841      char packed[];
1842      char *holding;
1843 {
1844     char *p = holding;
1845     char *q = packed;
1846     int runlength = 0;
1847     int curr = 9999;
1848     do {
1849         if (*p == curr) {
1850             runlength++;
1851         } else {
1852             switch (runlength) {
1853               case 0:
1854                 break;
1855               case 1:
1856                 *q++ = curr;
1857                 break;
1858               case 2:
1859                 *q++ = curr;
1860                 *q++ = curr;
1861                 break;
1862               default:
1863                 sprintf(q, "%d", runlength);
1864                 while (*q) q++;
1865                 *q++ = curr;
1866                 break;
1867             }
1868             runlength = 1;
1869             curr = *p;
1870         }
1871     } while (*p++);
1872     *q = NULLCHAR;
1873 }
1874
1875 /* Telnet protocol requests from the front end */
1876 void
1877 TelnetRequest(ddww, option)
1878      unsigned char ddww, option;
1879 {
1880     unsigned char msg[3];
1881     int outCount, outError;
1882
1883     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1884
1885     if (appData.debugMode) {
1886         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1887         switch (ddww) {
1888           case TN_DO:
1889             ddwwStr = "DO";
1890             break;
1891           case TN_DONT:
1892             ddwwStr = "DONT";
1893             break;
1894           case TN_WILL:
1895             ddwwStr = "WILL";
1896             break;
1897           case TN_WONT:
1898             ddwwStr = "WONT";
1899             break;
1900           default:
1901             ddwwStr = buf1;
1902             sprintf(buf1, "%d", ddww);
1903             break;
1904         }
1905         switch (option) {
1906           case TN_ECHO:
1907             optionStr = "ECHO";
1908             break;
1909           default:
1910             optionStr = buf2;
1911             sprintf(buf2, "%d", option);
1912             break;
1913         }
1914         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1915     }
1916     msg[0] = TN_IAC;
1917     msg[1] = ddww;
1918     msg[2] = option;
1919     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1920     if (outCount < 3) {
1921         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1922     }
1923 }
1924
1925 void
1926 DoEcho()
1927 {
1928     if (!appData.icsActive) return;
1929     TelnetRequest(TN_DO, TN_ECHO);
1930 }
1931
1932 void
1933 DontEcho()
1934 {
1935     if (!appData.icsActive) return;
1936     TelnetRequest(TN_DONT, TN_ECHO);
1937 }
1938
1939 void
1940 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1941 {
1942     /* put the holdings sent to us by the server on the board holdings area */
1943     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1944     char p;
1945     ChessSquare piece;
1946
1947     if(gameInfo.holdingsWidth < 2)  return;
1948     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1949         return; // prevent overwriting by pre-board holdings
1950
1951     if( (int)lowestPiece >= BlackPawn ) {
1952         holdingsColumn = 0;
1953         countsColumn = 1;
1954         holdingsStartRow = BOARD_HEIGHT-1;
1955         direction = -1;
1956     } else {
1957         holdingsColumn = BOARD_WIDTH-1;
1958         countsColumn = BOARD_WIDTH-2;
1959         holdingsStartRow = 0;
1960         direction = 1;
1961     }
1962
1963     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1964         board[i][holdingsColumn] = EmptySquare;
1965         board[i][countsColumn]   = (ChessSquare) 0;
1966     }
1967     while( (p=*holdings++) != NULLCHAR ) {
1968         piece = CharToPiece( ToUpper(p) );
1969         if(piece == EmptySquare) continue;
1970         /*j = (int) piece - (int) WhitePawn;*/
1971         j = PieceToNumber(piece);
1972         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1973         if(j < 0) continue;               /* should not happen */
1974         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1975         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1976         board[holdingsStartRow+j*direction][countsColumn]++;
1977     }
1978 }
1979
1980
1981 void
1982 VariantSwitch(Board board, VariantClass newVariant)
1983 {
1984    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1985    Board oldBoard;
1986
1987    startedFromPositionFile = FALSE;
1988    if(gameInfo.variant == newVariant) return;
1989
1990    /* [HGM] This routine is called each time an assignment is made to
1991     * gameInfo.variant during a game, to make sure the board sizes
1992     * are set to match the new variant. If that means adding or deleting
1993     * holdings, we shift the playing board accordingly
1994     * This kludge is needed because in ICS observe mode, we get boards
1995     * of an ongoing game without knowing the variant, and learn about the
1996     * latter only later. This can be because of the move list we requested,
1997     * in which case the game history is refilled from the beginning anyway,
1998     * but also when receiving holdings of a crazyhouse game. In the latter
1999     * case we want to add those holdings to the already received position.
2000     */
2001
2002    
2003    if (appData.debugMode) {
2004      fprintf(debugFP, "Switch board from %s to %s\n",
2005              VariantName(gameInfo.variant), VariantName(newVariant));
2006      setbuf(debugFP, NULL);
2007    }
2008    shuffleOpenings = 0;       /* [HGM] shuffle */
2009    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2010    switch(newVariant) 
2011      {
2012      case VariantShogi:
2013        newWidth = 9;  newHeight = 9;
2014        gameInfo.holdingsSize = 7;
2015      case VariantBughouse:
2016      case VariantCrazyhouse:
2017        newHoldingsWidth = 2; break;
2018      case VariantGreat:
2019        newWidth = 10;
2020      case VariantSuper:
2021        newHoldingsWidth = 2;
2022        gameInfo.holdingsSize = 8;
2023        break;
2024      case VariantGothic:
2025      case VariantCapablanca:
2026      case VariantCapaRandom:
2027        newWidth = 10;
2028      default:
2029        newHoldingsWidth = gameInfo.holdingsSize = 0;
2030      };
2031    
2032    if(newWidth  != gameInfo.boardWidth  ||
2033       newHeight != gameInfo.boardHeight ||
2034       newHoldingsWidth != gameInfo.holdingsWidth ) {
2035      
2036      /* shift position to new playing area, if needed */
2037      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++) 
2039          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042        for(i=0; i<newHeight; i++) {
2043          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2044          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2045        }
2046      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2047        for(i=0; i<BOARD_HEIGHT; i++)
2048          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2049            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2050              board[i][j];
2051      }
2052      gameInfo.boardWidth  = newWidth;
2053      gameInfo.boardHeight = newHeight;
2054      gameInfo.holdingsWidth = newHoldingsWidth;
2055      gameInfo.variant = newVariant;
2056      InitDrawingSizes(-2, 0);
2057    } else gameInfo.variant = newVariant;
2058    CopyBoard(oldBoard, board);   // remember correctly formatted board
2059      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2060    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2061 }
2062
2063 static int loggedOn = FALSE;
2064
2065 /*-- Game start info cache: --*/
2066 int gs_gamenum;
2067 char gs_kind[MSG_SIZ];
2068 static char player1Name[128] = "";
2069 static char player2Name[128] = "";
2070 static char cont_seq[] = "\n\\   ";
2071 static int player1Rating = -1;
2072 static int player2Rating = -1;
2073 /*----------------------------*/
2074
2075 ColorClass curColor = ColorNormal;
2076 int suppressKibitz = 0;
2077
2078 // [HGM] seekgraph
2079 Boolean soughtPending = FALSE;
2080 Boolean seekGraphUp;
2081 #define MAX_SEEK_ADS 200
2082 #define SQUARE 0x80
2083 char *seekAdList[MAX_SEEK_ADS];
2084 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2085 float tcList[MAX_SEEK_ADS];
2086 char colorList[MAX_SEEK_ADS];
2087 int nrOfSeekAds = 0;
2088 int minRating = 1010, maxRating = 2800;
2089 int hMargin = 10, vMargin = 20, h, w;
2090 extern int squareSize, lineGap;
2091
2092 void
2093 PlotSeekAd(int i)
2094 {
2095         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2096         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2097         if(r < minRating+100 && r >=0 ) r = minRating+100;
2098         if(r > maxRating) r = maxRating;
2099         if(tc < 1.) tc = 1.;
2100         if(tc > 95.) tc = 95.;
2101         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2102         y = ((double)r - minRating)/(maxRating - minRating)
2103             * (h-vMargin-squareSize/8-1) + vMargin;
2104         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2105         if(strstr(seekAdList[i], " u ")) color = 1;
2106         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2107            !strstr(seekAdList[i], "bullet") &&
2108            !strstr(seekAdList[i], "blitz") &&
2109            !strstr(seekAdList[i], "standard") ) color = 2;
2110         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2111         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2112 }
2113
2114 void
2115 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2116 {
2117         char buf[MSG_SIZ], *ext = "";
2118         VariantClass v = StringToVariant(type);
2119         if(strstr(type, "wild")) {
2120             ext = type + 4; // append wild number
2121             if(v == VariantFischeRandom) type = "chess960"; else
2122             if(v == VariantLoadable) type = "setup"; else
2123             type = VariantName(v);
2124         }
2125         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2126         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2127             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2128             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2129             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2130             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2131             seekNrList[nrOfSeekAds] = nr;
2132             zList[nrOfSeekAds] = 0;
2133             seekAdList[nrOfSeekAds++] = StrSave(buf);
2134             if(plot) PlotSeekAd(nrOfSeekAds-1);
2135         }
2136 }
2137
2138 void
2139 EraseSeekDot(int i)
2140 {
2141     int x = xList[i], y = yList[i], d=squareSize/4, k;
2142     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2143     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2144     // now replot every dot that overlapped
2145     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2146         int xx = xList[k], yy = yList[k];
2147         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2148             DrawSeekDot(xx, yy, colorList[k]);
2149     }
2150 }
2151
2152 void
2153 RemoveSeekAd(int nr)
2154 {
2155         int i;
2156         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2157             EraseSeekDot(i);
2158             if(seekAdList[i]) free(seekAdList[i]);
2159             seekAdList[i] = seekAdList[--nrOfSeekAds];
2160             seekNrList[i] = seekNrList[nrOfSeekAds];
2161             ratingList[i] = ratingList[nrOfSeekAds];
2162             colorList[i]  = colorList[nrOfSeekAds];
2163             tcList[i] = tcList[nrOfSeekAds];
2164             xList[i]  = xList[nrOfSeekAds];
2165             yList[i]  = yList[nrOfSeekAds];
2166             zList[i]  = zList[nrOfSeekAds];
2167             seekAdList[nrOfSeekAds] = NULL;
2168             break;
2169         }
2170 }
2171
2172 Boolean
2173 MatchSoughtLine(char *line)
2174 {
2175     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2176     int nr, base, inc, u=0; char dummy;
2177
2178     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2179        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2180        (u=1) &&
2181        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2182         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2183         // match: compact and save the line
2184         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2185         return TRUE;
2186     }
2187     return FALSE;
2188 }
2189
2190 int
2191 DrawSeekGraph()
2192 {
2193     if(!seekGraphUp) return FALSE;
2194     int i;
2195     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2196     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2197
2198     DrawSeekBackground(0, 0, w, h);
2199     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2200     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2201     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2202         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2203         yy = h-1-yy;
2204         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2205         if(i%500 == 0) {
2206             char buf[MSG_SIZ];
2207             sprintf(buf, "%d", i);
2208             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2209         }
2210     }
2211     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2212     for(i=1; i<100; i+=(i<10?1:5)) {
2213         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2214         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2215         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2216             char buf[MSG_SIZ];
2217             sprintf(buf, "%d", i);
2218             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2219         }
2220     }
2221     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2222     return TRUE;
2223 }
2224
2225 int SeekGraphClick(ClickType click, int x, int y, int moving)
2226 {
2227     static int lastDown = 0, displayed = 0, lastSecond;
2228     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2229         if(click == Release || moving) return FALSE;
2230         nrOfSeekAds = 0;
2231         soughtPending = TRUE;
2232         SendToICS(ics_prefix);
2233         SendToICS("sought\n"); // should this be "sought all"?
2234     } else { // issue challenge based on clicked ad
2235         int dist = 10000; int i, closest = 0, second = 0;
2236         for(i=0; i<nrOfSeekAds; i++) {
2237             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2238             if(d < dist) { dist = d; closest = i; }
2239             second += (d - zList[i] < 120); // count in-range ads
2240             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2241         }
2242         if(dist < 120) {
2243             char buf[MSG_SIZ];
2244             second = (second > 1);
2245             if(displayed != closest || second != lastSecond) {
2246                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2247                 lastSecond = second; displayed = closest;
2248             }
2249             if(click == Press) {
2250                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2251                 lastDown = closest;
2252                 return TRUE;
2253             } // on press 'hit', only show info
2254             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2255             sprintf(buf, "play %d\n", seekNrList[closest]);
2256             SendToICS(ics_prefix);
2257             SendToICS(buf);
2258             return TRUE; // let incoming board of started game pop down the graph
2259         } else if(click == Release) { // release 'miss' is ignored
2260             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2261             if(moving == 2) { // right up-click
2262                 nrOfSeekAds = 0; // refresh graph
2263                 soughtPending = TRUE;
2264                 SendToICS(ics_prefix);
2265                 SendToICS("sought\n"); // should this be "sought all"?
2266             }
2267             return TRUE;
2268         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2269         // press miss or release hit 'pop down' seek graph
2270         seekGraphUp = FALSE;
2271         DrawPosition(TRUE, NULL);
2272     }
2273     return TRUE;
2274 }
2275
2276 void
2277 read_from_ics(isr, closure, data, count, error)
2278      InputSourceRef isr;
2279      VOIDSTAR closure;
2280      char *data;
2281      int count;
2282      int error;
2283 {
2284 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2285 #define STARTED_NONE 0
2286 #define STARTED_MOVES 1
2287 #define STARTED_BOARD 2
2288 #define STARTED_OBSERVE 3
2289 #define STARTED_HOLDINGS 4
2290 #define STARTED_CHATTER 5
2291 #define STARTED_COMMENT 6
2292 #define STARTED_MOVES_NOHIDE 7
2293     
2294     static int started = STARTED_NONE;
2295     static char parse[20000];
2296     static int parse_pos = 0;
2297     static char buf[BUF_SIZE + 1];
2298     static int firstTime = TRUE, intfSet = FALSE;
2299     static ColorClass prevColor = ColorNormal;
2300     static int savingComment = FALSE;
2301     static int cmatch = 0; // continuation sequence match
2302     char *bp;
2303     char str[500];
2304     int i, oldi;
2305     int buf_len;
2306     int next_out;
2307     int tkind;
2308     int backup;    /* [DM] For zippy color lines */
2309     char *p;
2310     char talker[MSG_SIZ]; // [HGM] chat
2311     int channel;
2312
2313     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2314
2315     if (appData.debugMode) {
2316       if (!error) {
2317         fprintf(debugFP, "<ICS: ");
2318         show_bytes(debugFP, data, count);
2319         fprintf(debugFP, "\n");
2320       }
2321     }
2322
2323     if (appData.debugMode) { int f = forwardMostMove;
2324         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2325                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2326                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2327     }
2328     if (count > 0) {
2329         /* If last read ended with a partial line that we couldn't parse,
2330            prepend it to the new read and try again. */
2331         if (leftover_len > 0) {
2332             for (i=0; i<leftover_len; i++)
2333               buf[i] = buf[leftover_start + i];
2334         }
2335
2336     /* copy new characters into the buffer */
2337     bp = buf + leftover_len;
2338     buf_len=leftover_len;
2339     for (i=0; i<count; i++)
2340     {
2341         // ignore these
2342         if (data[i] == '\r')
2343             continue;
2344
2345         // join lines split by ICS?
2346         if (!appData.noJoin)
2347         {
2348             /*
2349                 Joining just consists of finding matches against the
2350                 continuation sequence, and discarding that sequence
2351                 if found instead of copying it.  So, until a match
2352                 fails, there's nothing to do since it might be the
2353                 complete sequence, and thus, something we don't want
2354                 copied.
2355             */
2356             if (data[i] == cont_seq[cmatch])
2357             {
2358                 cmatch++;
2359                 if (cmatch == strlen(cont_seq))
2360                 {
2361                     cmatch = 0; // complete match.  just reset the counter
2362
2363                     /*
2364                         it's possible for the ICS to not include the space
2365                         at the end of the last word, making our [correct]
2366                         join operation fuse two separate words.  the server
2367                         does this when the space occurs at the width setting.
2368                     */
2369                     if (!buf_len || buf[buf_len-1] != ' ')
2370                     {
2371                         *bp++ = ' ';
2372                         buf_len++;
2373                     }
2374                 }
2375                 continue;
2376             }
2377             else if (cmatch)
2378             {
2379                 /*
2380                     match failed, so we have to copy what matched before
2381                     falling through and copying this character.  In reality,
2382                     this will only ever be just the newline character, but
2383                     it doesn't hurt to be precise.
2384                 */
2385                 strncpy(bp, cont_seq, cmatch);
2386                 bp += cmatch;
2387                 buf_len += cmatch;
2388                 cmatch = 0;
2389             }
2390         }
2391
2392         // copy this char
2393         *bp++ = data[i];
2394         buf_len++;
2395     }
2396
2397         buf[buf_len] = NULLCHAR;
2398 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2399         next_out = 0;
2400         leftover_start = 0;
2401         
2402         i = 0;
2403         while (i < buf_len) {
2404             /* Deal with part of the TELNET option negotiation
2405                protocol.  We refuse to do anything beyond the
2406                defaults, except that we allow the WILL ECHO option,
2407                which ICS uses to turn off password echoing when we are
2408                directly connected to it.  We reject this option
2409                if localLineEditing mode is on (always on in xboard)
2410                and we are talking to port 23, which might be a real
2411                telnet server that will try to keep WILL ECHO on permanently.
2412              */
2413             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2414                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2415                 unsigned char option;
2416                 oldi = i;
2417                 switch ((unsigned char) buf[++i]) {
2418                   case TN_WILL:
2419                     if (appData.debugMode)
2420                       fprintf(debugFP, "\n<WILL ");
2421                     switch (option = (unsigned char) buf[++i]) {
2422                       case TN_ECHO:
2423                         if (appData.debugMode)
2424                           fprintf(debugFP, "ECHO ");
2425                         /* Reply only if this is a change, according
2426                            to the protocol rules. */
2427                         if (remoteEchoOption) break;
2428                         if (appData.localLineEditing &&
2429                             atoi(appData.icsPort) == TN_PORT) {
2430                             TelnetRequest(TN_DONT, TN_ECHO);
2431                         } else {
2432                             EchoOff();
2433                             TelnetRequest(TN_DO, TN_ECHO);
2434                             remoteEchoOption = TRUE;
2435                         }
2436                         break;
2437                       default:
2438                         if (appData.debugMode)
2439                           fprintf(debugFP, "%d ", option);
2440                         /* Whatever this is, we don't want it. */
2441                         TelnetRequest(TN_DONT, option);
2442                         break;
2443                     }
2444                     break;
2445                   case TN_WONT:
2446                     if (appData.debugMode)
2447                       fprintf(debugFP, "\n<WONT ");
2448                     switch (option = (unsigned char) buf[++i]) {
2449                       case TN_ECHO:
2450                         if (appData.debugMode)
2451                           fprintf(debugFP, "ECHO ");
2452                         /* Reply only if this is a change, according
2453                            to the protocol rules. */
2454                         if (!remoteEchoOption) break;
2455                         EchoOn();
2456                         TelnetRequest(TN_DONT, TN_ECHO);
2457                         remoteEchoOption = FALSE;
2458                         break;
2459                       default:
2460                         if (appData.debugMode)
2461                           fprintf(debugFP, "%d ", (unsigned char) option);
2462                         /* Whatever this is, it must already be turned
2463                            off, because we never agree to turn on
2464                            anything non-default, so according to the
2465                            protocol rules, we don't reply. */
2466                         break;
2467                     }
2468                     break;
2469                   case TN_DO:
2470                     if (appData.debugMode)
2471                       fprintf(debugFP, "\n<DO ");
2472                     switch (option = (unsigned char) buf[++i]) {
2473                       default:
2474                         /* Whatever this is, we refuse to do it. */
2475                         if (appData.debugMode)
2476                           fprintf(debugFP, "%d ", option);
2477                         TelnetRequest(TN_WONT, option);
2478                         break;
2479                     }
2480                     break;
2481                   case TN_DONT:
2482                     if (appData.debugMode)
2483                       fprintf(debugFP, "\n<DONT ");
2484                     switch (option = (unsigned char) buf[++i]) {
2485                       default:
2486                         if (appData.debugMode)
2487                           fprintf(debugFP, "%d ", option);
2488                         /* Whatever this is, we are already not doing
2489                            it, because we never agree to do anything
2490                            non-default, so according to the protocol
2491                            rules, we don't reply. */
2492                         break;
2493                     }
2494                     break;
2495                   case TN_IAC:
2496                     if (appData.debugMode)
2497                       fprintf(debugFP, "\n<IAC ");
2498                     /* Doubled IAC; pass it through */
2499                     i--;
2500                     break;
2501                   default:
2502                     if (appData.debugMode)
2503                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2504                     /* Drop all other telnet commands on the floor */
2505                     break;
2506                 }
2507                 if (oldi > next_out)
2508                   SendToPlayer(&buf[next_out], oldi - next_out);
2509                 if (++i > next_out)
2510                   next_out = i;
2511                 continue;
2512             }
2513                 
2514             /* OK, this at least will *usually* work */
2515             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2516                 loggedOn = TRUE;
2517             }
2518             
2519             if (loggedOn && !intfSet) {
2520                 if (ics_type == ICS_ICC) {
2521                   sprintf(str,
2522                           "/set-quietly interface %s\n/set-quietly style 12\n",
2523                           programVersion);
2524                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2525                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2526                 } else if (ics_type == ICS_CHESSNET) {
2527                   sprintf(str, "/style 12\n");
2528                 } else {
2529                   strcpy(str, "alias $ @\n$set interface ");
2530                   strcat(str, programVersion);
2531                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2532                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2533                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2534 #ifdef WIN32
2535                   strcat(str, "$iset nohighlight 1\n");
2536 #endif
2537                   strcat(str, "$iset lock 1\n$style 12\n");
2538                 }
2539                 SendToICS(str);
2540                 NotifyFrontendLogin();
2541                 intfSet = TRUE;
2542             }
2543
2544             if (started == STARTED_COMMENT) {
2545                 /* Accumulate characters in comment */
2546                 parse[parse_pos++] = buf[i];
2547                 if (buf[i] == '\n') {
2548                     parse[parse_pos] = NULLCHAR;
2549                     if(chattingPartner>=0) {
2550                         char mess[MSG_SIZ];
2551                         sprintf(mess, "%s%s", talker, parse);
2552                         OutputChatMessage(chattingPartner, mess);
2553                         chattingPartner = -1;
2554                         next_out = i+1; // [HGM] suppress printing in ICS window
2555                     } else
2556                     if(!suppressKibitz) // [HGM] kibitz
2557                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2558                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2559                         int nrDigit = 0, nrAlph = 0, j;
2560                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2561                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2562                         parse[parse_pos] = NULLCHAR;
2563                         // try to be smart: if it does not look like search info, it should go to
2564                         // ICS interaction window after all, not to engine-output window.
2565                         for(j=0; j<parse_pos; j++) { // count letters and digits
2566                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2567                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2568                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2569                         }
2570                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2571                             int depth=0; float score;
2572                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2573                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2574                                 pvInfoList[forwardMostMove-1].depth = depth;
2575                                 pvInfoList[forwardMostMove-1].score = 100*score;
2576                             }
2577                             OutputKibitz(suppressKibitz, parse);
2578                         } else {
2579                             char tmp[MSG_SIZ];
2580                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2581                             SendToPlayer(tmp, strlen(tmp));
2582                         }
2583                         next_out = i+1; // [HGM] suppress printing in ICS window
2584                     }
2585                     started = STARTED_NONE;
2586                 } else {
2587                     /* Don't match patterns against characters in comment */
2588                     i++;
2589                     continue;
2590                 }
2591             }
2592             if (started == STARTED_CHATTER) {
2593                 if (buf[i] != '\n') {
2594                     /* Don't match patterns against characters in chatter */
2595                     i++;
2596                     continue;
2597                 }
2598                 started = STARTED_NONE;
2599                 if(suppressKibitz) next_out = i+1;
2600             }
2601
2602             /* Kludge to deal with rcmd protocol */
2603             if (firstTime && looking_at(buf, &i, "\001*")) {
2604                 DisplayFatalError(&buf[1], 0, 1);
2605                 continue;
2606             } else {
2607                 firstTime = FALSE;
2608             }
2609
2610             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2611                 ics_type = ICS_ICC;
2612                 ics_prefix = "/";
2613                 if (appData.debugMode)
2614                   fprintf(debugFP, "ics_type %d\n", ics_type);
2615                 continue;
2616             }
2617             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2618                 ics_type = ICS_FICS;
2619                 ics_prefix = "$";
2620                 if (appData.debugMode)
2621                   fprintf(debugFP, "ics_type %d\n", ics_type);
2622                 continue;
2623             }
2624             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2625                 ics_type = ICS_CHESSNET;
2626                 ics_prefix = "/";
2627                 if (appData.debugMode)
2628                   fprintf(debugFP, "ics_type %d\n", ics_type);
2629                 continue;
2630             }
2631
2632             if (!loggedOn &&
2633                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2634                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2635                  looking_at(buf, &i, "will be \"*\""))) {
2636               strcpy(ics_handle, star_match[0]);
2637               continue;
2638             }
2639
2640             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2641               char buf[MSG_SIZ];
2642               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2643               DisplayIcsInteractionTitle(buf);
2644               have_set_title = TRUE;
2645             }
2646
2647             /* skip finger notes */
2648             if (started == STARTED_NONE &&
2649                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2650                  (buf[i] == '1' && buf[i+1] == '0')) &&
2651                 buf[i+2] == ':' && buf[i+3] == ' ') {
2652               started = STARTED_CHATTER;
2653               i += 3;
2654               continue;
2655             }
2656
2657             oldi = i;
2658             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2659             if(appData.seekGraph) {
2660                 if(soughtPending && MatchSoughtLine(buf+i)) {
2661                     i = strstr(buf+i, "rated") - buf;
2662                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2663                     next_out = leftover_start = i;
2664                     started = STARTED_CHATTER;
2665                     suppressKibitz = TRUE;
2666                     continue;
2667                 }
2668                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2669                         && looking_at(buf, &i, "* ads displayed")) {
2670                     soughtPending = FALSE;
2671                     seekGraphUp = TRUE;
2672                     DrawSeekGraph();
2673                     continue;
2674                 }
2675                 if(appData.autoRefresh) {
2676                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2677                         int s = (ics_type == ICS_ICC); // ICC format differs
2678                         if(seekGraphUp)
2679                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2680                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2681                         looking_at(buf, &i, "*% "); // eat prompt
2682                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2683                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2684                         next_out = i; // suppress
2685                         continue;
2686                     }
2687                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2688                         char *p = star_match[0];
2689                         while(*p) {
2690                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2691                             while(*p && *p++ != ' '); // next
2692                         }
2693                         looking_at(buf, &i, "*% "); // eat prompt
2694                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2695                         next_out = i;
2696                         continue;
2697                     }
2698                 }
2699             }
2700
2701             /* skip formula vars */
2702             if (started == STARTED_NONE &&
2703                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2704               started = STARTED_CHATTER;
2705               i += 3;
2706               continue;
2707             }
2708
2709             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2710             if (appData.autoKibitz && started == STARTED_NONE && 
2711                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2712                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2713                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2714                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2715                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2716                         suppressKibitz = TRUE;
2717                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2718                         next_out = i;
2719                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2720                                 && (gameMode == IcsPlayingWhite)) ||
2721                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2722                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2723                             started = STARTED_CHATTER; // own kibitz we simply discard
2724                         else {
2725                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2726                             parse_pos = 0; parse[0] = NULLCHAR;
2727                             savingComment = TRUE;
2728                             suppressKibitz = gameMode != IcsObserving ? 2 :
2729                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2730                         } 
2731                         continue;
2732                 } else
2733                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2734                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2735                          && atoi(star_match[0])) {
2736                     // suppress the acknowledgements of our own autoKibitz
2737                     char *p;
2738                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2739                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2740                     SendToPlayer(star_match[0], strlen(star_match[0]));
2741                     if(looking_at(buf, &i, "*% ")) // eat prompt
2742                         suppressKibitz = FALSE;
2743                     next_out = i;
2744                     continue;
2745                 }
2746             } // [HGM] kibitz: end of patch
2747
2748             // [HGM] chat: intercept tells by users for which we have an open chat window
2749             channel = -1;
2750             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2751                                            looking_at(buf, &i, "* whispers:") ||
2752                                            looking_at(buf, &i, "* kibitzes:") ||
2753                                            looking_at(buf, &i, "* shouts:") ||
2754                                            looking_at(buf, &i, "* c-shouts:") ||
2755                                            looking_at(buf, &i, "--> * ") ||
2756                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2757                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2758                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2759                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2760                 int p;
2761                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2762                 chattingPartner = -1;
2763
2764                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2765                 for(p=0; p<MAX_CHAT; p++) {
2766                     if(channel == atoi(chatPartner[p])) {
2767                     talker[0] = '['; strcat(talker, "] ");
2768                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2769                     chattingPartner = p; break;
2770                     }
2771                 } else
2772                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2773                 for(p=0; p<MAX_CHAT; p++) {
2774                     if(!strcmp("kibitzes", chatPartner[p])) {
2775                         talker[0] = '['; strcat(talker, "] ");
2776                         chattingPartner = p; break;
2777                     }
2778                 } else
2779                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2780                 for(p=0; p<MAX_CHAT; p++) {
2781                     if(!strcmp("whispers", chatPartner[p])) {
2782                         talker[0] = '['; strcat(talker, "] ");
2783                         chattingPartner = p; break;
2784                     }
2785                 } else
2786                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2787                   if(buf[i-8] == '-' && buf[i-3] == 't')
2788                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2789                     if(!strcmp("c-shouts", chatPartner[p])) {
2790                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2791                         chattingPartner = p; break;
2792                     }
2793                   }
2794                   if(chattingPartner < 0)
2795                   for(p=0; p<MAX_CHAT; p++) {
2796                     if(!strcmp("shouts", chatPartner[p])) {
2797                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2798                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2799                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2800                         chattingPartner = p; break;
2801                     }
2802                   }
2803                 }
2804                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2805                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2806                     talker[0] = 0; Colorize(ColorTell, FALSE);
2807                     chattingPartner = p; break;
2808                 }
2809                 if(chattingPartner<0) i = oldi; else {
2810                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2811                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2812                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2813                     started = STARTED_COMMENT;
2814                     parse_pos = 0; parse[0] = NULLCHAR;
2815                     savingComment = 3 + chattingPartner; // counts as TRUE
2816                     suppressKibitz = TRUE;
2817                     continue;
2818                 }
2819             } // [HGM] chat: end of patch
2820
2821             if (appData.zippyTalk || appData.zippyPlay) {
2822                 /* [DM] Backup address for color zippy lines */
2823                 backup = i;
2824 #if ZIPPY
2825        #ifdef WIN32
2826                if (loggedOn == TRUE)
2827                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2828                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2829        #else
2830                 if (ZippyControl(buf, &i) ||
2831                     ZippyConverse(buf, &i) ||
2832                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2833                       loggedOn = TRUE;
2834                       if (!appData.colorize) continue;
2835                 }
2836        #endif
2837 #endif
2838             } // [DM] 'else { ' deleted
2839                 if (
2840                     /* Regular tells and says */
2841                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2842                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2843                     looking_at(buf, &i, "* says: ") ||
2844                     /* Don't color "message" or "messages" output */
2845                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2846                     looking_at(buf, &i, "*. * at *:*: ") ||
2847                     looking_at(buf, &i, "--* (*:*): ") ||
2848                     /* Message notifications (same color as tells) */
2849                     looking_at(buf, &i, "* has left a message ") ||
2850                     looking_at(buf, &i, "* just sent you a message:\n") ||
2851                     /* Whispers and kibitzes */
2852                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2853                     looking_at(buf, &i, "* kibitzes: ") ||
2854                     /* Channel tells */
2855                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2856
2857                   if (tkind == 1 && strchr(star_match[0], ':')) {
2858                       /* Avoid "tells you:" spoofs in channels */
2859                      tkind = 3;
2860                   }
2861                   if (star_match[0][0] == NULLCHAR ||
2862                       strchr(star_match[0], ' ') ||
2863                       (tkind == 3 && strchr(star_match[1], ' '))) {
2864                     /* Reject bogus matches */
2865                     i = oldi;
2866                   } else {
2867                     if (appData.colorize) {
2868                       if (oldi > next_out) {
2869                         SendToPlayer(&buf[next_out], oldi - next_out);
2870                         next_out = oldi;
2871                       }
2872                       switch (tkind) {
2873                       case 1:
2874                         Colorize(ColorTell, FALSE);
2875                         curColor = ColorTell;
2876                         break;
2877                       case 2:
2878                         Colorize(ColorKibitz, FALSE);
2879                         curColor = ColorKibitz;
2880                         break;
2881                       case 3:
2882                         p = strrchr(star_match[1], '(');
2883                         if (p == NULL) {
2884                           p = star_match[1];
2885                         } else {
2886                           p++;
2887                         }
2888                         if (atoi(p) == 1) {
2889                           Colorize(ColorChannel1, FALSE);
2890                           curColor = ColorChannel1;
2891                         } else {
2892                           Colorize(ColorChannel, FALSE);
2893                           curColor = ColorChannel;
2894                         }
2895                         break;
2896                       case 5:
2897                         curColor = ColorNormal;
2898                         break;
2899                       }
2900                     }
2901                     if (started == STARTED_NONE && appData.autoComment &&
2902                         (gameMode == IcsObserving ||
2903                          gameMode == IcsPlayingWhite ||
2904                          gameMode == IcsPlayingBlack)) {
2905                       parse_pos = i - oldi;
2906                       memcpy(parse, &buf[oldi], parse_pos);
2907                       parse[parse_pos] = NULLCHAR;
2908                       started = STARTED_COMMENT;
2909                       savingComment = TRUE;
2910                     } else {
2911                       started = STARTED_CHATTER;
2912                       savingComment = FALSE;
2913                     }
2914                     loggedOn = TRUE;
2915                     continue;
2916                   }
2917                 }
2918
2919                 if (looking_at(buf, &i, "* s-shouts: ") ||
2920                     looking_at(buf, &i, "* c-shouts: ")) {
2921                     if (appData.colorize) {
2922                         if (oldi > next_out) {
2923                             SendToPlayer(&buf[next_out], oldi - next_out);
2924                             next_out = oldi;
2925                         }
2926                         Colorize(ColorSShout, FALSE);
2927                         curColor = ColorSShout;
2928                     }
2929                     loggedOn = TRUE;
2930                     started = STARTED_CHATTER;
2931                     continue;
2932                 }
2933
2934                 if (looking_at(buf, &i, "--->")) {
2935                     loggedOn = TRUE;
2936                     continue;
2937                 }
2938
2939                 if (looking_at(buf, &i, "* shouts: ") ||
2940                     looking_at(buf, &i, "--> ")) {
2941                     if (appData.colorize) {
2942                         if (oldi > next_out) {
2943                             SendToPlayer(&buf[next_out], oldi - next_out);
2944                             next_out = oldi;
2945                         }
2946                         Colorize(ColorShout, FALSE);
2947                         curColor = ColorShout;
2948                     }
2949                     loggedOn = TRUE;
2950                     started = STARTED_CHATTER;
2951                     continue;
2952                 }
2953
2954                 if (looking_at( buf, &i, "Challenge:")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorChallenge, FALSE);
2961                         curColor = ColorChallenge;
2962                     }
2963                     loggedOn = TRUE;
2964                     continue;
2965                 }
2966
2967                 if (looking_at(buf, &i, "* offers you") ||
2968                     looking_at(buf, &i, "* offers to be") ||
2969                     looking_at(buf, &i, "* would like to") ||
2970                     looking_at(buf, &i, "* requests to") ||
2971                     looking_at(buf, &i, "Your opponent offers") ||
2972                     looking_at(buf, &i, "Your opponent requests")) {
2973
2974                     if (appData.colorize) {
2975                         if (oldi > next_out) {
2976                             SendToPlayer(&buf[next_out], oldi - next_out);
2977                             next_out = oldi;
2978                         }
2979                         Colorize(ColorRequest, FALSE);
2980                         curColor = ColorRequest;
2981                     }
2982                     continue;
2983                 }
2984
2985                 if (looking_at(buf, &i, "* (*) seeking")) {
2986                     if (appData.colorize) {
2987                         if (oldi > next_out) {
2988                             SendToPlayer(&buf[next_out], oldi - next_out);
2989                             next_out = oldi;
2990                         }
2991                         Colorize(ColorSeek, FALSE);
2992                         curColor = ColorSeek;
2993                     }
2994                     continue;
2995             }
2996
2997             if (looking_at(buf, &i, "\\   ")) {
2998                 if (prevColor != ColorNormal) {
2999                     if (oldi > next_out) {
3000                         SendToPlayer(&buf[next_out], oldi - next_out);
3001                         next_out = oldi;
3002                     }
3003                     Colorize(prevColor, TRUE);
3004                     curColor = prevColor;
3005                 }
3006                 if (savingComment) {
3007                     parse_pos = i - oldi;
3008                     memcpy(parse, &buf[oldi], parse_pos);
3009                     parse[parse_pos] = NULLCHAR;
3010                     started = STARTED_COMMENT;
3011                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3012                         chattingPartner = savingComment - 3; // kludge to remember the box
3013                 } else {
3014                     started = STARTED_CHATTER;
3015                 }
3016                 continue;
3017             }
3018
3019             if (looking_at(buf, &i, "Black Strength :") ||
3020                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3021                 looking_at(buf, &i, "<10>") ||
3022                 looking_at(buf, &i, "#@#")) {
3023                 /* Wrong board style */
3024                 loggedOn = TRUE;
3025                 SendToICS(ics_prefix);
3026                 SendToICS("set style 12\n");
3027                 SendToICS(ics_prefix);
3028                 SendToICS("refresh\n");
3029                 continue;
3030             }
3031             
3032             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3033                 ICSInitScript();
3034                 have_sent_ICS_logon = 1;
3035                 continue;
3036             }
3037               
3038             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3039                 (looking_at(buf, &i, "\n<12> ") ||
3040                  looking_at(buf, &i, "<12> "))) {
3041                 loggedOn = TRUE;
3042                 if (oldi > next_out) {
3043                     SendToPlayer(&buf[next_out], oldi - next_out);
3044                 }
3045                 next_out = i;
3046                 started = STARTED_BOARD;
3047                 parse_pos = 0;
3048                 continue;
3049             }
3050
3051             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3052                 looking_at(buf, &i, "<b1> ")) {
3053                 if (oldi > next_out) {
3054                     SendToPlayer(&buf[next_out], oldi - next_out);
3055                 }
3056                 next_out = i;
3057                 started = STARTED_HOLDINGS;
3058                 parse_pos = 0;
3059                 continue;
3060             }
3061
3062             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3063                 loggedOn = TRUE;
3064                 /* Header for a move list -- first line */
3065
3066                 switch (ics_getting_history) {
3067                   case H_FALSE:
3068                     switch (gameMode) {
3069                       case IcsIdle:
3070                       case BeginningOfGame:
3071                         /* User typed "moves" or "oldmoves" while we
3072                            were idle.  Pretend we asked for these
3073                            moves and soak them up so user can step
3074                            through them and/or save them.
3075                            */
3076                         Reset(FALSE, TRUE);
3077                         gameMode = IcsObserving;
3078                         ModeHighlight();
3079                         ics_gamenum = -1;
3080                         ics_getting_history = H_GOT_UNREQ_HEADER;
3081                         break;
3082                       case EditGame: /*?*/
3083                       case EditPosition: /*?*/
3084                         /* Should above feature work in these modes too? */
3085                         /* For now it doesn't */
3086                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3087                         break;
3088                       default:
3089                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3090                         break;
3091                     }
3092                     break;
3093                   case H_REQUESTED:
3094                     /* Is this the right one? */
3095                     if (gameInfo.white && gameInfo.black &&
3096                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3097                         strcmp(gameInfo.black, star_match[2]) == 0) {
3098                         /* All is well */
3099                         ics_getting_history = H_GOT_REQ_HEADER;
3100                     }
3101                     break;
3102                   case H_GOT_REQ_HEADER:
3103                   case H_GOT_UNREQ_HEADER:
3104                   case H_GOT_UNWANTED_HEADER:
3105                   case H_GETTING_MOVES:
3106                     /* Should not happen */
3107                     DisplayError(_("Error gathering move list: two headers"), 0);
3108                     ics_getting_history = H_FALSE;
3109                     break;
3110                 }
3111
3112                 /* Save player ratings into gameInfo if needed */
3113                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3114                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3115                     (gameInfo.whiteRating == -1 ||
3116                      gameInfo.blackRating == -1)) {
3117
3118                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3119                     gameInfo.blackRating = string_to_rating(star_match[3]);
3120                     if (appData.debugMode)
3121                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3122                               gameInfo.whiteRating, gameInfo.blackRating);
3123                 }
3124                 continue;
3125             }
3126
3127             if (looking_at(buf, &i,
3128               "* * match, initial time: * minute*, increment: * second")) {
3129                 /* Header for a move list -- second line */
3130                 /* Initial board will follow if this is a wild game */
3131                 if (gameInfo.event != NULL) free(gameInfo.event);
3132                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3133                 gameInfo.event = StrSave(str);
3134                 /* [HGM] we switched variant. Translate boards if needed. */
3135                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3136                 continue;
3137             }
3138
3139             if (looking_at(buf, &i, "Move  ")) {
3140                 /* Beginning of a move list */
3141                 switch (ics_getting_history) {
3142                   case H_FALSE:
3143                     /* Normally should not happen */
3144                     /* Maybe user hit reset while we were parsing */
3145                     break;
3146                   case H_REQUESTED:
3147                     /* Happens if we are ignoring a move list that is not
3148                      * the one we just requested.  Common if the user
3149                      * tries to observe two games without turning off
3150                      * getMoveList */
3151                     break;
3152                   case H_GETTING_MOVES:
3153                     /* Should not happen */
3154                     DisplayError(_("Error gathering move list: nested"), 0);
3155                     ics_getting_history = H_FALSE;
3156                     break;
3157                   case H_GOT_REQ_HEADER:
3158                     ics_getting_history = H_GETTING_MOVES;
3159                     started = STARTED_MOVES;
3160                     parse_pos = 0;
3161                     if (oldi > next_out) {
3162                         SendToPlayer(&buf[next_out], oldi - next_out);
3163                     }
3164                     break;
3165                   case H_GOT_UNREQ_HEADER:
3166                     ics_getting_history = H_GETTING_MOVES;
3167                     started = STARTED_MOVES_NOHIDE;
3168                     parse_pos = 0;
3169                     break;
3170                   case H_GOT_UNWANTED_HEADER:
3171                     ics_getting_history = H_FALSE;
3172                     break;
3173                 }
3174                 continue;
3175             }                           
3176             
3177             if (looking_at(buf, &i, "% ") ||
3178                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3179                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3180                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3181                     soughtPending = FALSE;
3182                     seekGraphUp = TRUE;
3183                     DrawSeekGraph();
3184                 }
3185                 if(suppressKibitz) next_out = i;
3186                 savingComment = FALSE;
3187                 suppressKibitz = 0;
3188                 switch (started) {
3189                   case STARTED_MOVES:
3190                   case STARTED_MOVES_NOHIDE:
3191                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3192                     parse[parse_pos + i - oldi] = NULLCHAR;
3193                     ParseGameHistory(parse);
3194 #if ZIPPY
3195                     if (appData.zippyPlay && first.initDone) {
3196                         FeedMovesToProgram(&first, forwardMostMove);
3197                         if (gameMode == IcsPlayingWhite) {
3198                             if (WhiteOnMove(forwardMostMove)) {
3199                                 if (first.sendTime) {
3200                                   if (first.useColors) {
3201                                     SendToProgram("black\n", &first); 
3202                                   }
3203                                   SendTimeRemaining(&first, TRUE);
3204                                 }
3205                                 if (first.useColors) {
3206                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3207                                 }
3208                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3209                                 first.maybeThinking = TRUE;
3210                             } else {
3211                                 if (first.usePlayother) {
3212                                   if (first.sendTime) {
3213                                     SendTimeRemaining(&first, TRUE);
3214                                   }
3215                                   SendToProgram("playother\n", &first);
3216                                   firstMove = FALSE;
3217                                 } else {
3218                                   firstMove = TRUE;
3219                                 }
3220                             }
3221                         } else if (gameMode == IcsPlayingBlack) {
3222                             if (!WhiteOnMove(forwardMostMove)) {
3223                                 if (first.sendTime) {
3224                                   if (first.useColors) {
3225                                     SendToProgram("white\n", &first);
3226                                   }
3227                                   SendTimeRemaining(&first, FALSE);
3228                                 }
3229                                 if (first.useColors) {
3230                                   SendToProgram("black\n", &first);
3231                                 }
3232                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3233                                 first.maybeThinking = TRUE;
3234                             } else {
3235                                 if (first.usePlayother) {
3236                                   if (first.sendTime) {
3237                                     SendTimeRemaining(&first, FALSE);
3238                                   }
3239                                   SendToProgram("playother\n", &first);
3240                                   firstMove = FALSE;
3241                                 } else {
3242                                   firstMove = TRUE;
3243                                 }
3244                             }
3245                         }                       
3246                     }
3247 #endif
3248                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3249                         /* Moves came from oldmoves or moves command
3250                            while we weren't doing anything else.
3251                            */
3252                         currentMove = forwardMostMove;
3253                         ClearHighlights();/*!!could figure this out*/
3254                         flipView = appData.flipView;
3255                         DrawPosition(TRUE, boards[currentMove]);
3256                         DisplayBothClocks();
3257                         sprintf(str, "%s vs. %s",
3258                                 gameInfo.white, gameInfo.black);
3259                         DisplayTitle(str);
3260                         gameMode = IcsIdle;
3261                     } else {
3262                         /* Moves were history of an active game */
3263                         if (gameInfo.resultDetails != NULL) {
3264                             free(gameInfo.resultDetails);
3265                             gameInfo.resultDetails = NULL;
3266                         }
3267                     }
3268                     HistorySet(parseList, backwardMostMove,
3269                                forwardMostMove, currentMove-1);
3270                     DisplayMove(currentMove - 1);
3271                     if (started == STARTED_MOVES) next_out = i;
3272                     started = STARTED_NONE;
3273                     ics_getting_history = H_FALSE;
3274                     break;
3275
3276                   case STARTED_OBSERVE:
3277                     started = STARTED_NONE;
3278                     SendToICS(ics_prefix);
3279                     SendToICS("refresh\n");
3280                     break;
3281
3282                   default:
3283                     break;
3284                 }
3285                 if(bookHit) { // [HGM] book: simulate book reply
3286                     static char bookMove[MSG_SIZ]; // a bit generous?
3287
3288                     programStats.nodes = programStats.depth = programStats.time = 
3289                     programStats.score = programStats.got_only_move = 0;
3290                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3291
3292                     strcpy(bookMove, "move ");
3293                     strcat(bookMove, bookHit);
3294                     HandleMachineMove(bookMove, &first);
3295                 }
3296                 continue;
3297             }
3298             
3299             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3300                  started == STARTED_HOLDINGS ||
3301                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3302                 /* Accumulate characters in move list or board */
3303                 parse[parse_pos++] = buf[i];
3304             }
3305             
3306             /* Start of game messages.  Mostly we detect start of game
3307                when the first board image arrives.  On some versions
3308                of the ICS, though, we need to do a "refresh" after starting
3309                to observe in order to get the current board right away. */
3310             if (looking_at(buf, &i, "Adding game * to observation list")) {
3311                 started = STARTED_OBSERVE;
3312                 continue;
3313             }
3314
3315             /* Handle auto-observe */
3316             if (appData.autoObserve &&
3317                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3318                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3319                 char *player;
3320                 /* Choose the player that was highlighted, if any. */
3321                 if (star_match[0][0] == '\033' ||
3322                     star_match[1][0] != '\033') {
3323                     player = star_match[0];
3324                 } else {
3325                     player = star_match[2];
3326                 }
3327                 sprintf(str, "%sobserve %s\n",
3328                         ics_prefix, StripHighlightAndTitle(player));
3329                 SendToICS(str);
3330
3331                 /* Save ratings from notify string */
3332                 strcpy(player1Name, star_match[0]);
3333                 player1Rating = string_to_rating(star_match[1]);
3334                 strcpy(player2Name, star_match[2]);
3335                 player2Rating = string_to_rating(star_match[3]);
3336
3337                 if (appData.debugMode)
3338                   fprintf(debugFP, 
3339                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3340                           player1Name, player1Rating,
3341                           player2Name, player2Rating);
3342
3343                 continue;
3344             }
3345
3346             /* Deal with automatic examine mode after a game,
3347                and with IcsObserving -> IcsExamining transition */
3348             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3349                 looking_at(buf, &i, "has made you an examiner of game *")) {
3350
3351                 int gamenum = atoi(star_match[0]);
3352                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3353                     gamenum == ics_gamenum) {
3354                     /* We were already playing or observing this game;
3355                        no need to refetch history */
3356                     gameMode = IcsExamining;
3357                     if (pausing) {
3358                         pauseExamForwardMostMove = forwardMostMove;
3359                     } else if (currentMove < forwardMostMove) {
3360                         ForwardInner(forwardMostMove);
3361                     }
3362                 } else {
3363                     /* I don't think this case really can happen */
3364                     SendToICS(ics_prefix);
3365                     SendToICS("refresh\n");
3366                 }
3367                 continue;
3368             }    
3369             
3370             /* Error messages */
3371 //          if (ics_user_moved) {
3372             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3373                 if (looking_at(buf, &i, "Illegal move") ||
3374                     looking_at(buf, &i, "Not a legal move") ||
3375                     looking_at(buf, &i, "Your king is in check") ||
3376                     looking_at(buf, &i, "It isn't your turn") ||
3377                     looking_at(buf, &i, "It is not your move")) {
3378                     /* Illegal move */
3379                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3380                         currentMove = forwardMostMove-1;
3381                         DisplayMove(currentMove - 1); /* before DMError */
3382                         DrawPosition(FALSE, boards[currentMove]);
3383                         SwitchClocks(forwardMostMove-1); // [HGM] race
3384                         DisplayBothClocks();
3385                     }
3386                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3387                     ics_user_moved = 0;
3388                     continue;
3389                 }
3390             }
3391
3392             if (looking_at(buf, &i, "still have time") ||
3393                 looking_at(buf, &i, "not out of time") ||
3394                 looking_at(buf, &i, "either player is out of time") ||
3395                 looking_at(buf, &i, "has timeseal; checking")) {
3396                 /* We must have called his flag a little too soon */
3397                 whiteFlag = blackFlag = FALSE;
3398                 continue;
3399             }
3400
3401             if (looking_at(buf, &i, "added * seconds to") ||
3402                 looking_at(buf, &i, "seconds were added to")) {
3403                 /* Update the clocks */
3404                 SendToICS(ics_prefix);
3405                 SendToICS("refresh\n");
3406                 continue;
3407             }
3408
3409             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3410                 ics_clock_paused = TRUE;
3411                 StopClocks();
3412                 continue;
3413             }
3414
3415             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3416                 ics_clock_paused = FALSE;
3417                 StartClocks();
3418                 continue;
3419             }
3420
3421             /* Grab player ratings from the Creating: message.
3422                Note we have to check for the special case when
3423                the ICS inserts things like [white] or [black]. */
3424             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3425                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3426                 /* star_matches:
3427                    0    player 1 name (not necessarily white)
3428                    1    player 1 rating
3429                    2    empty, white, or black (IGNORED)
3430                    3    player 2 name (not necessarily black)
3431                    4    player 2 rating
3432                    
3433                    The names/ratings are sorted out when the game
3434                    actually starts (below).
3435                 */
3436                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3437                 player1Rating = string_to_rating(star_match[1]);
3438                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3439                 player2Rating = string_to_rating(star_match[4]);
3440
3441                 if (appData.debugMode)
3442                   fprintf(debugFP, 
3443                           "Ratings from 'Creating:' %s %d, %s %d\n",
3444                           player1Name, player1Rating,
3445                           player2Name, player2Rating);
3446
3447                 continue;
3448             }
3449             
3450             /* Improved generic start/end-of-game messages */
3451             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3452                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3453                 /* If tkind == 0: */
3454                 /* star_match[0] is the game number */
3455                 /*           [1] is the white player's name */
3456                 /*           [2] is the black player's name */
3457                 /* For end-of-game: */
3458                 /*           [3] is the reason for the game end */
3459                 /*           [4] is a PGN end game-token, preceded by " " */
3460                 /* For start-of-game: */
3461                 /*           [3] begins with "Creating" or "Continuing" */
3462                 /*           [4] is " *" or empty (don't care). */
3463                 int gamenum = atoi(star_match[0]);
3464                 char *whitename, *blackname, *why, *endtoken;
3465                 ChessMove endtype = (ChessMove) 0;
3466
3467                 if (tkind == 0) {
3468                   whitename = star_match[1];
3469                   blackname = star_match[2];
3470                   why = star_match[3];
3471                   endtoken = star_match[4];
3472                 } else {
3473                   whitename = star_match[1];
3474                   blackname = star_match[3];
3475                   why = star_match[5];
3476                   endtoken = star_match[6];
3477                 }
3478
3479                 /* Game start messages */
3480                 if (strncmp(why, "Creating ", 9) == 0 ||
3481                     strncmp(why, "Continuing ", 11) == 0) {
3482                     gs_gamenum = gamenum;
3483                     strcpy(gs_kind, strchr(why, ' ') + 1);
3484                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3485 #if ZIPPY
3486                     if (appData.zippyPlay) {
3487                         ZippyGameStart(whitename, blackname);
3488                     }
3489 #endif /*ZIPPY*/
3490                     partnerBoardValid = FALSE; // [HGM] bughouse
3491                     continue;
3492                 }
3493
3494                 /* Game end messages */
3495                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3496                     ics_gamenum != gamenum) {
3497                     continue;
3498                 }
3499                 while (endtoken[0] == ' ') endtoken++;
3500                 switch (endtoken[0]) {
3501                   case '*':
3502                   default:
3503                     endtype = GameUnfinished;
3504                     break;
3505                   case '0':
3506                     endtype = BlackWins;
3507                     break;
3508                   case '1':
3509                     if (endtoken[1] == '/')
3510                       endtype = GameIsDrawn;
3511                     else
3512                       endtype = WhiteWins;
3513                     break;
3514                 }
3515                 GameEnds(endtype, why, GE_ICS);
3516 #if ZIPPY
3517                 if (appData.zippyPlay && first.initDone) {
3518                     ZippyGameEnd(endtype, why);
3519                     if (first.pr == NULL) {
3520                       /* Start the next process early so that we'll
3521                          be ready for the next challenge */
3522                       StartChessProgram(&first);
3523                     }
3524                     /* Send "new" early, in case this command takes
3525                        a long time to finish, so that we'll be ready
3526                        for the next challenge. */
3527                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3528                     Reset(TRUE, TRUE);
3529                 }
3530 #endif /*ZIPPY*/
3531                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i, "Removing game * from observation") ||
3536                 looking_at(buf, &i, "no longer observing game *") ||
3537                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3538                 if (gameMode == IcsObserving &&
3539                     atoi(star_match[0]) == ics_gamenum)
3540                   {
3541                       /* icsEngineAnalyze */
3542                       if (appData.icsEngineAnalyze) {
3543                             ExitAnalyzeMode();
3544                             ModeHighlight();
3545                       }
3546                       StopClocks();
3547                       gameMode = IcsIdle;
3548                       ics_gamenum = -1;
3549                       ics_user_moved = FALSE;
3550                   }
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "no longer examining game *")) {
3555                 if (gameMode == IcsExamining &&
3556                     atoi(star_match[0]) == ics_gamenum)
3557                   {
3558                       gameMode = IcsIdle;
3559                       ics_gamenum = -1;
3560                       ics_user_moved = FALSE;
3561                   }
3562                 continue;
3563             }
3564
3565             /* Advance leftover_start past any newlines we find,
3566                so only partial lines can get reparsed */
3567             if (looking_at(buf, &i, "\n")) {
3568                 prevColor = curColor;
3569                 if (curColor != ColorNormal) {
3570                     if (oldi > next_out) {
3571                         SendToPlayer(&buf[next_out], oldi - next_out);
3572                         next_out = oldi;
3573                     }
3574                     Colorize(ColorNormal, FALSE);
3575                     curColor = ColorNormal;
3576                 }
3577                 if (started == STARTED_BOARD) {
3578                     started = STARTED_NONE;
3579                     parse[parse_pos] = NULLCHAR;
3580                     ParseBoard12(parse);
3581                     ics_user_moved = 0;
3582
3583                     /* Send premove here */
3584                     if (appData.premove) {
3585                       char str[MSG_SIZ];
3586                       if (currentMove == 0 &&
3587                           gameMode == IcsPlayingWhite &&
3588                           appData.premoveWhite) {
3589                         sprintf(str, "%s\n", appData.premoveWhiteText);
3590                         if (appData.debugMode)
3591                           fprintf(debugFP, "Sending premove:\n");
3592                         SendToICS(str);
3593                       } else if (currentMove == 1 &&
3594                                  gameMode == IcsPlayingBlack &&
3595                                  appData.premoveBlack) {
3596                         sprintf(str, "%s\n", appData.premoveBlackText);
3597                         if (appData.debugMode)
3598                           fprintf(debugFP, "Sending premove:\n");
3599                         SendToICS(str);
3600                       } else if (gotPremove) {
3601                         gotPremove = 0;
3602                         ClearPremoveHighlights();
3603                         if (appData.debugMode)
3604                           fprintf(debugFP, "Sending premove:\n");
3605                           UserMoveEvent(premoveFromX, premoveFromY, 
3606                                         premoveToX, premoveToY, 
3607                                         premovePromoChar);
3608                       }
3609                     }
3610
3611                     /* Usually suppress following prompt */
3612                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3613                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3614                         if (looking_at(buf, &i, "*% ")) {
3615                             savingComment = FALSE;
3616                             suppressKibitz = 0;
3617                         }
3618                     }
3619                     next_out = i;
3620                 } else if (started == STARTED_HOLDINGS) {
3621                     int gamenum;
3622                     char new_piece[MSG_SIZ];
3623                     started = STARTED_NONE;
3624                     parse[parse_pos] = NULLCHAR;
3625                     if (appData.debugMode)
3626                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3627                                                         parse, currentMove);
3628                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3629                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3630                         if (gameInfo.variant == VariantNormal) {
3631                           /* [HGM] We seem to switch variant during a game!
3632                            * Presumably no holdings were displayed, so we have
3633                            * to move the position two files to the right to
3634                            * create room for them!
3635                            */
3636                           VariantClass newVariant;
3637                           switch(gameInfo.boardWidth) { // base guess on board width
3638                                 case 9:  newVariant = VariantShogi; break;
3639                                 case 10: newVariant = VariantGreat; break;
3640                                 default: newVariant = VariantCrazyhouse; break;
3641                           }
3642                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3643                           /* Get a move list just to see the header, which
3644                              will tell us whether this is really bug or zh */
3645                           if (ics_getting_history == H_FALSE) {
3646                             ics_getting_history = H_REQUESTED;
3647                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648                             SendToICS(str);
3649                           }
3650                         }
3651                         new_piece[0] = NULLCHAR;
3652                         sscanf(parse, "game %d white [%s black [%s <- %s",
3653                                &gamenum, white_holding, black_holding,
3654                                new_piece);
3655                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3656                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3657                         /* [HGM] copy holdings to board holdings area */
3658                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3659                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3660                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3661 #if ZIPPY
3662                         if (appData.zippyPlay && first.initDone) {
3663                             ZippyHoldings(white_holding, black_holding,
3664                                           new_piece);
3665                         }
3666 #endif /*ZIPPY*/
3667                         if (tinyLayout || smallLayout) {
3668                             char wh[16], bh[16];
3669                             PackHolding(wh, white_holding);
3670                             PackHolding(bh, black_holding);
3671                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3672                                     gameInfo.white, gameInfo.black);
3673                         } else {
3674                             sprintf(str, "%s [%s] vs. %s [%s]",
3675                                     gameInfo.white, white_holding,
3676                                     gameInfo.black, black_holding);
3677                         }
3678                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3679                         DrawPosition(FALSE, boards[currentMove]);
3680                         DisplayTitle(str);
3681                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3682                         sscanf(parse, "game %d white [%s black [%s <- %s",
3683                                &gamenum, white_holding, black_holding,
3684                                new_piece);
3685                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3686                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3687                         /* [HGM] copy holdings to partner-board holdings area */
3688                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3689                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3690                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3691                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3692                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3693                       }
3694                     }
3695                     /* Suppress following prompt */
3696                     if (looking_at(buf, &i, "*% ")) {
3697                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3698                         savingComment = FALSE;
3699                         suppressKibitz = 0;
3700                     }
3701                     next_out = i;
3702                 }
3703                 continue;
3704             }
3705
3706             i++;                /* skip unparsed character and loop back */
3707         }
3708         
3709         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3710 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3711 //          SendToPlayer(&buf[next_out], i - next_out);
3712             started != STARTED_HOLDINGS && leftover_start > next_out) {
3713             SendToPlayer(&buf[next_out], leftover_start - next_out);
3714             next_out = i;
3715         }
3716         
3717         leftover_len = buf_len - leftover_start;
3718         /* if buffer ends with something we couldn't parse,
3719            reparse it after appending the next read */
3720         
3721     } else if (count == 0) {
3722         RemoveInputSource(isr);
3723         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3724     } else {
3725         DisplayFatalError(_("Error reading from ICS"), error, 1);
3726     }
3727 }
3728
3729
3730 /* Board style 12 looks like this:
3731    
3732    <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
3733    
3734  * The "<12> " is stripped before it gets to this routine.  The two
3735  * trailing 0's (flip state and clock ticking) are later addition, and
3736  * some chess servers may not have them, or may have only the first.
3737  * Additional trailing fields may be added in the future.  
3738  */
3739
3740 #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"
3741
3742 #define RELATION_OBSERVING_PLAYED    0
3743 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3744 #define RELATION_PLAYING_MYMOVE      1
3745 #define RELATION_PLAYING_NOTMYMOVE  -1
3746 #define RELATION_EXAMINING           2
3747 #define RELATION_ISOLATED_BOARD     -3
3748 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3749
3750 void
3751 ParseBoard12(string)
3752      char *string;
3753
3754     GameMode newGameMode;
3755     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3756     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3757     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3758     char to_play, board_chars[200];
3759     char move_str[500], str[500], elapsed_time[500];
3760     char black[32], white[32];
3761     Board board;
3762     int prevMove = currentMove;
3763     int ticking = 2;
3764     ChessMove moveType;
3765     int fromX, fromY, toX, toY;
3766     char promoChar;
3767     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3768     char *bookHit = NULL; // [HGM] book
3769     Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3770
3771     fromX = fromY = toX = toY = -1;
3772     
3773     newGame = FALSE;
3774
3775     if (appData.debugMode)
3776       fprintf(debugFP, _("Parsing board: %s\n"), string);
3777
3778     move_str[0] = NULLCHAR;
3779     elapsed_time[0] = NULLCHAR;
3780     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3781         int  i = 0, j;
3782         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3783             if(string[i] == ' ') { ranks++; files = 0; }
3784             else files++;
3785             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3786             i++;
3787         }
3788         for(j = 0; j <i; j++) board_chars[j] = string[j];
3789         board_chars[i] = '\0';
3790         string += i + 1;
3791     }
3792     n = sscanf(string, PATTERN, &to_play, &double_push,
3793                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3794                &gamenum, white, black, &relation, &basetime, &increment,
3795                &white_stren, &black_stren, &white_time, &black_time,
3796                &moveNum, str, elapsed_time, move_str, &ics_flip,
3797                &ticking);
3798
3799     if (n < 21) {
3800         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3801         DisplayError(str, 0);
3802         return;
3803     }
3804
3805     /* Convert the move number to internal form */
3806     moveNum = (moveNum - 1) * 2;
3807     if (to_play == 'B') moveNum++;
3808     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3809       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3810                         0, 1);
3811       return;
3812     }
3813     
3814     switch (relation) {
3815       case RELATION_OBSERVING_PLAYED:
3816       case RELATION_OBSERVING_STATIC:
3817         if (gamenum == -1) {
3818             /* Old ICC buglet */
3819             relation = RELATION_OBSERVING_STATIC;
3820         }
3821         newGameMode = IcsObserving;
3822         break;
3823       case RELATION_PLAYING_MYMOVE:
3824       case RELATION_PLAYING_NOTMYMOVE:
3825         newGameMode =
3826           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3827             IcsPlayingWhite : IcsPlayingBlack;
3828         break;
3829       case RELATION_EXAMINING:
3830         newGameMode = IcsExamining;
3831         break;
3832       case RELATION_ISOLATED_BOARD:
3833       default:
3834         /* Just display this board.  If user was doing something else,
3835            we will forget about it until the next board comes. */ 
3836         newGameMode = IcsIdle;
3837         break;
3838       case RELATION_STARTING_POSITION:
3839         newGameMode = gameMode;
3840         break;
3841     }
3842     
3843     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3844          && newGameMode == IcsObserving && appData.bgObserve) {
3845       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3846       for (k = 0; k < ranks; k++) {
3847         for (j = 0; j < files; j++)
3848           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3849         if(gameInfo.holdingsWidth > 1) {
3850              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3851              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3852         }
3853       }
3854       CopyBoard(partnerBoard, board);
3855       if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3856       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3857       if(partnerUp) DrawPosition(repaint, partnerBoard);
3858       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3859       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3860                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3861       DisplayMessage(partnerStatus, "");
3862         partnerBoardValid = TRUE;
3863       return;
3864     }
3865
3866     /* Modify behavior for initial board display on move listing
3867        of wild games.
3868        */
3869     switch (ics_getting_history) {
3870       case H_FALSE:
3871       case H_REQUESTED:
3872         break;
3873       case H_GOT_REQ_HEADER:
3874       case H_GOT_UNREQ_HEADER:
3875         /* This is the initial position of the current game */
3876         gamenum = ics_gamenum;
3877         moveNum = 0;            /* old ICS bug workaround */
3878         if (to_play == 'B') {
3879           startedFromSetupPosition = TRUE;
3880           blackPlaysFirst = TRUE;
3881           moveNum = 1;
3882           if (forwardMostMove == 0) forwardMostMove = 1;
3883           if (backwardMostMove == 0) backwardMostMove = 1;
3884           if (currentMove == 0) currentMove = 1;
3885         }
3886         newGameMode = gameMode;
3887         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3888         break;
3889       case H_GOT_UNWANTED_HEADER:
3890         /* This is an initial board that we don't want */
3891         return;
3892       case H_GETTING_MOVES:
3893         /* Should not happen */
3894         DisplayError(_("Error gathering move list: extra board"), 0);
3895         ics_getting_history = H_FALSE;
3896         return;
3897     }
3898
3899    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3900                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3901      /* [HGM] We seem to have switched variant unexpectedly
3902       * Try to guess new variant from board size
3903       */
3904           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3905           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3906           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3907           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3908           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3909           if(!weird) newVariant = VariantNormal;
3910           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3911           /* Get a move list just to see the header, which
3912              will tell us whether this is really bug or zh */
3913           if (ics_getting_history == H_FALSE) {
3914             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3915             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3916             SendToICS(str);
3917           }
3918     }
3919     
3920     /* Take action if this is the first board of a new game, or of a
3921        different game than is currently being displayed.  */
3922     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3923         relation == RELATION_ISOLATED_BOARD) {
3924         
3925         /* Forget the old game and get the history (if any) of the new one */
3926         if (gameMode != BeginningOfGame) {
3927           Reset(TRUE, TRUE);
3928         }
3929         newGame = TRUE;
3930         if (appData.autoRaiseBoard) BoardToTop();
3931         prevMove = -3;
3932         if (gamenum == -1) {
3933             newGameMode = IcsIdle;
3934         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3935                    appData.getMoveList && !reqFlag) {
3936             /* Need to get game history */
3937             ics_getting_history = H_REQUESTED;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940         }
3941         
3942         /* Initially flip the board to have black on the bottom if playing
3943            black or if the ICS flip flag is set, but let the user change
3944            it with the Flip View button. */
3945         flipView = appData.autoFlipView ? 
3946           (newGameMode == IcsPlayingBlack) || ics_flip :
3947           appData.flipView;
3948         
3949         /* Done with values from previous mode; copy in new ones */
3950         gameMode = newGameMode;
3951         ModeHighlight();
3952         ics_gamenum = gamenum;
3953         if (gamenum == gs_gamenum) {
3954             int klen = strlen(gs_kind);
3955             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3956             sprintf(str, "ICS %s", gs_kind);
3957             gameInfo.event = StrSave(str);
3958         } else {
3959             gameInfo.event = StrSave("ICS game");
3960         }
3961         gameInfo.site = StrSave(appData.icsHost);
3962         gameInfo.date = PGNDate();
3963         gameInfo.round = StrSave("-");
3964         gameInfo.white = StrSave(white);
3965         gameInfo.black = StrSave(black);
3966         timeControl = basetime * 60 * 1000;
3967         timeControl_2 = 0;
3968         timeIncrement = increment * 1000;
3969         movesPerSession = 0;
3970         gameInfo.timeControl = TimeControlTagValue();
3971         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3972   if (appData.debugMode) {
3973     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3974     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3975     setbuf(debugFP, NULL);
3976   }
3977
3978         gameInfo.outOfBook = NULL;
3979         
3980         /* Do we have the ratings? */
3981         if (strcmp(player1Name, white) == 0 &&
3982             strcmp(player2Name, black) == 0) {
3983             if (appData.debugMode)
3984               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3985                       player1Rating, player2Rating);
3986             gameInfo.whiteRating = player1Rating;
3987             gameInfo.blackRating = player2Rating;
3988         } else if (strcmp(player2Name, white) == 0 &&
3989                    strcmp(player1Name, black) == 0) {
3990             if (appData.debugMode)
3991               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3992                       player2Rating, player1Rating);
3993             gameInfo.whiteRating = player2Rating;
3994             gameInfo.blackRating = player1Rating;
3995         }
3996         player1Name[0] = player2Name[0] = NULLCHAR;
3997
3998         /* Silence shouts if requested */
3999         if (appData.quietPlay &&
4000             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4001             SendToICS(ics_prefix);
4002             SendToICS("set shout 0\n");
4003         }
4004     }
4005     
4006     /* Deal with midgame name changes */
4007     if (!newGame) {
4008         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4009             if (gameInfo.white) free(gameInfo.white);
4010             gameInfo.white = StrSave(white);
4011         }
4012         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4013             if (gameInfo.black) free(gameInfo.black);
4014             gameInfo.black = StrSave(black);
4015         }
4016     }
4017     
4018     /* Throw away game result if anything actually changes in examine mode */
4019     if (gameMode == IcsExamining && !newGame) {
4020         gameInfo.result = GameUnfinished;
4021         if (gameInfo.resultDetails != NULL) {
4022             free(gameInfo.resultDetails);
4023             gameInfo.resultDetails = NULL;
4024         }
4025     }
4026     
4027     /* In pausing && IcsExamining mode, we ignore boards coming
4028        in if they are in a different variation than we are. */
4029     if (pauseExamInvalid) return;
4030     if (pausing && gameMode == IcsExamining) {
4031         if (moveNum <= pauseExamForwardMostMove) {
4032             pauseExamInvalid = TRUE;
4033             forwardMostMove = pauseExamForwardMostMove;
4034             return;
4035         }
4036     }
4037     
4038   if (appData.debugMode) {
4039     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4040   }
4041     /* Parse the board */
4042     for (k = 0; k < ranks; k++) {
4043       for (j = 0; j < files; j++)
4044         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4045       if(gameInfo.holdingsWidth > 1) {
4046            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4047            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4048       }
4049     }
4050     CopyBoard(boards[moveNum], board);
4051     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4052     if (moveNum == 0) {
4053         startedFromSetupPosition =
4054           !CompareBoards(board, initialPosition);
4055         if(startedFromSetupPosition)
4056             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4057     }
4058
4059     /* [HGM] Set castling rights. Take the outermost Rooks,
4060        to make it also work for FRC opening positions. Note that board12
4061        is really defective for later FRC positions, as it has no way to
4062        indicate which Rook can castle if they are on the same side of King.
4063        For the initial position we grant rights to the outermost Rooks,
4064        and remember thos rights, and we then copy them on positions
4065        later in an FRC game. This means WB might not recognize castlings with
4066        Rooks that have moved back to their original position as illegal,
4067        but in ICS mode that is not its job anyway.
4068     */
4069     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4070     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4071
4072         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4073             if(board[0][i] == WhiteRook) j = i;
4074         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4075         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4076             if(board[0][i] == WhiteRook) j = i;
4077         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4078         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4079             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4080         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4081         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4082             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4083         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4084
4085         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4086         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4087             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4088         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4089             if(board[BOARD_HEIGHT-1][k] == bKing)
4090                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4091         if(gameInfo.variant == VariantTwoKings) {
4092             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4093             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4094             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4095         }
4096     } else { int r;
4097         r = boards[moveNum][CASTLING][0] = initialRights[0];
4098         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4099         r = boards[moveNum][CASTLING][1] = initialRights[1];
4100         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4101         r = boards[moveNum][CASTLING][3] = initialRights[3];
4102         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4103         r = boards[moveNum][CASTLING][4] = initialRights[4];
4104         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4105         /* wildcastle kludge: always assume King has rights */
4106         r = boards[moveNum][CASTLING][2] = initialRights[2];
4107         r = boards[moveNum][CASTLING][5] = initialRights[5];
4108     }
4109     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4110     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4111
4112     
4113     if (ics_getting_history == H_GOT_REQ_HEADER ||
4114         ics_getting_history == H_GOT_UNREQ_HEADER) {
4115         /* This was an initial position from a move list, not
4116            the current position */
4117         return;
4118     }
4119     
4120     /* Update currentMove and known move number limits */
4121     newMove = newGame || moveNum > forwardMostMove;
4122
4123     if (newGame) {
4124         forwardMostMove = backwardMostMove = currentMove = moveNum;
4125         if (gameMode == IcsExamining && moveNum == 0) {
4126           /* Workaround for ICS limitation: we are not told the wild
4127              type when starting to examine a game.  But if we ask for
4128              the move list, the move list header will tell us */
4129             ics_getting_history = H_REQUESTED;
4130             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4131             SendToICS(str);
4132         }
4133     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4134                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4135 #if ZIPPY
4136         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4137         /* [HGM] applied this also to an engine that is silently watching        */
4138         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4139             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4140             gameInfo.variant == currentlyInitializedVariant) {
4141           takeback = forwardMostMove - moveNum;
4142           for (i = 0; i < takeback; i++) {
4143             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4144             SendToProgram("undo\n", &first);
4145           }
4146         }
4147 #endif
4148
4149         forwardMostMove = moveNum;
4150         if (!pausing || currentMove > forwardMostMove)
4151           currentMove = forwardMostMove;
4152     } else {
4153         /* New part of history that is not contiguous with old part */ 
4154         if (pausing && gameMode == IcsExamining) {
4155             pauseExamInvalid = TRUE;
4156             forwardMostMove = pauseExamForwardMostMove;
4157             return;
4158         }
4159         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4160 #if ZIPPY
4161             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4162                 // [HGM] when we will receive the move list we now request, it will be
4163                 // fed to the engine from the first move on. So if the engine is not
4164                 // in the initial position now, bring it there.
4165                 InitChessProgram(&first, 0);
4166             }
4167 #endif
4168             ics_getting_history = H_REQUESTED;
4169             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4170             SendToICS(str);
4171         }
4172         forwardMostMove = backwardMostMove = currentMove = moveNum;
4173     }
4174     
4175     /* Update the clocks */
4176     if (strchr(elapsed_time, '.')) {
4177       /* Time is in ms */
4178       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4179       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4180     } else {
4181       /* Time is in seconds */
4182       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4183       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4184     }
4185       
4186
4187 #if ZIPPY
4188     if (appData.zippyPlay && newGame &&
4189         gameMode != IcsObserving && gameMode != IcsIdle &&
4190         gameMode != IcsExamining)
4191       ZippyFirstBoard(moveNum, basetime, increment);
4192 #endif
4193     
4194     /* Put the move on the move list, first converting
4195        to canonical algebraic form. */
4196     if (moveNum > 0) {
4197   if (appData.debugMode) {
4198     if (appData.debugMode) { int f = forwardMostMove;
4199         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4200                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4201                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4202     }
4203     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4204     fprintf(debugFP, "moveNum = %d\n", moveNum);
4205     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4206     setbuf(debugFP, NULL);
4207   }
4208         if (moveNum <= backwardMostMove) {
4209             /* We don't know what the board looked like before
4210                this move.  Punt. */
4211             strcpy(parseList[moveNum - 1], move_str);
4212             strcat(parseList[moveNum - 1], " ");
4213             strcat(parseList[moveNum - 1], elapsed_time);
4214             moveList[moveNum - 1][0] = NULLCHAR;
4215         } else if (strcmp(move_str, "none") == 0) {
4216             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4217             /* Again, we don't know what the board looked like;
4218                this is really the start of the game. */
4219             parseList[moveNum - 1][0] = NULLCHAR;
4220             moveList[moveNum - 1][0] = NULLCHAR;
4221             backwardMostMove = moveNum;
4222             startedFromSetupPosition = TRUE;
4223             fromX = fromY = toX = toY = -1;
4224         } else {
4225           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4226           //                 So we parse the long-algebraic move string in stead of the SAN move
4227           int valid; char buf[MSG_SIZ], *prom;
4228
4229           // str looks something like "Q/a1-a2"; kill the slash
4230           if(str[1] == '/') 
4231                 sprintf(buf, "%c%s", str[0], str+2);
4232           else  strcpy(buf, str); // might be castling
4233           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4234                 strcat(buf, prom); // long move lacks promo specification!
4235           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4236                 if(appData.debugMode) 
4237                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4238                 strcpy(move_str, buf);
4239           }
4240           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4241                                 &fromX, &fromY, &toX, &toY, &promoChar)
4242                || ParseOneMove(buf, moveNum - 1, &moveType,
4243                                 &fromX, &fromY, &toX, &toY, &promoChar);
4244           // end of long SAN patch
4245           if (valid) {
4246             (void) CoordsToAlgebraic(boards[moveNum - 1],
4247                                      PosFlags(moveNum - 1),
4248                                      fromY, fromX, toY, toX, promoChar,
4249                                      parseList[moveNum-1]);
4250             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4251               case MT_NONE:
4252               case MT_STALEMATE:
4253               default:
4254                 break;
4255               case MT_CHECK:
4256                 if(gameInfo.variant != VariantShogi)
4257                     strcat(parseList[moveNum - 1], "+");
4258                 break;
4259               case MT_CHECKMATE:
4260               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4261                 strcat(parseList[moveNum - 1], "#");
4262                 break;
4263             }
4264             strcat(parseList[moveNum - 1], " ");
4265             strcat(parseList[moveNum - 1], elapsed_time);
4266             /* currentMoveString is set as a side-effect of ParseOneMove */
4267             strcpy(moveList[moveNum - 1], currentMoveString);
4268             strcat(moveList[moveNum - 1], "\n");
4269           } else {
4270             /* Move from ICS was illegal!?  Punt. */
4271   if (appData.debugMode) {
4272     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4273     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4274   }
4275             strcpy(parseList[moveNum - 1], move_str);
4276             strcat(parseList[moveNum - 1], " ");
4277             strcat(parseList[moveNum - 1], elapsed_time);
4278             moveList[moveNum - 1][0] = NULLCHAR;
4279             fromX = fromY = toX = toY = -1;
4280           }
4281         }
4282   if (appData.debugMode) {
4283     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4284     setbuf(debugFP, NULL);
4285   }
4286
4287 #if ZIPPY
4288         /* Send move to chess program (BEFORE animating it). */
4289         if (appData.zippyPlay && !newGame && newMove && 
4290            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4291
4292             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4293                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4294                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4295                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4296                             move_str);
4297                     DisplayError(str, 0);
4298                 } else {
4299                     if (first.sendTime) {
4300                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4301                     }
4302                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4303                     if (firstMove && !bookHit) {
4304                         firstMove = FALSE;
4305                         if (first.useColors) {
4306                           SendToProgram(gameMode == IcsPlayingWhite ?
4307                                         "white\ngo\n" :
4308                                         "black\ngo\n", &first);
4309                         } else {
4310                           SendToProgram("go\n", &first);
4311                         }
4312                         first.maybeThinking = TRUE;
4313                     }
4314                 }
4315             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4316               if (moveList[moveNum - 1][0] == NULLCHAR) {
4317                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4318                 DisplayError(str, 0);
4319               } else {
4320                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4321                 SendMoveToProgram(moveNum - 1, &first);
4322               }
4323             }
4324         }
4325 #endif
4326     }
4327
4328     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4329         /* If move comes from a remote source, animate it.  If it
4330            isn't remote, it will have already been animated. */
4331         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4332             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4333         }
4334         if (!pausing && appData.highlightLastMove) {
4335             SetHighlights(fromX, fromY, toX, toY);
4336         }
4337     }
4338     
4339     /* Start the clocks */
4340     whiteFlag = blackFlag = FALSE;
4341     appData.clockMode = !(basetime == 0 && increment == 0);
4342     if (ticking == 0) {
4343       ics_clock_paused = TRUE;
4344       StopClocks();
4345     } else if (ticking == 1) {
4346       ics_clock_paused = FALSE;
4347     }
4348     if (gameMode == IcsIdle ||
4349         relation == RELATION_OBSERVING_STATIC ||
4350         relation == RELATION_EXAMINING ||
4351         ics_clock_paused)
4352       DisplayBothClocks();
4353     else
4354       StartClocks();
4355     
4356     /* Display opponents and material strengths */
4357     if (gameInfo.variant != VariantBughouse &&
4358         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4359         if (tinyLayout || smallLayout) {
4360             if(gameInfo.variant == VariantNormal)
4361                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4362                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4363                     basetime, increment);
4364             else
4365                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4366                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4367                     basetime, increment, (int) gameInfo.variant);
4368         } else {
4369             if(gameInfo.variant == VariantNormal)
4370                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4371                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4372                     basetime, increment);
4373             else
4374                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4375                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4376                     basetime, increment, VariantName(gameInfo.variant));
4377         }
4378         DisplayTitle(str);
4379   if (appData.debugMode) {
4380     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4381   }
4382     }
4383
4384
4385     /* Display the board */
4386     if (!pausing && !appData.noGUI) {
4387       
4388       if (appData.premove)
4389           if (!gotPremove || 
4390              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4391              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4392               ClearPremoveHighlights();
4393
4394       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4395         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4396       DrawPosition(j, boards[currentMove]);
4397
4398       DisplayMove(moveNum - 1);
4399       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4400             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4401               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4402         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4403       }
4404     }
4405
4406     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4407 #if ZIPPY
4408     if(bookHit) { // [HGM] book: simulate book reply
4409         static char bookMove[MSG_SIZ]; // a bit generous?
4410
4411         programStats.nodes = programStats.depth = programStats.time = 
4412         programStats.score = programStats.got_only_move = 0;
4413         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4414
4415         strcpy(bookMove, "move ");
4416         strcat(bookMove, bookHit);
4417         HandleMachineMove(bookMove, &first);
4418     }
4419 #endif
4420 }
4421
4422 void
4423 GetMoveListEvent()
4424 {
4425     char buf[MSG_SIZ];
4426     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4427         ics_getting_history = H_REQUESTED;
4428         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4429         SendToICS(buf);
4430     }
4431 }
4432
4433 void
4434 AnalysisPeriodicEvent(force)
4435      int force;
4436 {
4437     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4438          && !force) || !appData.periodicUpdates)
4439       return;
4440
4441     /* Send . command to Crafty to collect stats */
4442     SendToProgram(".\n", &first);
4443
4444     /* Don't send another until we get a response (this makes
4445        us stop sending to old Crafty's which don't understand
4446        the "." command (sending illegal cmds resets node count & time,
4447        which looks bad)) */
4448     programStats.ok_to_send = 0;
4449 }
4450
4451 void ics_update_width(new_width)
4452         int new_width;
4453 {
4454         ics_printf("set width %d\n", new_width);
4455 }
4456
4457 void
4458 SendMoveToProgram(moveNum, cps)
4459      int moveNum;
4460      ChessProgramState *cps;
4461 {
4462     char buf[MSG_SIZ];
4463
4464     if (cps->useUsermove) {
4465       SendToProgram("usermove ", cps);
4466     }
4467     if (cps->useSAN) {
4468       char *space;
4469       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4470         int len = space - parseList[moveNum];
4471         memcpy(buf, parseList[moveNum], len);
4472         buf[len++] = '\n';
4473         buf[len] = NULLCHAR;
4474       } else {
4475         sprintf(buf, "%s\n", parseList[moveNum]);
4476       }
4477       SendToProgram(buf, cps);
4478     } else {
4479       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4480         AlphaRank(moveList[moveNum], 4);
4481         SendToProgram(moveList[moveNum], cps);
4482         AlphaRank(moveList[moveNum], 4); // and back
4483       } else
4484       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4485        * the engine. It would be nice to have a better way to identify castle 
4486        * moves here. */
4487       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4488                                                                          && cps->useOOCastle) {
4489         int fromX = moveList[moveNum][0] - AAA; 
4490         int fromY = moveList[moveNum][1] - ONE;
4491         int toX = moveList[moveNum][2] - AAA; 
4492         int toY = moveList[moveNum][3] - ONE;
4493         if((boards[moveNum][fromY][fromX] == WhiteKing 
4494             && boards[moveNum][toY][toX] == WhiteRook)
4495            || (boards[moveNum][fromY][fromX] == BlackKing 
4496                && boards[moveNum][toY][toX] == BlackRook)) {
4497           if(toX > fromX) SendToProgram("O-O\n", cps);
4498           else SendToProgram("O-O-O\n", cps);
4499         }
4500         else SendToProgram(moveList[moveNum], cps);
4501       }
4502       else SendToProgram(moveList[moveNum], cps);
4503       /* End of additions by Tord */
4504     }
4505
4506     /* [HGM] setting up the opening has brought engine in force mode! */
4507     /*       Send 'go' if we are in a mode where machine should play. */
4508     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4509         (gameMode == TwoMachinesPlay   ||
4510 #ifdef ZIPPY
4511          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4512 #endif
4513          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4514         SendToProgram("go\n", cps);
4515   if (appData.debugMode) {
4516     fprintf(debugFP, "(extra)\n");
4517   }
4518     }
4519     setboardSpoiledMachineBlack = 0;
4520 }
4521
4522 void
4523 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4524      ChessMove moveType;
4525      int fromX, fromY, toX, toY;
4526 {
4527     char user_move[MSG_SIZ];
4528
4529     switch (moveType) {
4530       default:
4531         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4532                 (int)moveType, fromX, fromY, toX, toY);
4533         DisplayError(user_move + strlen("say "), 0);
4534         break;
4535       case WhiteKingSideCastle:
4536       case BlackKingSideCastle:
4537       case WhiteQueenSideCastleWild:
4538       case BlackQueenSideCastleWild:
4539       /* PUSH Fabien */
4540       case WhiteHSideCastleFR:
4541       case BlackHSideCastleFR:
4542       /* POP Fabien */
4543         sprintf(user_move, "o-o\n");
4544         break;
4545       case WhiteQueenSideCastle:
4546       case BlackQueenSideCastle:
4547       case WhiteKingSideCastleWild:
4548       case BlackKingSideCastleWild:
4549       /* PUSH Fabien */
4550       case WhiteASideCastleFR:
4551       case BlackASideCastleFR:
4552       /* POP Fabien */
4553         sprintf(user_move, "o-o-o\n");
4554         break;
4555       case WhitePromotionQueen:
4556       case BlackPromotionQueen:
4557       case WhitePromotionRook:
4558       case BlackPromotionRook:
4559       case WhitePromotionBishop:
4560       case BlackPromotionBishop:
4561       case WhitePromotionKnight:
4562       case BlackPromotionKnight:
4563       case WhitePromotionKing:
4564       case BlackPromotionKing:
4565       case WhitePromotionChancellor:
4566       case BlackPromotionChancellor:
4567       case WhitePromotionArchbishop:
4568       case BlackPromotionArchbishop:
4569         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4570             sprintf(user_move, "%c%c%c%c=%c\n",
4571                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4572                 PieceToChar(WhiteFerz));
4573         else if(gameInfo.variant == VariantGreat)
4574             sprintf(user_move, "%c%c%c%c=%c\n",
4575                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4576                 PieceToChar(WhiteMan));
4577         else
4578             sprintf(user_move, "%c%c%c%c=%c\n",
4579                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4580                 PieceToChar(PromoPiece(moveType)));
4581         break;
4582       case WhiteDrop:
4583       case BlackDrop:
4584         sprintf(user_move, "%c@%c%c\n",
4585                 ToUpper(PieceToChar((ChessSquare) fromX)),
4586                 AAA + toX, ONE + toY);
4587         break;
4588       case NormalMove:
4589       case WhiteCapturesEnPassant:
4590       case BlackCapturesEnPassant:
4591       case IllegalMove:  /* could be a variant we don't quite understand */
4592         sprintf(user_move, "%c%c%c%c\n",
4593                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4594         break;
4595     }
4596     SendToICS(user_move);
4597     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4598         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4599 }
4600
4601 void
4602 UploadGameEvent()
4603 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4604     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4605     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4606     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4607         DisplayError("You cannot do this while you are playing or observing", 0);
4608         return;
4609     }
4610     if(gameMode != IcsExamining) { // is this ever not the case?
4611         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4612
4613         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4614             sprintf(command, "match %s", ics_handle);
4615         } else { // on FICS we must first go to general examine mode
4616             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4617         }
4618         if(gameInfo.variant != VariantNormal) {
4619             // try figure out wild number, as xboard names are not always valid on ICS
4620             for(i=1; i<=36; i++) {
4621                 sprintf(buf, "wild/%d", i);
4622                 if(StringToVariant(buf) == gameInfo.variant) break;
4623             }
4624             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4625             else if(i == 22) sprintf(buf, "%s fr\n", command);
4626             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4627         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4628         SendToICS(ics_prefix);
4629         SendToICS(buf);
4630         if(startedFromSetupPosition || backwardMostMove != 0) {
4631           fen = PositionToFEN(backwardMostMove, NULL);
4632           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4633             sprintf(buf, "loadfen %s\n", fen);
4634             SendToICS(buf);
4635           } else { // FICS: everything has to set by separate bsetup commands
4636             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4637             sprintf(buf, "bsetup fen %s\n", fen);
4638             SendToICS(buf);
4639             if(!WhiteOnMove(backwardMostMove)) {
4640                 SendToICS("bsetup tomove black\n");
4641             }
4642             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4643             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4644             SendToICS(buf);
4645             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4646             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4647             SendToICS(buf);
4648             i = boards[backwardMostMove][EP_STATUS];
4649             if(i >= 0) { // set e.p.
4650                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4651                 SendToICS(buf);
4652             }
4653             bsetup++;
4654           }
4655         }
4656       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4657             SendToICS("bsetup done\n"); // switch to normal examining.
4658     }
4659     for(i = backwardMostMove; i<last; i++) {
4660         char buf[20];
4661         sprintf(buf, "%s\n", parseList[i]);
4662         SendToICS(buf);
4663     }
4664     SendToICS(ics_prefix);
4665     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4666 }
4667
4668 void
4669 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4670      int rf, ff, rt, ft;
4671      char promoChar;
4672      char move[7];
4673 {
4674     if (rf == DROP_RANK) {
4675         sprintf(move, "%c@%c%c\n",
4676                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4677     } else {
4678         if (promoChar == 'x' || promoChar == NULLCHAR) {
4679             sprintf(move, "%c%c%c%c\n",
4680                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4681         } else {
4682             sprintf(move, "%c%c%c%c%c\n",
4683                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4684         }
4685     }
4686 }
4687
4688 void
4689 ProcessICSInitScript(f)
4690      FILE *f;
4691 {
4692     char buf[MSG_SIZ];
4693
4694     while (fgets(buf, MSG_SIZ, f)) {
4695         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4696     }
4697
4698     fclose(f);
4699 }
4700
4701
4702 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4703 void
4704 AlphaRank(char *move, int n)
4705 {
4706 //    char *p = move, c; int x, y;
4707
4708     if (appData.debugMode) {
4709         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4710     }
4711
4712     if(move[1]=='*' && 
4713        move[2]>='0' && move[2]<='9' &&
4714        move[3]>='a' && move[3]<='x'    ) {
4715         move[1] = '@';
4716         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4717         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4718     } else
4719     if(move[0]>='0' && move[0]<='9' &&
4720        move[1]>='a' && move[1]<='x' &&
4721        move[2]>='0' && move[2]<='9' &&
4722        move[3]>='a' && move[3]<='x'    ) {
4723         /* input move, Shogi -> normal */
4724         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4725         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4726         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4727         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4728     } else
4729     if(move[1]=='@' &&
4730        move[3]>='0' && move[3]<='9' &&
4731        move[2]>='a' && move[2]<='x'    ) {
4732         move[1] = '*';
4733         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4734         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4735     } else
4736     if(
4737        move[0]>='a' && move[0]<='x' &&
4738        move[3]>='0' && move[3]<='9' &&
4739        move[2]>='a' && move[2]<='x'    ) {
4740          /* output move, normal -> Shogi */
4741         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4742         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4743         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4745         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4746     }
4747     if (appData.debugMode) {
4748         fprintf(debugFP, "   out = '%s'\n", move);
4749     }
4750 }
4751
4752 char yy_textstr[8000];
4753
4754 /* Parser for moves from gnuchess, ICS, or user typein box */
4755 Boolean
4756 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4757      char *move;
4758      int moveNum;
4759      ChessMove *moveType;
4760      int *fromX, *fromY, *toX, *toY;
4761      char *promoChar;
4762 {       
4763     if (appData.debugMode) {
4764         fprintf(debugFP, "move to parse: %s\n", move);
4765     }
4766     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4767
4768     switch (*moveType) {
4769       case WhitePromotionChancellor:
4770       case BlackPromotionChancellor:
4771       case WhitePromotionArchbishop:
4772       case BlackPromotionArchbishop:
4773       case WhitePromotionQueen:
4774       case BlackPromotionQueen:
4775       case WhitePromotionRook:
4776       case BlackPromotionRook:
4777       case WhitePromotionBishop:
4778       case BlackPromotionBishop:
4779       case WhitePromotionKnight:
4780       case BlackPromotionKnight:
4781       case WhitePromotionKing:
4782       case BlackPromotionKing:
4783       case NormalMove:
4784       case WhiteCapturesEnPassant:
4785       case BlackCapturesEnPassant:
4786       case WhiteKingSideCastle:
4787       case WhiteQueenSideCastle:
4788       case BlackKingSideCastle:
4789       case BlackQueenSideCastle:
4790       case WhiteKingSideCastleWild:
4791       case WhiteQueenSideCastleWild:
4792       case BlackKingSideCastleWild:
4793       case BlackQueenSideCastleWild:
4794       /* Code added by Tord: */
4795       case WhiteHSideCastleFR:
4796       case WhiteASideCastleFR:
4797       case BlackHSideCastleFR:
4798       case BlackASideCastleFR:
4799       /* End of code added by Tord */
4800       case IllegalMove:         /* bug or odd chess variant */
4801         *fromX = currentMoveString[0] - AAA;
4802         *fromY = currentMoveString[1] - ONE;
4803         *toX = currentMoveString[2] - AAA;
4804         *toY = currentMoveString[3] - ONE;
4805         *promoChar = currentMoveString[4];
4806         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4807             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4808     if (appData.debugMode) {
4809         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4810     }
4811             *fromX = *fromY = *toX = *toY = 0;
4812             return FALSE;
4813         }
4814         if (appData.testLegality) {
4815           return (*moveType != IllegalMove);
4816         } else {
4817           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4818                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4819         }
4820
4821       case WhiteDrop:
4822       case BlackDrop:
4823         *fromX = *moveType == WhiteDrop ?
4824           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4825           (int) CharToPiece(ToLower(currentMoveString[0]));
4826         *fromY = DROP_RANK;
4827         *toX = currentMoveString[2] - AAA;
4828         *toY = currentMoveString[3] - ONE;
4829         *promoChar = NULLCHAR;
4830         return TRUE;
4831
4832       case AmbiguousMove:
4833       case ImpossibleMove:
4834       case (ChessMove) 0:       /* end of file */
4835       case ElapsedTime:
4836       case Comment:
4837       case PGNTag:
4838       case NAG:
4839       case WhiteWins:
4840       case BlackWins:
4841       case GameIsDrawn:
4842       default:
4843     if (appData.debugMode) {
4844         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4845     }
4846         /* bug? */
4847         *fromX = *fromY = *toX = *toY = 0;
4848         *promoChar = NULLCHAR;
4849         return FALSE;
4850     }
4851 }
4852
4853
4854 void
4855 ParsePV(char *pv, Boolean storeComments)
4856 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4857   int fromX, fromY, toX, toY; char promoChar;
4858   ChessMove moveType;
4859   Boolean valid;
4860   int nr = 0;
4861
4862   endPV = forwardMostMove;
4863   do {
4864     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4865     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4866     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4867 if(appData.debugMode){
4868 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);
4869 }
4870     if(!valid && nr == 0 &&
4871        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4872         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4873         // Hande case where played move is different from leading PV move
4874         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4875         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4876         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4877         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4878           endPV += 2; // if position different, keep this
4879           moveList[endPV-1][0] = fromX + AAA;
4880           moveList[endPV-1][1] = fromY + ONE;
4881           moveList[endPV-1][2] = toX + AAA;
4882           moveList[endPV-1][3] = toY + ONE;
4883           parseList[endPV-1][0] = NULLCHAR;
4884           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4885         }
4886       }
4887     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4888     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4889     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4890     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4891         valid++; // allow comments in PV
4892         continue;
4893     }
4894     nr++;
4895     if(endPV+1 > framePtr) break; // no space, truncate
4896     if(!valid) break;
4897     endPV++;
4898     CopyBoard(boards[endPV], boards[endPV-1]);
4899     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4900     moveList[endPV-1][0] = fromX + AAA;
4901     moveList[endPV-1][1] = fromY + ONE;
4902     moveList[endPV-1][2] = toX + AAA;
4903     moveList[endPV-1][3] = toY + ONE;
4904     if(storeComments)
4905         CoordsToAlgebraic(boards[endPV - 1],
4906                              PosFlags(endPV - 1),
4907                              fromY, fromX, toY, toX, promoChar,
4908                              parseList[endPV - 1]);
4909     else
4910         parseList[endPV-1][0] = NULLCHAR;
4911   } while(valid);
4912   currentMove = endPV;
4913   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4914   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4915                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4916   DrawPosition(TRUE, boards[currentMove]);
4917 }
4918
4919 static int lastX, lastY;
4920
4921 Boolean
4922 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4923 {
4924         int startPV;
4925         char *p;
4926
4927         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4928         lastX = x; lastY = y;
4929         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4930         startPV = index;
4931         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4932         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4933         index = startPV;
4934         do{ while(buf[index] && buf[index] != '\n') index++;
4935         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4936         buf[index] = 0;
4937         ParsePV(buf+startPV, FALSE);
4938         *start = startPV; *end = index-1;
4939         return TRUE;
4940 }
4941
4942 Boolean
4943 LoadPV(int x, int y)
4944 { // called on right mouse click to load PV
4945   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4946   lastX = x; lastY = y;
4947   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4948   return TRUE;
4949 }
4950
4951 void
4952 UnLoadPV()
4953 {
4954   if(endPV < 0) return;
4955   endPV = -1;
4956   currentMove = forwardMostMove;
4957   ClearPremoveHighlights();
4958   DrawPosition(TRUE, boards[currentMove]);
4959 }
4960
4961 void
4962 MovePV(int x, int y, int h)
4963 { // step through PV based on mouse coordinates (called on mouse move)
4964   int margin = h>>3, step = 0;
4965
4966   if(endPV < 0) return;
4967   // we must somehow check if right button is still down (might be released off board!)
4968   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4969   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4970   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4971   if(!step) return;
4972   lastX = x; lastY = y;
4973   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4974   currentMove += step;
4975   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4976   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4977                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4978   DrawPosition(FALSE, boards[currentMove]);
4979 }
4980
4981
4982 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4983 // All positions will have equal probability, but the current method will not provide a unique
4984 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4985 #define DARK 1
4986 #define LITE 2
4987 #define ANY 3
4988
4989 int squaresLeft[4];
4990 int piecesLeft[(int)BlackPawn];
4991 int seed, nrOfShuffles;
4992
4993 void GetPositionNumber()
4994 {       // sets global variable seed
4995         int i;
4996
4997         seed = appData.defaultFrcPosition;
4998         if(seed < 0) { // randomize based on time for negative FRC position numbers
4999                 for(i=0; i<50; i++) seed += random();
5000                 seed = random() ^ random() >> 8 ^ random() << 8;
5001                 if(seed<0) seed = -seed;
5002         }
5003 }
5004
5005 int put(Board board, int pieceType, int rank, int n, int shade)
5006 // put the piece on the (n-1)-th empty squares of the given shade
5007 {
5008         int i;
5009
5010         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5011                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5012                         board[rank][i] = (ChessSquare) pieceType;
5013                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5014                         squaresLeft[ANY]--;
5015                         piecesLeft[pieceType]--; 
5016                         return i;
5017                 }
5018         }
5019         return -1;
5020 }
5021
5022
5023 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5024 // calculate where the next piece goes, (any empty square), and put it there
5025 {
5026         int i;
5027
5028         i = seed % squaresLeft[shade];
5029         nrOfShuffles *= squaresLeft[shade];
5030         seed /= squaresLeft[shade];
5031         put(board, pieceType, rank, i, shade);
5032 }
5033
5034 void AddTwoPieces(Board board, int pieceType, int rank)
5035 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5036 {
5037         int i, n=squaresLeft[ANY], j=n-1, k;
5038
5039         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5040         i = seed % k;  // pick one
5041         nrOfShuffles *= k;
5042         seed /= k;
5043         while(i >= j) i -= j--;
5044         j = n - 1 - j; i += j;
5045         put(board, pieceType, rank, j, ANY);
5046         put(board, pieceType, rank, i, ANY);
5047 }
5048
5049 void SetUpShuffle(Board board, int number)
5050 {
5051         int i, p, first=1;
5052
5053         GetPositionNumber(); nrOfShuffles = 1;
5054
5055         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5056         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5057         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5058
5059         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5060
5061         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5062             p = (int) board[0][i];
5063             if(p < (int) BlackPawn) piecesLeft[p] ++;
5064             board[0][i] = EmptySquare;
5065         }
5066
5067         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5068             // shuffles restricted to allow normal castling put KRR first
5069             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5070                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5071             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5072                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5073             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5074                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5075             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5076                 put(board, WhiteRook, 0, 0, ANY);
5077             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5078         }
5079
5080         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5081             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5082             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5083                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5084                 while(piecesLeft[p] >= 2) {
5085                     AddOnePiece(board, p, 0, LITE);
5086                     AddOnePiece(board, p, 0, DARK);
5087                 }
5088                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5089             }
5090
5091         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5092             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5093             // but we leave King and Rooks for last, to possibly obey FRC restriction
5094             if(p == (int)WhiteRook) continue;
5095             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5096             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5097         }
5098
5099         // now everything is placed, except perhaps King (Unicorn) and Rooks
5100
5101         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5102             // Last King gets castling rights
5103             while(piecesLeft[(int)WhiteUnicorn]) {
5104                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5105                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5106             }
5107
5108             while(piecesLeft[(int)WhiteKing]) {
5109                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5110                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5111             }
5112
5113
5114         } else {
5115             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5116             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5117         }
5118
5119         // Only Rooks can be left; simply place them all
5120         while(piecesLeft[(int)WhiteRook]) {
5121                 i = put(board, WhiteRook, 0, 0, ANY);
5122                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5123                         if(first) {
5124                                 first=0;
5125                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5126                         }
5127                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5128                 }
5129         }
5130         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5131             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5132         }
5133
5134         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5135 }
5136
5137 int SetCharTable( char *table, const char * map )
5138 /* [HGM] moved here from winboard.c because of its general usefulness */
5139 /*       Basically a safe strcpy that uses the last character as King */
5140 {
5141     int result = FALSE; int NrPieces;
5142
5143     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5144                     && NrPieces >= 12 && !(NrPieces&1)) {
5145         int i; /* [HGM] Accept even length from 12 to 34 */
5146
5147         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5148         for( i=0; i<NrPieces/2-1; i++ ) {
5149             table[i] = map[i];
5150             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5151         }
5152         table[(int) WhiteKing]  = map[NrPieces/2-1];
5153         table[(int) BlackKing]  = map[NrPieces-1];
5154
5155         result = TRUE;
5156     }
5157
5158     return result;
5159 }
5160
5161 void Prelude(Board board)
5162 {       // [HGM] superchess: random selection of exo-pieces
5163         int i, j, k; ChessSquare p; 
5164         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5165
5166         GetPositionNumber(); // use FRC position number
5167
5168         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5169             SetCharTable(pieceToChar, appData.pieceToCharTable);
5170             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5171                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5172         }
5173
5174         j = seed%4;                 seed /= 4; 
5175         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5176         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5177         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5178         j = seed%3 + (seed%3 >= j); seed /= 3; 
5179         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5180         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5181         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5182         j = seed%3;                 seed /= 3; 
5183         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5184         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5185         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5186         j = seed%2 + (seed%2 >= j); seed /= 2; 
5187         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5188         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5189         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5190         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5191         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5192         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5193         put(board, exoPieces[0],    0, 0, ANY);
5194         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5195 }
5196
5197 void
5198 InitPosition(redraw)
5199      int redraw;
5200 {
5201     ChessSquare (* pieces)[BOARD_FILES];
5202     int i, j, pawnRow, overrule,
5203     oldx = gameInfo.boardWidth,
5204     oldy = gameInfo.boardHeight,
5205     oldh = gameInfo.holdingsWidth,
5206     oldv = gameInfo.variant;
5207
5208     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5209
5210     /* [AS] Initialize pv info list [HGM] and game status */
5211     {
5212         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5213             pvInfoList[i].depth = 0;
5214             boards[i][EP_STATUS] = EP_NONE;
5215             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5216         }
5217
5218         initialRulePlies = 0; /* 50-move counter start */
5219
5220         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5221         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5222     }
5223
5224     
5225     /* [HGM] logic here is completely changed. In stead of full positions */
5226     /* the initialized data only consist of the two backranks. The switch */
5227     /* selects which one we will use, which is than copied to the Board   */
5228     /* initialPosition, which for the rest is initialized by Pawns and    */
5229     /* empty squares. This initial position is then copied to boards[0],  */
5230     /* possibly after shuffling, so that it remains available.            */
5231
5232     gameInfo.holdingsWidth = 0; /* default board sizes */
5233     gameInfo.boardWidth    = 8;
5234     gameInfo.boardHeight   = 8;
5235     gameInfo.holdingsSize  = 0;
5236     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5237     for(i=0; i<BOARD_FILES-2; i++)
5238       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5239     initialPosition[EP_STATUS] = EP_NONE;
5240     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5241
5242     switch (gameInfo.variant) {
5243     case VariantFischeRandom:
5244       shuffleOpenings = TRUE;
5245     default:
5246       pieces = FIDEArray;
5247       break;
5248     case VariantShatranj:
5249       pieces = ShatranjArray;
5250       nrCastlingRights = 0;
5251       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5252       break;
5253     case VariantMakruk:
5254       pieces = makrukArray;
5255       nrCastlingRights = 0;
5256       startedFromSetupPosition = TRUE;
5257       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5258       break;
5259     case VariantTwoKings:
5260       pieces = twoKingsArray;
5261       break;
5262     case VariantCapaRandom:
5263       shuffleOpenings = TRUE;
5264     case VariantCapablanca:
5265       pieces = CapablancaArray;
5266       gameInfo.boardWidth = 10;
5267       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5268       break;
5269     case VariantGothic:
5270       pieces = GothicArray;
5271       gameInfo.boardWidth = 10;
5272       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5273       break;
5274     case VariantJanus:
5275       pieces = JanusArray;
5276       gameInfo.boardWidth = 10;
5277       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5278       nrCastlingRights = 6;
5279         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5280         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5281         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5282         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5283         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5284         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5285       break;
5286     case VariantFalcon:
5287       pieces = FalconArray;
5288       gameInfo.boardWidth = 10;
5289       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5290       break;
5291     case VariantXiangqi:
5292       pieces = XiangqiArray;
5293       gameInfo.boardWidth  = 9;
5294       gameInfo.boardHeight = 10;
5295       nrCastlingRights = 0;
5296       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5297       break;
5298     case VariantShogi:
5299       pieces = ShogiArray;
5300       gameInfo.boardWidth  = 9;
5301       gameInfo.boardHeight = 9;
5302       gameInfo.holdingsSize = 7;
5303       nrCastlingRights = 0;
5304       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5305       break;
5306     case VariantCourier:
5307       pieces = CourierArray;
5308       gameInfo.boardWidth  = 12;
5309       nrCastlingRights = 0;
5310       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5311       break;
5312     case VariantKnightmate:
5313       pieces = KnightmateArray;
5314       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5315       break;
5316     case VariantFairy:
5317       pieces = fairyArray;
5318       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5319       break;
5320     case VariantGreat:
5321       pieces = GreatArray;
5322       gameInfo.boardWidth = 10;
5323       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5324       gameInfo.holdingsSize = 8;
5325       break;
5326     case VariantSuper:
5327       pieces = FIDEArray;
5328       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5329       gameInfo.holdingsSize = 8;
5330       startedFromSetupPosition = TRUE;
5331       break;
5332     case VariantCrazyhouse:
5333     case VariantBughouse:
5334       pieces = FIDEArray;
5335       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5336       gameInfo.holdingsSize = 5;
5337       break;
5338     case VariantWildCastle:
5339       pieces = FIDEArray;
5340       /* !!?shuffle with kings guaranteed to be on d or e file */
5341       shuffleOpenings = 1;
5342       break;
5343     case VariantNoCastle:
5344       pieces = FIDEArray;
5345       nrCastlingRights = 0;
5346       /* !!?unconstrained back-rank shuffle */
5347       shuffleOpenings = 1;
5348       break;
5349     }
5350
5351     overrule = 0;
5352     if(appData.NrFiles >= 0) {
5353         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5354         gameInfo.boardWidth = appData.NrFiles;
5355     }
5356     if(appData.NrRanks >= 0) {
5357         gameInfo.boardHeight = appData.NrRanks;
5358     }
5359     if(appData.holdingsSize >= 0) {
5360         i = appData.holdingsSize;
5361         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5362         gameInfo.holdingsSize = i;
5363     }
5364     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5365     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5366         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5367
5368     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5369     if(pawnRow < 1) pawnRow = 1;
5370     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5371
5372     /* User pieceToChar list overrules defaults */
5373     if(appData.pieceToCharTable != NULL)
5374         SetCharTable(pieceToChar, appData.pieceToCharTable);
5375
5376     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5377
5378         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5379             s = (ChessSquare) 0; /* account holding counts in guard band */
5380         for( i=0; i<BOARD_HEIGHT; i++ )
5381             initialPosition[i][j] = s;
5382
5383         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5384         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5385         initialPosition[pawnRow][j] = WhitePawn;
5386         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5387         if(gameInfo.variant == VariantXiangqi) {
5388             if(j&1) {
5389                 initialPosition[pawnRow][j] = 
5390                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5391                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5392                    initialPosition[2][j] = WhiteCannon;
5393                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5394                 }
5395             }
5396         }
5397         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5398     }
5399     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5400
5401             j=BOARD_LEFT+1;
5402             initialPosition[1][j] = WhiteBishop;
5403             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5404             j=BOARD_RGHT-2;
5405             initialPosition[1][j] = WhiteRook;
5406             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5407     }
5408
5409     if( nrCastlingRights == -1) {
5410         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5411         /*       This sets default castling rights from none to normal corners   */
5412         /* Variants with other castling rights must set them themselves above    */
5413         nrCastlingRights = 6;
5414        
5415         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5416         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5417         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5418         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5419         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5420         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5421      }
5422
5423      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5424      if(gameInfo.variant == VariantGreat) { // promotion commoners
5425         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5426         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5427         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5428         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5429      }
5430   if (appData.debugMode) {
5431     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5432   }
5433     if(shuffleOpenings) {
5434         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5435         startedFromSetupPosition = TRUE;
5436     }
5437     if(startedFromPositionFile) {
5438       /* [HGM] loadPos: use PositionFile for every new game */
5439       CopyBoard(initialPosition, filePosition);
5440       for(i=0; i<nrCastlingRights; i++)
5441           initialRights[i] = filePosition[CASTLING][i];
5442       startedFromSetupPosition = TRUE;
5443     }
5444
5445     CopyBoard(boards[0], initialPosition);
5446
5447     if(oldx != gameInfo.boardWidth ||
5448        oldy != gameInfo.boardHeight ||
5449        oldh != gameInfo.holdingsWidth
5450 #ifdef GOTHIC
5451        || oldv == VariantGothic ||        // For licensing popups
5452        gameInfo.variant == VariantGothic
5453 #endif
5454 #ifdef FALCON
5455        || oldv == VariantFalcon ||
5456        gameInfo.variant == VariantFalcon
5457 #endif
5458                                          )
5459             InitDrawingSizes(-2 ,0);
5460
5461     if (redraw)
5462       DrawPosition(TRUE, boards[currentMove]);
5463 }
5464
5465 void
5466 SendBoard(cps, moveNum)
5467      ChessProgramState *cps;
5468      int moveNum;
5469 {
5470     char message[MSG_SIZ];
5471     
5472     if (cps->useSetboard) {
5473       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5474       sprintf(message, "setboard %s\n", fen);
5475       SendToProgram(message, cps);
5476       free(fen);
5477
5478     } else {
5479       ChessSquare *bp;
5480       int i, j;
5481       /* Kludge to set black to move, avoiding the troublesome and now
5482        * deprecated "black" command.
5483        */
5484       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5485
5486       SendToProgram("edit\n", cps);
5487       SendToProgram("#\n", cps);
5488       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5489         bp = &boards[moveNum][i][BOARD_LEFT];
5490         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5491           if ((int) *bp < (int) BlackPawn) {
5492             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5493                     AAA + j, ONE + i);
5494             if(message[0] == '+' || message[0] == '~') {
5495                 sprintf(message, "%c%c%c+\n",
5496                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5497                         AAA + j, ONE + i);
5498             }
5499             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5500                 message[1] = BOARD_RGHT   - 1 - j + '1';
5501                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5502             }
5503             SendToProgram(message, cps);
5504           }
5505         }
5506       }
5507     
5508       SendToProgram("c\n", cps);
5509       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5510         bp = &boards[moveNum][i][BOARD_LEFT];
5511         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5512           if (((int) *bp != (int) EmptySquare)
5513               && ((int) *bp >= (int) BlackPawn)) {
5514             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5515                     AAA + j, ONE + i);
5516             if(message[0] == '+' || message[0] == '~') {
5517                 sprintf(message, "%c%c%c+\n",
5518                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5519                         AAA + j, ONE + i);
5520             }
5521             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5522                 message[1] = BOARD_RGHT   - 1 - j + '1';
5523                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5524             }
5525             SendToProgram(message, cps);
5526           }
5527         }
5528       }
5529     
5530       SendToProgram(".\n", cps);
5531     }
5532     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5533 }
5534
5535 static int autoQueen; // [HGM] oneclick
5536
5537 int
5538 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5539 {
5540     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5541     /* [HGM] add Shogi promotions */
5542     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5543     ChessSquare piece;
5544     ChessMove moveType;
5545     Boolean premove;
5546
5547     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5548     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5549
5550     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5551       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5552         return FALSE;
5553
5554     piece = boards[currentMove][fromY][fromX];
5555     if(gameInfo.variant == VariantShogi) {
5556         promotionZoneSize = 3;
5557         highestPromotingPiece = (int)WhiteFerz;
5558     } else if(gameInfo.variant == VariantMakruk) {
5559         promotionZoneSize = 3;
5560     }
5561
5562     // next weed out all moves that do not touch the promotion zone at all
5563     if((int)piece >= BlackPawn) {
5564         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5565              return FALSE;
5566         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5567     } else {
5568         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5569            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5570     }
5571
5572     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5573
5574     // weed out mandatory Shogi promotions
5575     if(gameInfo.variant == VariantShogi) {
5576         if(piece >= BlackPawn) {
5577             if(toY == 0 && piece == BlackPawn ||
5578                toY == 0 && piece == BlackQueen ||
5579                toY <= 1 && piece == BlackKnight) {
5580                 *promoChoice = '+';
5581                 return FALSE;
5582             }
5583         } else {
5584             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5585                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5586                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5587                 *promoChoice = '+';
5588                 return FALSE;
5589             }
5590         }
5591     }
5592
5593     // weed out obviously illegal Pawn moves
5594     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5595         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5596         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5597         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5598         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5599         // note we are not allowed to test for valid (non-)capture, due to premove
5600     }
5601
5602     // we either have a choice what to promote to, or (in Shogi) whether to promote
5603     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5604         *promoChoice = PieceToChar(BlackFerz);  // no choice
5605         return FALSE;
5606     }
5607     if(autoQueen) { // predetermined
5608         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5609              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5610         else *promoChoice = PieceToChar(BlackQueen);
5611         return FALSE;
5612     }
5613
5614     // suppress promotion popup on illegal moves that are not premoves
5615     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5616               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5617     if(appData.testLegality && !premove) {
5618         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5619                         fromY, fromX, toY, toX, NULLCHAR);
5620         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5621            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5622             return FALSE;
5623     }
5624
5625     return TRUE;
5626 }
5627
5628 int
5629 InPalace(row, column)
5630      int row, column;
5631 {   /* [HGM] for Xiangqi */
5632     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5633          column < (BOARD_WIDTH + 4)/2 &&
5634          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5635     return FALSE;
5636 }
5637
5638 int
5639 PieceForSquare (x, y)
5640      int x;
5641      int y;
5642 {
5643   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5644      return -1;
5645   else
5646      return boards[currentMove][y][x];
5647 }
5648
5649 int
5650 OKToStartUserMove(x, y)
5651      int x, y;
5652 {
5653     ChessSquare from_piece;
5654     int white_piece;
5655
5656     if (matchMode) return FALSE;
5657     if (gameMode == EditPosition) return TRUE;
5658
5659     if (x >= 0 && y >= 0)
5660       from_piece = boards[currentMove][y][x];
5661     else
5662       from_piece = EmptySquare;
5663
5664     if (from_piece == EmptySquare) return FALSE;
5665
5666     white_piece = (int)from_piece >= (int)WhitePawn &&
5667       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5668
5669     switch (gameMode) {
5670       case PlayFromGameFile:
5671       case AnalyzeFile:
5672       case TwoMachinesPlay:
5673       case EndOfGame:
5674         return FALSE;
5675
5676       case IcsObserving:
5677       case IcsIdle:
5678         return FALSE;
5679
5680       case MachinePlaysWhite:
5681       case IcsPlayingBlack:
5682         if (appData.zippyPlay) return FALSE;
5683         if (white_piece) {
5684             DisplayMoveError(_("You are playing Black"));
5685             return FALSE;
5686         }
5687         break;
5688
5689       case MachinePlaysBlack:
5690       case IcsPlayingWhite:
5691         if (appData.zippyPlay) return FALSE;
5692         if (!white_piece) {
5693             DisplayMoveError(_("You are playing White"));
5694             return FALSE;
5695         }
5696         break;
5697
5698       case EditGame:
5699         if (!white_piece && WhiteOnMove(currentMove)) {
5700             DisplayMoveError(_("It is White's turn"));
5701             return FALSE;
5702         }           
5703         if (white_piece && !WhiteOnMove(currentMove)) {
5704             DisplayMoveError(_("It is Black's turn"));
5705             return FALSE;
5706         }           
5707         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5708             /* Editing correspondence game history */
5709             /* Could disallow this or prompt for confirmation */
5710             cmailOldMove = -1;
5711         }
5712         break;
5713
5714       case BeginningOfGame:
5715         if (appData.icsActive) return FALSE;
5716         if (!appData.noChessProgram) {
5717             if (!white_piece) {
5718                 DisplayMoveError(_("You are playing White"));
5719                 return FALSE;
5720             }
5721         }
5722         break;
5723         
5724       case Training:
5725         if (!white_piece && WhiteOnMove(currentMove)) {
5726             DisplayMoveError(_("It is White's turn"));
5727             return FALSE;
5728         }           
5729         if (white_piece && !WhiteOnMove(currentMove)) {
5730             DisplayMoveError(_("It is Black's turn"));
5731             return FALSE;
5732         }           
5733         break;
5734
5735       default:
5736       case IcsExamining:
5737         break;
5738     }
5739     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5740         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5741         && gameMode != AnalyzeFile && gameMode != Training) {
5742         DisplayMoveError(_("Displayed position is not current"));
5743         return FALSE;
5744     }
5745     return TRUE;
5746 }
5747
5748 Boolean
5749 OnlyMove(int *x, int *y, Boolean captures) {
5750     DisambiguateClosure cl;
5751     if (appData.zippyPlay) return FALSE;
5752     switch(gameMode) {
5753       case MachinePlaysBlack:
5754       case IcsPlayingWhite:
5755       case BeginningOfGame:
5756         if(!WhiteOnMove(currentMove)) return FALSE;
5757         break;
5758       case MachinePlaysWhite:
5759       case IcsPlayingBlack:
5760         if(WhiteOnMove(currentMove)) return FALSE;
5761         break;
5762       default:
5763         return FALSE;
5764     }
5765     cl.pieceIn = EmptySquare; 
5766     cl.rfIn = *y;
5767     cl.ffIn = *x;
5768     cl.rtIn = -1;
5769     cl.ftIn = -1;
5770     cl.promoCharIn = NULLCHAR;
5771     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5772     if( cl.kind == NormalMove ||
5773         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5774         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5775         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5776         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5777       fromX = cl.ff;
5778       fromY = cl.rf;
5779       *x = cl.ft;
5780       *y = cl.rt;
5781       return TRUE;
5782     }
5783     if(cl.kind != ImpossibleMove) return FALSE;
5784     cl.pieceIn = EmptySquare;
5785     cl.rfIn = -1;
5786     cl.ffIn = -1;
5787     cl.rtIn = *y;
5788     cl.ftIn = *x;
5789     cl.promoCharIn = NULLCHAR;
5790     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5791     if( cl.kind == NormalMove ||
5792         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5793         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5794         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5795         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5796       fromX = cl.ff;
5797       fromY = cl.rf;
5798       *x = cl.ft;
5799       *y = cl.rt;
5800       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5801       return TRUE;
5802     }
5803     return FALSE;
5804 }
5805
5806 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5807 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5808 int lastLoadGameUseList = FALSE;
5809 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5810 ChessMove lastLoadGameStart = (ChessMove) 0;
5811
5812 ChessMove
5813 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5814      int fromX, fromY, toX, toY;
5815      int promoChar;
5816      Boolean captureOwn;
5817 {
5818     ChessMove moveType;
5819     ChessSquare pdown, pup;
5820
5821     /* Check if the user is playing in turn.  This is complicated because we
5822        let the user "pick up" a piece before it is his turn.  So the piece he
5823        tried to pick up may have been captured by the time he puts it down!
5824        Therefore we use the color the user is supposed to be playing in this
5825        test, not the color of the piece that is currently on the starting
5826        square---except in EditGame mode, where the user is playing both
5827        sides; fortunately there the capture race can't happen.  (It can
5828        now happen in IcsExamining mode, but that's just too bad.  The user
5829        will get a somewhat confusing message in that case.)
5830        */
5831
5832     switch (gameMode) {
5833       case PlayFromGameFile:
5834       case AnalyzeFile:
5835       case TwoMachinesPlay:
5836       case EndOfGame:
5837       case IcsObserving:
5838       case IcsIdle:
5839         /* We switched into a game mode where moves are not accepted,
5840            perhaps while the mouse button was down. */
5841         return ImpossibleMove;
5842
5843       case MachinePlaysWhite:
5844         /* User is moving for Black */
5845         if (WhiteOnMove(currentMove)) {
5846             DisplayMoveError(_("It is White's turn"));
5847             return ImpossibleMove;
5848         }
5849         break;
5850
5851       case MachinePlaysBlack:
5852         /* User is moving for White */
5853         if (!WhiteOnMove(currentMove)) {
5854             DisplayMoveError(_("It is Black's turn"));
5855             return ImpossibleMove;
5856         }
5857         break;
5858
5859       case EditGame:
5860       case IcsExamining:
5861       case BeginningOfGame:
5862       case AnalyzeMode:
5863       case Training:
5864         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5865             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5866             /* User is moving for Black */
5867             if (WhiteOnMove(currentMove)) {
5868                 DisplayMoveError(_("It is White's turn"));
5869                 return ImpossibleMove;
5870             }
5871         } else {
5872             /* User is moving for White */
5873             if (!WhiteOnMove(currentMove)) {
5874                 DisplayMoveError(_("It is Black's turn"));
5875                 return ImpossibleMove;
5876             }
5877         }
5878         break;
5879
5880       case IcsPlayingBlack:
5881         /* User is moving for Black */
5882         if (WhiteOnMove(currentMove)) {
5883             if (!appData.premove) {
5884                 DisplayMoveError(_("It is White's turn"));
5885             } else if (toX >= 0 && toY >= 0) {
5886                 premoveToX = toX;
5887                 premoveToY = toY;
5888                 premoveFromX = fromX;
5889                 premoveFromY = fromY;
5890                 premovePromoChar = promoChar;
5891                 gotPremove = 1;
5892                 if (appData.debugMode) 
5893                     fprintf(debugFP, "Got premove: fromX %d,"
5894                             "fromY %d, toX %d, toY %d\n",
5895                             fromX, fromY, toX, toY);
5896             }
5897             return ImpossibleMove;
5898         }
5899         break;
5900
5901       case IcsPlayingWhite:
5902         /* User is moving for White */
5903         if (!WhiteOnMove(currentMove)) {
5904             if (!appData.premove) {
5905                 DisplayMoveError(_("It is Black's turn"));
5906             } else if (toX >= 0 && toY >= 0) {
5907                 premoveToX = toX;
5908                 premoveToY = toY;
5909                 premoveFromX = fromX;
5910                 premoveFromY = fromY;
5911                 premovePromoChar = promoChar;
5912                 gotPremove = 1;
5913                 if (appData.debugMode) 
5914                     fprintf(debugFP, "Got premove: fromX %d,"
5915                             "fromY %d, toX %d, toY %d\n",
5916                             fromX, fromY, toX, toY);
5917             }
5918             return ImpossibleMove;
5919         }
5920         break;
5921
5922       default:
5923         break;
5924
5925       case EditPosition:
5926         /* EditPosition, empty square, or different color piece;
5927            click-click move is possible */
5928         if (toX == -2 || toY == -2) {
5929             boards[0][fromY][fromX] = EmptySquare;
5930             return AmbiguousMove;
5931         } else if (toX >= 0 && toY >= 0) {
5932             boards[0][toY][toX] = boards[0][fromY][fromX];
5933             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5934                 if(boards[0][fromY][0] != EmptySquare) {
5935                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5936                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5937                 }
5938             } else
5939             if(fromX == BOARD_RGHT+1) {
5940                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5941                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5942                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5943                 }
5944             } else
5945             boards[0][fromY][fromX] = EmptySquare;
5946             return AmbiguousMove;
5947         }
5948         return ImpossibleMove;
5949     }
5950
5951     if(toX < 0 || toY < 0) return ImpossibleMove;
5952     pdown = boards[currentMove][fromY][fromX];
5953     pup = boards[currentMove][toY][toX];
5954
5955     /* [HGM] If move started in holdings, it means a drop */
5956     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5957          if( pup != EmptySquare ) return ImpossibleMove;
5958          if(appData.testLegality) {
5959              /* it would be more logical if LegalityTest() also figured out
5960               * which drops are legal. For now we forbid pawns on back rank.
5961               * Shogi is on its own here...
5962               */
5963              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5964                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5965                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5966          }
5967          return WhiteDrop; /* Not needed to specify white or black yet */
5968     }
5969
5970     /* [HGM] always test for legality, to get promotion info */
5971     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5972                                          fromY, fromX, toY, toX, promoChar);
5973     /* [HGM] but possibly ignore an IllegalMove result */
5974     if (appData.testLegality) {
5975         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5976             DisplayMoveError(_("Illegal move"));
5977             return ImpossibleMove;
5978         }
5979     }
5980
5981     return moveType;
5982     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5983        function is made into one that returns an OK move type if FinishMove
5984        should be called. This to give the calling driver routine the
5985        opportunity to finish the userMove input with a promotion popup,
5986        without bothering the user with this for invalid or illegal moves */
5987
5988 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5989 }
5990
5991 /* Common tail of UserMoveEvent and DropMenuEvent */
5992 int
5993 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5994      ChessMove moveType;
5995      int fromX, fromY, toX, toY;
5996      /*char*/int promoChar;
5997 {
5998     char *bookHit = 0;
5999
6000     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6001         // [HGM] superchess: suppress promotions to non-available piece
6002         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6003         if(WhiteOnMove(currentMove)) {
6004             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6005         } else {
6006             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6007         }
6008     }
6009
6010     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6011        move type in caller when we know the move is a legal promotion */
6012     if(moveType == NormalMove && promoChar)
6013         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6014
6015     /* [HGM] convert drag-and-drop piece drops to standard form */
6016     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6017          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6018            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6019                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6020            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6021            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6022            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6023            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6024          fromY = DROP_RANK;
6025     }
6026
6027     /* [HGM] <popupFix> The following if has been moved here from
6028        UserMoveEvent(). Because it seemed to belong here (why not allow
6029        piece drops in training games?), and because it can only be
6030        performed after it is known to what we promote. */
6031     if (gameMode == Training) {
6032       /* compare the move played on the board to the next move in the
6033        * game. If they match, display the move and the opponent's response. 
6034        * If they don't match, display an error message.
6035        */
6036       int saveAnimate;
6037       Board testBoard;
6038       CopyBoard(testBoard, boards[currentMove]);
6039       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6040
6041       if (CompareBoards(testBoard, boards[currentMove+1])) {
6042         ForwardInner(currentMove+1);
6043
6044         /* Autoplay the opponent's response.
6045          * if appData.animate was TRUE when Training mode was entered,
6046          * the response will be animated.
6047          */
6048         saveAnimate = appData.animate;
6049         appData.animate = animateTraining;
6050         ForwardInner(currentMove+1);
6051         appData.animate = saveAnimate;
6052
6053         /* check for the end of the game */
6054         if (currentMove >= forwardMostMove) {
6055           gameMode = PlayFromGameFile;
6056           ModeHighlight();
6057           SetTrainingModeOff();
6058           DisplayInformation(_("End of game"));
6059         }
6060       } else {
6061         DisplayError(_("Incorrect move"), 0);
6062       }
6063       return 1;
6064     }
6065
6066   /* Ok, now we know that the move is good, so we can kill
6067      the previous line in Analysis Mode */
6068   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6069                                 && currentMove < forwardMostMove) {
6070     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6071   }
6072
6073   /* If we need the chess program but it's dead, restart it */
6074   ResurrectChessProgram();
6075
6076   /* A user move restarts a paused game*/
6077   if (pausing)
6078     PauseEvent();
6079
6080   thinkOutput[0] = NULLCHAR;
6081
6082   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6083
6084   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6085
6086   if (gameMode == BeginningOfGame) {
6087     if (appData.noChessProgram) {
6088       gameMode = EditGame;
6089       SetGameInfo();
6090     } else {
6091       char buf[MSG_SIZ];
6092       gameMode = MachinePlaysBlack;
6093       StartClocks();
6094       SetGameInfo();
6095       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6096       DisplayTitle(buf);
6097       if (first.sendName) {
6098         sprintf(buf, "name %s\n", gameInfo.white);
6099         SendToProgram(buf, &first);
6100       }
6101       StartClocks();
6102     }
6103     ModeHighlight();
6104   }
6105
6106   /* Relay move to ICS or chess engine */
6107   if (appData.icsActive) {
6108     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6109         gameMode == IcsExamining) {
6110       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6111         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6112         SendToICS("draw ");
6113         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6114       }
6115       // also send plain move, in case ICS does not understand atomic claims
6116       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6117       ics_user_moved = 1;
6118     }
6119   } else {
6120     if (first.sendTime && (gameMode == BeginningOfGame ||
6121                            gameMode == MachinePlaysWhite ||
6122                            gameMode == MachinePlaysBlack)) {
6123       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6124     }
6125     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6126          // [HGM] book: if program might be playing, let it use book
6127         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6128         first.maybeThinking = TRUE;
6129     } else SendMoveToProgram(forwardMostMove-1, &first);
6130     if (currentMove == cmailOldMove + 1) {
6131       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6132     }
6133   }
6134
6135   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6136
6137   switch (gameMode) {
6138   case EditGame:
6139     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6140     case MT_NONE:
6141     case MT_CHECK:
6142       break;
6143     case MT_CHECKMATE:
6144     case MT_STAINMATE:
6145       if (WhiteOnMove(currentMove)) {
6146         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6147       } else {
6148         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6149       }
6150       break;
6151     case MT_STALEMATE:
6152       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6153       break;
6154     }
6155     break;
6156     
6157   case MachinePlaysBlack:
6158   case MachinePlaysWhite:
6159     /* disable certain menu options while machine is thinking */
6160     SetMachineThinkingEnables();
6161     break;
6162
6163   default:
6164     break;
6165   }
6166
6167   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6168         
6169   if(bookHit) { // [HGM] book: simulate book reply
6170         static char bookMove[MSG_SIZ]; // a bit generous?
6171
6172         programStats.nodes = programStats.depth = programStats.time = 
6173         programStats.score = programStats.got_only_move = 0;
6174         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6175
6176         strcpy(bookMove, "move ");
6177         strcat(bookMove, bookHit);
6178         HandleMachineMove(bookMove, &first);
6179   }
6180   return 1;
6181 }
6182
6183 void
6184 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6185      int fromX, fromY, toX, toY;
6186      int promoChar;
6187 {
6188     /* [HGM] This routine was added to allow calling of its two logical
6189        parts from other modules in the old way. Before, UserMoveEvent()
6190        automatically called FinishMove() if the move was OK, and returned
6191        otherwise. I separated the two, in order to make it possible to
6192        slip a promotion popup in between. But that it always needs two
6193        calls, to the first part, (now called UserMoveTest() ), and to
6194        FinishMove if the first part succeeded. Calls that do not need
6195        to do anything in between, can call this routine the old way. 
6196     */
6197     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6198 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6199     if(moveType == AmbiguousMove)
6200         DrawPosition(FALSE, boards[currentMove]);
6201     else if(moveType != ImpossibleMove && moveType != Comment)
6202         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6203 }
6204
6205 void
6206 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6207      Board board;
6208      int flags;
6209      ChessMove kind;
6210      int rf, ff, rt, ft;
6211      VOIDSTAR closure;
6212 {
6213     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6214     Markers *m = (Markers *) closure;
6215     if(rf == fromY && ff == fromX)
6216         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6217                          || kind == WhiteCapturesEnPassant
6218                          || kind == BlackCapturesEnPassant);
6219     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6220 }
6221
6222 void
6223 MarkTargetSquares(int clear)
6224 {
6225   int x, y;
6226   if(!appData.markers || !appData.highlightDragging || 
6227      !appData.testLegality || gameMode == EditPosition) return;
6228   if(clear) {
6229     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6230   } else {
6231     int capt = 0;
6232     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6233     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6234       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6235       if(capt)
6236       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6237     }
6238   }
6239   DrawPosition(TRUE, NULL);
6240 }
6241
6242 void LeftClick(ClickType clickType, int xPix, int yPix)
6243 {
6244     int x, y;
6245     Boolean saveAnimate;
6246     static int second = 0, promotionChoice = 0;
6247     char promoChoice = NULLCHAR;
6248
6249     if(appData.seekGraph && appData.icsActive && loggedOn &&
6250         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6251         SeekGraphClick(clickType, xPix, yPix, 0);
6252         return;
6253     }
6254
6255     if (clickType == Press) ErrorPopDown();
6256     MarkTargetSquares(1);
6257
6258     x = EventToSquare(xPix, BOARD_WIDTH);
6259     y = EventToSquare(yPix, BOARD_HEIGHT);
6260     if (!flipView && y >= 0) {
6261         y = BOARD_HEIGHT - 1 - y;
6262     }
6263     if (flipView && x >= 0) {
6264         x = BOARD_WIDTH - 1 - x;
6265     }
6266
6267     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6268         if(clickType == Release) return; // ignore upclick of click-click destination
6269         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6270         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6271         if(gameInfo.holdingsWidth && 
6272                 (WhiteOnMove(currentMove) 
6273                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6274                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6275             // click in right holdings, for determining promotion piece
6276             ChessSquare p = boards[currentMove][y][x];
6277             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6278             if(p != EmptySquare) {
6279                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6280                 fromX = fromY = -1;
6281                 return;
6282             }
6283         }
6284         DrawPosition(FALSE, boards[currentMove]);
6285         return;
6286     }
6287
6288     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6289     if(clickType == Press
6290             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6291               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6292               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6293         return;
6294
6295     autoQueen = appData.alwaysPromoteToQueen;
6296
6297     if (fromX == -1) {
6298       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6299         if (clickType == Press) {
6300             /* First square */
6301             if (OKToStartUserMove(x, y)) {
6302                 fromX = x;
6303                 fromY = y;
6304                 second = 0;
6305                 MarkTargetSquares(0);
6306                 DragPieceBegin(xPix, yPix);
6307                 if (appData.highlightDragging) {
6308                     SetHighlights(x, y, -1, -1);
6309                 }
6310             }
6311         }
6312         return;
6313       }
6314     }
6315
6316     /* fromX != -1 */
6317     if (clickType == Press && gameMode != EditPosition) {
6318         ChessSquare fromP;
6319         ChessSquare toP;
6320         int frc;
6321
6322         // ignore off-board to clicks
6323         if(y < 0 || x < 0) return;
6324
6325         /* Check if clicking again on the same color piece */
6326         fromP = boards[currentMove][fromY][fromX];
6327         toP = boards[currentMove][y][x];
6328         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6329         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6330              WhitePawn <= toP && toP <= WhiteKing &&
6331              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6332              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6333             (BlackPawn <= fromP && fromP <= BlackKing && 
6334              BlackPawn <= toP && toP <= BlackKing &&
6335              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6336              !(fromP == BlackKing && toP == BlackRook && frc))) {
6337             /* Clicked again on same color piece -- changed his mind */
6338             second = (x == fromX && y == fromY);
6339            if(!second || !OnlyMove(&x, &y, TRUE)) {
6340             if (appData.highlightDragging) {
6341                 SetHighlights(x, y, -1, -1);
6342             } else {
6343                 ClearHighlights();
6344             }
6345             if (OKToStartUserMove(x, y)) {
6346                 fromX = x;
6347                 fromY = y;
6348                 MarkTargetSquares(0);
6349                 DragPieceBegin(xPix, yPix);
6350             }
6351             return;
6352            }
6353         }
6354         // ignore clicks on holdings
6355         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6356     }
6357
6358     if (clickType == Release && x == fromX && y == fromY) {
6359         DragPieceEnd(xPix, yPix);
6360         if (appData.animateDragging) {
6361             /* Undo animation damage if any */
6362             DrawPosition(FALSE, NULL);
6363         }
6364         if (second) {
6365             /* Second up/down in same square; just abort move */
6366             second = 0;
6367             fromX = fromY = -1;
6368             ClearHighlights();
6369             gotPremove = 0;
6370             ClearPremoveHighlights();
6371         } else {
6372             /* First upclick in same square; start click-click mode */
6373             SetHighlights(x, y, -1, -1);
6374         }
6375         return;
6376     }
6377
6378     /* we now have a different from- and (possibly off-board) to-square */
6379     /* Completed move */
6380     toX = x;
6381     toY = y;
6382     saveAnimate = appData.animate;
6383     if (clickType == Press) {
6384         /* Finish clickclick move */
6385         if (appData.animate || appData.highlightLastMove) {
6386             SetHighlights(fromX, fromY, toX, toY);
6387         } else {
6388             ClearHighlights();
6389         }
6390     } else {
6391         /* Finish drag move */
6392         if (appData.highlightLastMove) {
6393             SetHighlights(fromX, fromY, toX, toY);
6394         } else {
6395             ClearHighlights();
6396         }
6397         DragPieceEnd(xPix, yPix);
6398         /* Don't animate move and drag both */
6399         appData.animate = FALSE;
6400     }
6401
6402     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6403     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6404         ChessSquare piece = boards[currentMove][fromY][fromX];
6405         if(gameMode == EditPosition && piece != EmptySquare &&
6406            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6407             int n;
6408              
6409             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6410                 n = PieceToNumber(piece - (int)BlackPawn);
6411                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6412                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6413                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6414             } else
6415             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6416                 n = PieceToNumber(piece);
6417                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6418                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6419                 boards[currentMove][n][BOARD_WIDTH-2]++;
6420             }
6421             boards[currentMove][fromY][fromX] = EmptySquare;
6422         }
6423         ClearHighlights();
6424         fromX = fromY = -1;
6425         DrawPosition(TRUE, boards[currentMove]);
6426         return;
6427     }
6428
6429     // off-board moves should not be highlighted
6430     if(x < 0 || x < 0) ClearHighlights();
6431
6432     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6433         SetHighlights(fromX, fromY, toX, toY);
6434         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6435             // [HGM] super: promotion to captured piece selected from holdings
6436             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6437             promotionChoice = TRUE;
6438             // kludge follows to temporarily execute move on display, without promoting yet
6439             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6440             boards[currentMove][toY][toX] = p;
6441             DrawPosition(FALSE, boards[currentMove]);
6442             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6443             boards[currentMove][toY][toX] = q;
6444             DisplayMessage("Click in holdings to choose piece", "");
6445             return;
6446         }
6447         PromotionPopUp();
6448     } else {
6449         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6450         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6451         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6452         fromX = fromY = -1;
6453     }
6454     appData.animate = saveAnimate;
6455     if (appData.animate || appData.animateDragging) {
6456         /* Undo animation damage if needed */
6457         DrawPosition(FALSE, NULL);
6458     }
6459 }
6460
6461 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6462 {   // front-end-free part taken out of PieceMenuPopup
6463     int whichMenu; int xSqr, ySqr;
6464
6465     if(seekGraphUp) { // [HGM] seekgraph
6466         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6467         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6468         return -2;
6469     }
6470
6471     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6472          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6473         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6474         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6475         if(action == Press)   {
6476             originalFlip = flipView;
6477             flipView = !flipView; // temporarily flip board to see game from partners perspective
6478             DrawPosition(TRUE, partnerBoard);
6479             DisplayMessage(partnerStatus, "");
6480             partnerUp = TRUE;
6481         } else if(action == Release) {
6482             flipView = originalFlip;
6483             DrawPosition(TRUE, boards[currentMove]);
6484             partnerUp = FALSE;
6485         }
6486         return -2;
6487     }
6488
6489     xSqr = EventToSquare(x, BOARD_WIDTH);
6490     ySqr = EventToSquare(y, BOARD_HEIGHT);
6491     if (action == Release) UnLoadPV(); // [HGM] pv
6492     if (action != Press) return -2; // return code to be ignored
6493     switch (gameMode) {
6494       case IcsExamining:
6495         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6496       case EditPosition:
6497         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6498         if (xSqr < 0 || ySqr < 0) return -1;\r
6499         whichMenu = 0; // edit-position menu
6500         break;
6501       case IcsObserving:
6502         if(!appData.icsEngineAnalyze) return -1;
6503       case IcsPlayingWhite:
6504       case IcsPlayingBlack:
6505         if(!appData.zippyPlay) goto noZip;
6506       case AnalyzeMode:
6507       case AnalyzeFile:
6508       case MachinePlaysWhite:
6509       case MachinePlaysBlack:
6510       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6511         if (!appData.dropMenu) {
6512           LoadPV(x, y);
6513           return 2; // flag front-end to grab mouse events
6514         }
6515         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6516            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6517       case EditGame:
6518       noZip:
6519         if (xSqr < 0 || ySqr < 0) return -1;
6520         if (!appData.dropMenu || appData.testLegality &&
6521             gameInfo.variant != VariantBughouse &&
6522             gameInfo.variant != VariantCrazyhouse) return -1;
6523         whichMenu = 1; // drop menu
6524         break;
6525       default:
6526         return -1;
6527     }
6528
6529     if (((*fromX = xSqr) < 0) ||
6530         ((*fromY = ySqr) < 0)) {
6531         *fromX = *fromY = -1;
6532         return -1;
6533     }
6534     if (flipView)
6535       *fromX = BOARD_WIDTH - 1 - *fromX;
6536     else
6537       *fromY = BOARD_HEIGHT - 1 - *fromY;
6538
6539     return whichMenu;
6540 }
6541
6542 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6543 {
6544 //    char * hint = lastHint;
6545     FrontEndProgramStats stats;
6546
6547     stats.which = cps == &first ? 0 : 1;
6548     stats.depth = cpstats->depth;
6549     stats.nodes = cpstats->nodes;
6550     stats.score = cpstats->score;
6551     stats.time = cpstats->time;
6552     stats.pv = cpstats->movelist;
6553     stats.hint = lastHint;
6554     stats.an_move_index = 0;
6555     stats.an_move_count = 0;
6556
6557     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6558         stats.hint = cpstats->move_name;
6559         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6560         stats.an_move_count = cpstats->nr_moves;
6561     }
6562
6563     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6564
6565     SetProgramStats( &stats );
6566 }
6567
6568 int
6569 Adjudicate(ChessProgramState *cps)
6570 {       // [HGM] some adjudications useful with buggy engines
6571         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6572         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6573         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6574         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6575         int k, count = 0; static int bare = 1;
6576         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6577         Boolean canAdjudicate = !appData.icsActive;
6578
6579         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6580         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6581             if( appData.testLegality )
6582             {   /* [HGM] Some more adjudications for obstinate engines */
6583                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6584                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6585                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6586                 static int moveCount = 6;
6587                 ChessMove result;
6588                 char *reason = NULL;
6589
6590                 /* Count what is on board. */
6591                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6592                 {   ChessSquare p = boards[forwardMostMove][i][j];
6593                     int m=i;
6594
6595                     switch((int) p)
6596                     {   /* count B,N,R and other of each side */
6597                         case WhiteKing:
6598                         case BlackKing:
6599                              NrK++; break; // [HGM] atomic: count Kings
6600                         case WhiteKnight:
6601                              NrWN++; break;
6602                         case WhiteBishop:
6603                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6604                              bishopsColor |= 1 << ((i^j)&1);
6605                              NrWB++; break;
6606                         case BlackKnight:
6607                              NrBN++; break;
6608                         case BlackBishop:
6609                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6610                              bishopsColor |= 1 << ((i^j)&1);
6611                              NrBB++; break;
6612                         case WhiteRook:
6613                              NrWR++; break;
6614                         case BlackRook:
6615                              NrBR++; break;
6616                         case WhiteQueen:
6617                              NrWQ++; break;
6618                         case BlackQueen:
6619                              NrBQ++; break;
6620                         case EmptySquare: 
6621                              break;
6622                         case BlackPawn:
6623                              m = 7-i;
6624                         case WhitePawn:
6625                              PawnAdvance += m; NrPawns++;
6626                     }
6627                     NrPieces += (p != EmptySquare);
6628                     NrW += ((int)p < (int)BlackPawn);
6629                     if(gameInfo.variant == VariantXiangqi && 
6630                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6631                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6632                         NrW -= ((int)p < (int)BlackPawn);
6633                     }
6634                 }
6635
6636                 /* Some material-based adjudications that have to be made before stalemate test */
6637                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6638                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6639                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6640                      if(canAdjudicate && appData.checkMates) {
6641                          if(engineOpponent)
6642                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6643                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6644                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6645                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6646                          return 1;
6647                      }
6648                 }
6649
6650                 /* Bare King in Shatranj (loses) or Losers (wins) */
6651                 if( NrW == 1 || NrPieces - NrW == 1) {
6652                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6653                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6654                      if(canAdjudicate && appData.checkMates) {
6655                          if(engineOpponent)
6656                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6657                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6658                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6659                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6660                          return 1;
6661                      }
6662                   } else
6663                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6664                   {    /* bare King */
6665                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6666                         if(canAdjudicate && appData.checkMates) {
6667                             /* but only adjudicate if adjudication enabled */
6668                             if(engineOpponent)
6669                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6670                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6671                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6672                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6673                             return 1;
6674                         }
6675                   }
6676                 } else bare = 1;
6677
6678
6679             // don't wait for engine to announce game end if we can judge ourselves
6680             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6681               case MT_CHECK:
6682                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6683                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6684                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6685                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6686                             checkCnt++;
6687                         if(checkCnt >= 2) {
6688                             reason = "Xboard adjudication: 3rd check";
6689                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6690                             break;
6691                         }
6692                     }
6693                 }
6694               case MT_NONE:
6695               default:
6696                 break;
6697               case MT_STALEMATE:
6698               case MT_STAINMATE:
6699                 reason = "Xboard adjudication: Stalemate";
6700                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6701                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6702                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6703                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6704                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6705                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6706                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6707                                                                         EP_CHECKMATE : EP_WINS);
6708                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6709                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6710                 }
6711                 break;
6712               case MT_CHECKMATE:
6713                 reason = "Xboard adjudication: Checkmate";
6714                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6715                 break;
6716             }
6717
6718                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6719                     case EP_STALEMATE:
6720                         result = GameIsDrawn; break;
6721                     case EP_CHECKMATE:
6722                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6723                     case EP_WINS:
6724                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6725                     default:
6726                         result = (ChessMove) 0;
6727                 }
6728                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6729                     if(engineOpponent)
6730                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6731                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6732                     GameEnds( result, reason, GE_XBOARD );
6733                     return 1;
6734                 }
6735
6736                 /* Next absolutely insufficient mating material. */
6737                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6738                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6739                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6740                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6741                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6742
6743                      /* always flag draws, for judging claims */
6744                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6745
6746                      if(canAdjudicate && appData.materialDraws) {
6747                          /* but only adjudicate them if adjudication enabled */
6748                          if(engineOpponent) {
6749                            SendToProgram("force\n", engineOpponent); // suppress reply
6750                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6751                          }
6752                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6753                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6754                          return 1;
6755                      }
6756                 }
6757
6758                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6759                 if(NrPieces == 4 && 
6760                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6761                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6762                    || NrWN==2 || NrBN==2     /* KNNK */
6763                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6764                   ) ) {
6765                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6766                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6767                           if(engineOpponent) {
6768                             SendToProgram("force\n", engineOpponent); // suppress reply
6769                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6770                           }
6771                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6772                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6773                           return 1;
6774                      }
6775                 } else moveCount = 6;
6776             }
6777         }
6778           
6779         if (appData.debugMode) { int i;
6780             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6781                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6782                     appData.drawRepeats);
6783             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6784               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6785             
6786         }
6787
6788         // Repetition draws and 50-move rule can be applied independently of legality testing
6789
6790                 /* Check for rep-draws */
6791                 count = 0;
6792                 for(k = forwardMostMove-2;
6793                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6794                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6795                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6796                     k-=2)
6797                 {   int rights=0;
6798                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6799                         /* compare castling rights */
6800                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6801                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6802                                 rights++; /* King lost rights, while rook still had them */
6803                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6804                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6805                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6806                                    rights++; /* but at least one rook lost them */
6807                         }
6808                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6809                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6810                                 rights++; 
6811                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6812                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6813                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6814                                    rights++;
6815                         }
6816                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6817                             && appData.drawRepeats > 1) {
6818                              /* adjudicate after user-specified nr of repeats */
6819                              if(engineOpponent) {
6820                                SendToProgram("force\n", engineOpponent); // suppress reply
6821                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6822                              }
6823                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6824                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6825                                 // [HGM] xiangqi: check for forbidden perpetuals
6826                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6827                                 for(m=forwardMostMove; m>k; m-=2) {
6828                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6829                                         ourPerpetual = 0; // the current mover did not always check
6830                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6831                                         hisPerpetual = 0; // the opponent did not always check
6832                                 }
6833                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6834                                                                         ourPerpetual, hisPerpetual);
6835                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6836                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6837                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6838                                     return 1;
6839                                 }
6840                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6841                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6842                                 // Now check for perpetual chases
6843                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6844                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6845                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6846                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6847                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6848                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6849                                         return 1;
6850                                     }
6851                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6852                                         break; // Abort repetition-checking loop.
6853                                 }
6854                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6855                              }
6856                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6857                              return 1;
6858                         }
6859                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6860                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6861                     }
6862                 }
6863
6864                 /* Now we test for 50-move draws. Determine ply count */
6865                 count = forwardMostMove;
6866                 /* look for last irreversble move */
6867                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6868                     count--;
6869                 /* if we hit starting position, add initial plies */
6870                 if( count == backwardMostMove )
6871                     count -= initialRulePlies;
6872                 count = forwardMostMove - count; 
6873                 if( count >= 100)
6874                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6875                          /* this is used to judge if draw claims are legal */
6876                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6877                          if(engineOpponent) {
6878                            SendToProgram("force\n", engineOpponent); // suppress reply
6879                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6880                          }
6881                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6883                          return 1;
6884                 }
6885
6886                 /* if draw offer is pending, treat it as a draw claim
6887                  * when draw condition present, to allow engines a way to
6888                  * claim draws before making their move to avoid a race
6889                  * condition occurring after their move
6890                  */
6891                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6892                          char *p = NULL;
6893                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6894                              p = "Draw claim: 50-move rule";
6895                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6896                              p = "Draw claim: 3-fold repetition";
6897                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6898                              p = "Draw claim: insufficient mating material";
6899                          if( p != NULL && canAdjudicate) {
6900                              if(engineOpponent) {
6901                                SendToProgram("force\n", engineOpponent); // suppress reply
6902                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                              }
6904                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6905                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6906                              return 1;
6907                          }
6908                 }
6909
6910                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6911                     if(engineOpponent) {
6912                       SendToProgram("force\n", engineOpponent); // suppress reply
6913                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6914                     }
6915                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6916                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6917                     return 1;
6918                 }
6919         return 0;
6920 }
6921
6922 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6923 {   // [HGM] book: this routine intercepts moves to simulate book replies
6924     char *bookHit = NULL;
6925
6926     //first determine if the incoming move brings opponent into his book
6927     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6928         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6929     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6930     if(bookHit != NULL && !cps->bookSuspend) {
6931         // make sure opponent is not going to reply after receiving move to book position
6932         SendToProgram("force\n", cps);
6933         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6934     }
6935     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6936     // now arrange restart after book miss
6937     if(bookHit) {
6938         // after a book hit we never send 'go', and the code after the call to this routine
6939         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6940         char buf[MSG_SIZ];
6941         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6942         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6943         SendToProgram(buf, cps);
6944         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6945     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6946         SendToProgram("go\n", cps);
6947         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6948     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6949         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6950             SendToProgram("go\n", cps); 
6951         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6952     }
6953     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6954 }
6955
6956 char *savedMessage;
6957 ChessProgramState *savedState;
6958 void DeferredBookMove(void)
6959 {
6960         if(savedState->lastPing != savedState->lastPong)
6961                     ScheduleDelayedEvent(DeferredBookMove, 10);
6962         else
6963         HandleMachineMove(savedMessage, savedState);
6964 }
6965
6966 void
6967 HandleMachineMove(message, cps)
6968      char *message;
6969      ChessProgramState *cps;
6970 {
6971     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6972     char realname[MSG_SIZ];
6973     int fromX, fromY, toX, toY;
6974     ChessMove moveType;
6975     char promoChar;
6976     char *p;
6977     int machineWhite;
6978     char *bookHit;
6979
6980     cps->userError = 0;
6981
6982 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6983     /*
6984      * Kludge to ignore BEL characters
6985      */
6986     while (*message == '\007') message++;
6987
6988     /*
6989      * [HGM] engine debug message: ignore lines starting with '#' character
6990      */
6991     if(cps->debug && *message == '#') return;
6992
6993     /*
6994      * Look for book output
6995      */
6996     if (cps == &first && bookRequested) {
6997         if (message[0] == '\t' || message[0] == ' ') {
6998             /* Part of the book output is here; append it */
6999             strcat(bookOutput, message);
7000             strcat(bookOutput, "  \n");
7001             return;
7002         } else if (bookOutput[0] != NULLCHAR) {
7003             /* All of book output has arrived; display it */
7004             char *p = bookOutput;
7005             while (*p != NULLCHAR) {
7006                 if (*p == '\t') *p = ' ';
7007                 p++;
7008             }
7009             DisplayInformation(bookOutput);
7010             bookRequested = FALSE;
7011             /* Fall through to parse the current output */
7012         }
7013     }
7014
7015     /*
7016      * Look for machine move.
7017      */
7018     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7019         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7020     {
7021         /* This method is only useful on engines that support ping */
7022         if (cps->lastPing != cps->lastPong) {
7023           if (gameMode == BeginningOfGame) {
7024             /* Extra move from before last new; ignore */
7025             if (appData.debugMode) {
7026                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7027             }
7028           } else {
7029             if (appData.debugMode) {
7030                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7031                         cps->which, gameMode);
7032             }
7033
7034             SendToProgram("undo\n", cps);
7035           }
7036           return;
7037         }
7038
7039         switch (gameMode) {
7040           case BeginningOfGame:
7041             /* Extra move from before last reset; ignore */
7042             if (appData.debugMode) {
7043                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7044             }
7045             return;
7046
7047           case EndOfGame:
7048           case IcsIdle:
7049           default:
7050             /* Extra move after we tried to stop.  The mode test is
7051                not a reliable way of detecting this problem, but it's
7052                the best we can do on engines that don't support ping.
7053             */
7054             if (appData.debugMode) {
7055                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7056                         cps->which, gameMode);
7057             }
7058             SendToProgram("undo\n", cps);
7059             return;
7060
7061           case MachinePlaysWhite:
7062           case IcsPlayingWhite:
7063             machineWhite = TRUE;
7064             break;
7065
7066           case MachinePlaysBlack:
7067           case IcsPlayingBlack:
7068             machineWhite = FALSE;
7069             break;
7070
7071           case TwoMachinesPlay:
7072             machineWhite = (cps->twoMachinesColor[0] == 'w');
7073             break;
7074         }
7075         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7076             if (appData.debugMode) {
7077                 fprintf(debugFP,
7078                         "Ignoring move out of turn by %s, gameMode %d"
7079                         ", forwardMost %d\n",
7080                         cps->which, gameMode, forwardMostMove);
7081             }
7082             return;
7083         }
7084
7085     if (appData.debugMode) { int f = forwardMostMove;
7086         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7087                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7088                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7089     }
7090         if(cps->alphaRank) AlphaRank(machineMove, 4);
7091         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7092                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7093             /* Machine move could not be parsed; ignore it. */
7094             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7095                     machineMove, cps->which);
7096             DisplayError(buf1, 0);
7097             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7098                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7099             if (gameMode == TwoMachinesPlay) {
7100               GameEnds(machineWhite ? BlackWins : WhiteWins,
7101                        buf1, GE_XBOARD);
7102             }
7103             return;
7104         }
7105
7106         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7107         /* So we have to redo legality test with true e.p. status here,  */
7108         /* to make sure an illegal e.p. capture does not slip through,   */
7109         /* to cause a forfeit on a justified illegal-move complaint      */
7110         /* of the opponent.                                              */
7111         if( gameMode==TwoMachinesPlay && appData.testLegality
7112             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7113                                                               ) {
7114            ChessMove moveType;
7115            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7116                              fromY, fromX, toY, toX, promoChar);
7117             if (appData.debugMode) {
7118                 int i;
7119                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7120                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7121                 fprintf(debugFP, "castling rights\n");
7122             }
7123             if(moveType == IllegalMove) {
7124                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7125                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7126                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7127                            buf1, GE_XBOARD);
7128                 return;
7129            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7130            /* [HGM] Kludge to handle engines that send FRC-style castling
7131               when they shouldn't (like TSCP-Gothic) */
7132            switch(moveType) {
7133              case WhiteASideCastleFR:
7134              case BlackASideCastleFR:
7135                toX+=2;
7136                currentMoveString[2]++;
7137                break;
7138              case WhiteHSideCastleFR:
7139              case BlackHSideCastleFR:
7140                toX--;
7141                currentMoveString[2]--;
7142                break;
7143              default: ; // nothing to do, but suppresses warning of pedantic compilers
7144            }
7145         }
7146         hintRequested = FALSE;
7147         lastHint[0] = NULLCHAR;
7148         bookRequested = FALSE;
7149         /* Program may be pondering now */
7150         cps->maybeThinking = TRUE;
7151         if (cps->sendTime == 2) cps->sendTime = 1;
7152         if (cps->offeredDraw) cps->offeredDraw--;
7153
7154         /* currentMoveString is set as a side-effect of ParseOneMove */
7155         strcpy(machineMove, currentMoveString);
7156         strcat(machineMove, "\n");
7157         strcpy(moveList[forwardMostMove], machineMove);
7158
7159         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7160
7161         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7162         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7163             int count = 0;
7164
7165             while( count < adjudicateLossPlies ) {
7166                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7167
7168                 if( count & 1 ) {
7169                     score = -score; /* Flip score for winning side */
7170                 }
7171
7172                 if( score > adjudicateLossThreshold ) {
7173                     break;
7174                 }
7175
7176                 count++;
7177             }
7178
7179             if( count >= adjudicateLossPlies ) {
7180                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7181
7182                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7183                     "Xboard adjudication", 
7184                     GE_XBOARD );
7185
7186                 return;
7187             }
7188         }
7189
7190         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7191
7192 #if ZIPPY
7193         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7194             first.initDone) {
7195           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7196                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7197                 SendToICS("draw ");
7198                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7199           }
7200           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7201           ics_user_moved = 1;
7202           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7203                 char buf[3*MSG_SIZ];
7204
7205                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7206                         programStats.score / 100.,
7207                         programStats.depth,
7208                         programStats.time / 100.,
7209                         (unsigned int)programStats.nodes,
7210                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7211                         programStats.movelist);
7212                 SendToICS(buf);
7213 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7214           }
7215         }
7216 #endif
7217
7218         /* [AS] Save move info and clear stats for next move */
7219         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7220         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7221         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7222         ClearProgramStats();
7223         thinkOutput[0] = NULLCHAR;
7224         hiddenThinkOutputState = 0;
7225
7226         bookHit = NULL;
7227         if (gameMode == TwoMachinesPlay) {
7228             /* [HGM] relaying draw offers moved to after reception of move */
7229             /* and interpreting offer as claim if it brings draw condition */
7230             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7231                 SendToProgram("draw\n", cps->other);
7232             }
7233             if (cps->other->sendTime) {
7234                 SendTimeRemaining(cps->other,
7235                                   cps->other->twoMachinesColor[0] == 'w');
7236             }
7237             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7238             if (firstMove && !bookHit) {
7239                 firstMove = FALSE;
7240                 if (cps->other->useColors) {
7241                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7242                 }
7243                 SendToProgram("go\n", cps->other);
7244             }
7245             cps->other->maybeThinking = TRUE;
7246         }
7247
7248         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7249         
7250         if (!pausing && appData.ringBellAfterMoves) {
7251             RingBell();
7252         }
7253
7254         /* 
7255          * Reenable menu items that were disabled while
7256          * machine was thinking
7257          */
7258         if (gameMode != TwoMachinesPlay)
7259             SetUserThinkingEnables();
7260
7261         // [HGM] book: after book hit opponent has received move and is now in force mode
7262         // force the book reply into it, and then fake that it outputted this move by jumping
7263         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7264         if(bookHit) {
7265                 static char bookMove[MSG_SIZ]; // a bit generous?
7266
7267                 strcpy(bookMove, "move ");
7268                 strcat(bookMove, bookHit);
7269                 message = bookMove;
7270                 cps = cps->other;
7271                 programStats.nodes = programStats.depth = programStats.time = 
7272                 programStats.score = programStats.got_only_move = 0;
7273                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7274
7275                 if(cps->lastPing != cps->lastPong) {
7276                     savedMessage = message; // args for deferred call
7277                     savedState = cps;
7278                     ScheduleDelayedEvent(DeferredBookMove, 10);
7279                     return;
7280                 }
7281                 goto FakeBookMove;
7282         }
7283
7284         return;
7285     }
7286
7287     /* Set special modes for chess engines.  Later something general
7288      *  could be added here; for now there is just one kludge feature,
7289      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7290      *  when "xboard" is given as an interactive command.
7291      */
7292     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7293         cps->useSigint = FALSE;
7294         cps->useSigterm = FALSE;
7295     }
7296     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7297       ParseFeatures(message+8, cps);
7298       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7299     }
7300
7301     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7302      * want this, I was asked to put it in, and obliged.
7303      */
7304     if (!strncmp(message, "setboard ", 9)) {
7305         Board initial_position;
7306
7307         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7308
7309         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7310             DisplayError(_("Bad FEN received from engine"), 0);
7311             return ;
7312         } else {
7313            Reset(TRUE, FALSE);
7314            CopyBoard(boards[0], initial_position);
7315            initialRulePlies = FENrulePlies;
7316            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7317            else gameMode = MachinePlaysBlack;                 
7318            DrawPosition(FALSE, boards[currentMove]);
7319         }
7320         return;
7321     }
7322
7323     /*
7324      * Look for communication commands
7325      */
7326     if (!strncmp(message, "telluser ", 9)) {
7327         DisplayNote(message + 9);
7328         return;
7329     }
7330     if (!strncmp(message, "tellusererror ", 14)) {
7331         cps->userError = 1;
7332         DisplayError(message + 14, 0);
7333         return;
7334     }
7335     if (!strncmp(message, "tellopponent ", 13)) {
7336       if (appData.icsActive) {
7337         if (loggedOn) {
7338           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7339           SendToICS(buf1);
7340         }
7341       } else {
7342         DisplayNote(message + 13);
7343       }
7344       return;
7345     }
7346     if (!strncmp(message, "tellothers ", 11)) {
7347       if (appData.icsActive) {
7348         if (loggedOn) {
7349           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7350           SendToICS(buf1);
7351         }
7352       }
7353       return;
7354     }
7355     if (!strncmp(message, "tellall ", 8)) {
7356       if (appData.icsActive) {
7357         if (loggedOn) {
7358           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7359           SendToICS(buf1);
7360         }
7361       } else {
7362         DisplayNote(message + 8);
7363       }
7364       return;
7365     }
7366     if (strncmp(message, "warning", 7) == 0) {
7367         /* Undocumented feature, use tellusererror in new code */
7368         DisplayError(message, 0);
7369         return;
7370     }
7371     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7372         strcpy(realname, cps->tidy);
7373         strcat(realname, " query");
7374         AskQuestion(realname, buf2, buf1, cps->pr);
7375         return;
7376     }
7377     /* Commands from the engine directly to ICS.  We don't allow these to be 
7378      *  sent until we are logged on. Crafty kibitzes have been known to 
7379      *  interfere with the login process.
7380      */
7381     if (loggedOn) {
7382         if (!strncmp(message, "tellics ", 8)) {
7383             SendToICS(message + 8);
7384             SendToICS("\n");
7385             return;
7386         }
7387         if (!strncmp(message, "tellicsnoalias ", 15)) {
7388             SendToICS(ics_prefix);
7389             SendToICS(message + 15);
7390             SendToICS("\n");
7391             return;
7392         }
7393         /* The following are for backward compatibility only */
7394         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7395             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7396             SendToICS(ics_prefix);
7397             SendToICS(message);
7398             SendToICS("\n");
7399             return;
7400         }
7401     }
7402     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7403         return;
7404     }
7405     /*
7406      * If the move is illegal, cancel it and redraw the board.
7407      * Also deal with other error cases.  Matching is rather loose
7408      * here to accommodate engines written before the spec.
7409      */
7410     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7411         strncmp(message, "Error", 5) == 0) {
7412         if (StrStr(message, "name") || 
7413             StrStr(message, "rating") || StrStr(message, "?") ||
7414             StrStr(message, "result") || StrStr(message, "board") ||
7415             StrStr(message, "bk") || StrStr(message, "computer") ||
7416             StrStr(message, "variant") || StrStr(message, "hint") ||
7417             StrStr(message, "random") || StrStr(message, "depth") ||
7418             StrStr(message, "accepted")) {
7419             return;
7420         }
7421         if (StrStr(message, "protover")) {
7422           /* Program is responding to input, so it's apparently done
7423              initializing, and this error message indicates it is
7424              protocol version 1.  So we don't need to wait any longer
7425              for it to initialize and send feature commands. */
7426           FeatureDone(cps, 1);
7427           cps->protocolVersion = 1;
7428           return;
7429         }
7430         cps->maybeThinking = FALSE;
7431
7432         if (StrStr(message, "draw")) {
7433             /* Program doesn't have "draw" command */
7434             cps->sendDrawOffers = 0;
7435             return;
7436         }
7437         if (cps->sendTime != 1 &&
7438             (StrStr(message, "time") || StrStr(message, "otim"))) {
7439           /* Program apparently doesn't have "time" or "otim" command */
7440           cps->sendTime = 0;
7441           return;
7442         }
7443         if (StrStr(message, "analyze")) {
7444             cps->analysisSupport = FALSE;
7445             cps->analyzing = FALSE;
7446             Reset(FALSE, TRUE);
7447             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7448             DisplayError(buf2, 0);
7449             return;
7450         }
7451         if (StrStr(message, "(no matching move)st")) {
7452           /* Special kludge for GNU Chess 4 only */
7453           cps->stKludge = TRUE;
7454           SendTimeControl(cps, movesPerSession, timeControl,
7455                           timeIncrement, appData.searchDepth,
7456                           searchTime);
7457           return;
7458         }
7459         if (StrStr(message, "(no matching move)sd")) {
7460           /* Special kludge for GNU Chess 4 only */
7461           cps->sdKludge = TRUE;
7462           SendTimeControl(cps, movesPerSession, timeControl,
7463                           timeIncrement, appData.searchDepth,
7464                           searchTime);
7465           return;
7466         }
7467         if (!StrStr(message, "llegal")) {
7468             return;
7469         }
7470         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7471             gameMode == IcsIdle) return;
7472         if (forwardMostMove <= backwardMostMove) return;
7473         if (pausing) PauseEvent();
7474       if(appData.forceIllegal) {
7475             // [HGM] illegal: machine refused move; force position after move into it
7476           SendToProgram("force\n", cps);
7477           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7478                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7479                 // when black is to move, while there might be nothing on a2 or black
7480                 // might already have the move. So send the board as if white has the move.
7481                 // But first we must change the stm of the engine, as it refused the last move
7482                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7483                 if(WhiteOnMove(forwardMostMove)) {
7484                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7485                     SendBoard(cps, forwardMostMove); // kludgeless board
7486                 } else {
7487                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7488                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7489                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7490                 }
7491           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7492             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7493                  gameMode == TwoMachinesPlay)
7494               SendToProgram("go\n", cps);
7495             return;
7496       } else
7497         if (gameMode == PlayFromGameFile) {
7498             /* Stop reading this game file */
7499             gameMode = EditGame;
7500             ModeHighlight();
7501         }
7502         currentMove = forwardMostMove-1;
7503         DisplayMove(currentMove-1); /* before DisplayMoveError */
7504         SwitchClocks(forwardMostMove-1); // [HGM] race
7505         DisplayBothClocks();
7506         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7507                 parseList[currentMove], cps->which);
7508         DisplayMoveError(buf1);
7509         DrawPosition(FALSE, boards[currentMove]);
7510
7511         /* [HGM] illegal-move claim should forfeit game when Xboard */
7512         /* only passes fully legal moves                            */
7513         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7514             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7515                                 "False illegal-move claim", GE_XBOARD );
7516         }
7517         return;
7518     }
7519     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7520         /* Program has a broken "time" command that
7521            outputs a string not ending in newline.
7522            Don't use it. */
7523         cps->sendTime = 0;
7524     }
7525     
7526     /*
7527      * If chess program startup fails, exit with an error message.
7528      * Attempts to recover here are futile.
7529      */
7530     if ((StrStr(message, "unknown host") != NULL)
7531         || (StrStr(message, "No remote directory") != NULL)
7532         || (StrStr(message, "not found") != NULL)
7533         || (StrStr(message, "No such file") != NULL)
7534         || (StrStr(message, "can't alloc") != NULL)
7535         || (StrStr(message, "Permission denied") != NULL)) {
7536
7537         cps->maybeThinking = FALSE;
7538         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7539                 cps->which, cps->program, cps->host, message);
7540         RemoveInputSource(cps->isr);
7541         DisplayFatalError(buf1, 0, 1);
7542         return;
7543     }
7544     
7545     /* 
7546      * Look for hint output
7547      */
7548     if (sscanf(message, "Hint: %s", buf1) == 1) {
7549         if (cps == &first && hintRequested) {
7550             hintRequested = FALSE;
7551             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7552                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7553                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7554                                     PosFlags(forwardMostMove),
7555                                     fromY, fromX, toY, toX, promoChar, buf1);
7556                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7557                 DisplayInformation(buf2);
7558             } else {
7559                 /* Hint move could not be parsed!? */
7560               snprintf(buf2, sizeof(buf2),
7561                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7562                         buf1, cps->which);
7563                 DisplayError(buf2, 0);
7564             }
7565         } else {
7566             strcpy(lastHint, buf1);
7567         }
7568         return;
7569     }
7570
7571     /*
7572      * Ignore other messages if game is not in progress
7573      */
7574     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7575         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7576
7577     /*
7578      * look for win, lose, draw, or draw offer
7579      */
7580     if (strncmp(message, "1-0", 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         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7591         return;
7592     } else if (strncmp(message, "0-1", 3) == 0) {
7593         char *p, *q, *r = "";
7594         p = strchr(message, '{');
7595         if (p) {
7596             q = strchr(p, '}');
7597             if (q) {
7598                 *q = NULLCHAR;
7599                 r = p + 1;
7600             }
7601         }
7602         /* Kludge for Arasan 4.1 bug */
7603         if (strcmp(r, "Black resigns") == 0) {
7604             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7605             return;
7606         }
7607         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7608         return;
7609     } else if (strncmp(message, "1/2", 3) == 0) {
7610         char *p, *q, *r = "";
7611         p = strchr(message, '{');
7612         if (p) {
7613             q = strchr(p, '}');
7614             if (q) {
7615                 *q = NULLCHAR;
7616                 r = p + 1;
7617             }
7618         }
7619             
7620         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7621         return;
7622
7623     } else if (strncmp(message, "White resign", 12) == 0) {
7624         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7625         return;
7626     } else if (strncmp(message, "Black resign", 12) == 0) {
7627         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7628         return;
7629     } else if (strncmp(message, "White matches", 13) == 0 ||
7630                strncmp(message, "Black matches", 13) == 0   ) {
7631         /* [HGM] ignore GNUShogi noises */
7632         return;
7633     } else if (strncmp(message, "White", 5) == 0 &&
7634                message[5] != '(' &&
7635                StrStr(message, "Black") == NULL) {
7636         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7637         return;
7638     } else if (strncmp(message, "Black", 5) == 0 &&
7639                message[5] != '(') {
7640         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7641         return;
7642     } else if (strcmp(message, "resign") == 0 ||
7643                strcmp(message, "computer resigns") == 0) {
7644         switch (gameMode) {
7645           case MachinePlaysBlack:
7646           case IcsPlayingBlack:
7647             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7648             break;
7649           case MachinePlaysWhite:
7650           case IcsPlayingWhite:
7651             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7652             break;
7653           case TwoMachinesPlay:
7654             if (cps->twoMachinesColor[0] == 'w')
7655               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7656             else
7657               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7658             break;
7659           default:
7660             /* can't happen */
7661             break;
7662         }
7663         return;
7664     } else if (strncmp(message, "opponent mates", 14) == 0) {
7665         switch (gameMode) {
7666           case MachinePlaysBlack:
7667           case IcsPlayingBlack:
7668             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7669             break;
7670           case MachinePlaysWhite:
7671           case IcsPlayingWhite:
7672             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7673             break;
7674           case TwoMachinesPlay:
7675             if (cps->twoMachinesColor[0] == 'w')
7676               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7677             else
7678               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7679             break;
7680           default:
7681             /* can't happen */
7682             break;
7683         }
7684         return;
7685     } else if (strncmp(message, "computer mates", 14) == 0) {
7686         switch (gameMode) {
7687           case MachinePlaysBlack:
7688           case IcsPlayingBlack:
7689             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7690             break;
7691           case MachinePlaysWhite:
7692           case IcsPlayingWhite:
7693             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7694             break;
7695           case TwoMachinesPlay:
7696             if (cps->twoMachinesColor[0] == 'w')
7697               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7698             else
7699               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7700             break;
7701           default:
7702             /* can't happen */
7703             break;
7704         }
7705         return;
7706     } else if (strncmp(message, "checkmate", 9) == 0) {
7707         if (WhiteOnMove(forwardMostMove)) {
7708             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7709         } else {
7710             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7711         }
7712         return;
7713     } else if (strstr(message, "Draw") != NULL ||
7714                strstr(message, "game is a draw") != NULL) {
7715         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7716         return;
7717     } else if (strstr(message, "offer") != NULL &&
7718                strstr(message, "draw") != NULL) {
7719 #if ZIPPY
7720         if (appData.zippyPlay && first.initDone) {
7721             /* Relay offer to ICS */
7722             SendToICS(ics_prefix);
7723             SendToICS("draw\n");
7724         }
7725 #endif
7726         cps->offeredDraw = 2; /* valid until this engine moves twice */
7727         if (gameMode == TwoMachinesPlay) {
7728             if (cps->other->offeredDraw) {
7729                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7730             /* [HGM] in two-machine mode we delay relaying draw offer      */
7731             /* until after we also have move, to see if it is really claim */
7732             }
7733         } else if (gameMode == MachinePlaysWhite ||
7734                    gameMode == MachinePlaysBlack) {
7735           if (userOfferedDraw) {
7736             DisplayInformation(_("Machine accepts your draw offer"));
7737             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7738           } else {
7739             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7740           }
7741         }
7742     }
7743
7744     
7745     /*
7746      * Look for thinking output
7747      */
7748     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7749           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7750                                 ) {
7751         int plylev, mvleft, mvtot, curscore, time;
7752         char mvname[MOVE_LEN];
7753         u64 nodes; // [DM]
7754         char plyext;
7755         int ignore = FALSE;
7756         int prefixHint = FALSE;
7757         mvname[0] = NULLCHAR;
7758
7759         switch (gameMode) {
7760           case MachinePlaysBlack:
7761           case IcsPlayingBlack:
7762             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7763             break;
7764           case MachinePlaysWhite:
7765           case IcsPlayingWhite:
7766             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7767             break;
7768           case AnalyzeMode:
7769           case AnalyzeFile:
7770             break;
7771           case IcsObserving: /* [DM] icsEngineAnalyze */
7772             if (!appData.icsEngineAnalyze) ignore = TRUE;
7773             break;
7774           case TwoMachinesPlay:
7775             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7776                 ignore = TRUE;
7777             }
7778             break;
7779           default:
7780             ignore = TRUE;
7781             break;
7782         }
7783
7784         if (!ignore) {
7785             buf1[0] = NULLCHAR;
7786             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7787                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7788
7789                 if (plyext != ' ' && plyext != '\t') {
7790                     time *= 100;
7791                 }
7792
7793                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7794                 if( cps->scoreIsAbsolute && 
7795                     ( gameMode == MachinePlaysBlack ||
7796                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7797                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7798                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7799                      !WhiteOnMove(currentMove)
7800                     ) )
7801                 {
7802                     curscore = -curscore;
7803                 }
7804
7805
7806                 programStats.depth = plylev;
7807                 programStats.nodes = nodes;
7808                 programStats.time = time;
7809                 programStats.score = curscore;
7810                 programStats.got_only_move = 0;
7811
7812                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7813                         int ticklen;
7814
7815                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7816                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7817                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7818                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7819                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7820                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7821                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7822                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7823                 }
7824
7825                 /* Buffer overflow protection */
7826                 if (buf1[0] != NULLCHAR) {
7827                     if (strlen(buf1) >= sizeof(programStats.movelist)
7828                         && appData.debugMode) {
7829                         fprintf(debugFP,
7830                                 "PV is too long; using the first %u bytes.\n",
7831                                 (unsigned) sizeof(programStats.movelist) - 1);
7832                     }
7833
7834                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7835                 } else {
7836                     sprintf(programStats.movelist, " no PV\n");
7837                 }
7838
7839                 if (programStats.seen_stat) {
7840                     programStats.ok_to_send = 1;
7841                 }
7842
7843                 if (strchr(programStats.movelist, '(') != NULL) {
7844                     programStats.line_is_book = 1;
7845                     programStats.nr_moves = 0;
7846                     programStats.moves_left = 0;
7847                 } else {
7848                     programStats.line_is_book = 0;
7849                 }
7850
7851                 SendProgramStatsToFrontend( cps, &programStats );
7852
7853                 /* 
7854                     [AS] Protect the thinkOutput buffer from overflow... this
7855                     is only useful if buf1 hasn't overflowed first!
7856                 */
7857                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7858                         plylev, 
7859                         (gameMode == TwoMachinesPlay ?
7860                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7861                         ((double) curscore) / 100.0,
7862                         prefixHint ? lastHint : "",
7863                         prefixHint ? " " : "" );
7864
7865                 if( buf1[0] != NULLCHAR ) {
7866                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7867
7868                     if( strlen(buf1) > max_len ) {
7869                         if( appData.debugMode) {
7870                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7871                         }
7872                         buf1[max_len+1] = '\0';
7873                     }
7874
7875                     strcat( thinkOutput, buf1 );
7876                 }
7877
7878                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7879                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7880                     DisplayMove(currentMove - 1);
7881                 }
7882                 return;
7883
7884             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7885                 /* crafty (9.25+) says "(only move) <move>"
7886                  * if there is only 1 legal move
7887                  */
7888                 sscanf(p, "(only move) %s", buf1);
7889                 sprintf(thinkOutput, "%s (only move)", buf1);
7890                 sprintf(programStats.movelist, "%s (only move)", buf1);
7891                 programStats.depth = 1;
7892                 programStats.nr_moves = 1;
7893                 programStats.moves_left = 1;
7894                 programStats.nodes = 1;
7895                 programStats.time = 1;
7896                 programStats.got_only_move = 1;
7897
7898                 /* Not really, but we also use this member to
7899                    mean "line isn't going to change" (Crafty
7900                    isn't searching, so stats won't change) */
7901                 programStats.line_is_book = 1;
7902
7903                 SendProgramStatsToFrontend( cps, &programStats );
7904                 
7905                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7906                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7907                     DisplayMove(currentMove - 1);
7908                 }
7909                 return;
7910             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7911                               &time, &nodes, &plylev, &mvleft,
7912                               &mvtot, mvname) >= 5) {
7913                 /* The stat01: line is from Crafty (9.29+) in response
7914                    to the "." command */
7915                 programStats.seen_stat = 1;
7916                 cps->maybeThinking = TRUE;
7917
7918                 if (programStats.got_only_move || !appData.periodicUpdates)
7919                   return;
7920
7921                 programStats.depth = plylev;
7922                 programStats.time = time;
7923                 programStats.nodes = nodes;
7924                 programStats.moves_left = mvleft;
7925                 programStats.nr_moves = mvtot;
7926                 strcpy(programStats.move_name, mvname);
7927                 programStats.ok_to_send = 1;
7928                 programStats.movelist[0] = '\0';
7929
7930                 SendProgramStatsToFrontend( cps, &programStats );
7931
7932                 return;
7933
7934             } else if (strncmp(message,"++",2) == 0) {
7935                 /* Crafty 9.29+ outputs this */
7936                 programStats.got_fail = 2;
7937                 return;
7938
7939             } else if (strncmp(message,"--",2) == 0) {
7940                 /* Crafty 9.29+ outputs this */
7941                 programStats.got_fail = 1;
7942                 return;
7943
7944             } else if (thinkOutput[0] != NULLCHAR &&
7945                        strncmp(message, "    ", 4) == 0) {
7946                 unsigned message_len;
7947
7948                 p = message;
7949                 while (*p && *p == ' ') p++;
7950
7951                 message_len = strlen( p );
7952
7953                 /* [AS] Avoid buffer overflow */
7954                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7955                     strcat(thinkOutput, " ");
7956                     strcat(thinkOutput, p);
7957                 }
7958
7959                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7960                     strcat(programStats.movelist, " ");
7961                     strcat(programStats.movelist, p);
7962                 }
7963
7964                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7965                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7966                     DisplayMove(currentMove - 1);
7967                 }
7968                 return;
7969             }
7970         }
7971         else {
7972             buf1[0] = NULLCHAR;
7973
7974             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7975                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7976             {
7977                 ChessProgramStats cpstats;
7978
7979                 if (plyext != ' ' && plyext != '\t') {
7980                     time *= 100;
7981                 }
7982
7983                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7984                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7985                     curscore = -curscore;
7986                 }
7987
7988                 cpstats.depth = plylev;
7989                 cpstats.nodes = nodes;
7990                 cpstats.time = time;
7991                 cpstats.score = curscore;
7992                 cpstats.got_only_move = 0;
7993                 cpstats.movelist[0] = '\0';
7994
7995                 if (buf1[0] != NULLCHAR) {
7996                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7997                 }
7998
7999                 cpstats.ok_to_send = 0;
8000                 cpstats.line_is_book = 0;
8001                 cpstats.nr_moves = 0;
8002                 cpstats.moves_left = 0;
8003
8004                 SendProgramStatsToFrontend( cps, &cpstats );
8005             }
8006         }
8007     }
8008 }
8009
8010
8011 /* Parse a game score from the character string "game", and
8012    record it as the history of the current game.  The game
8013    score is NOT assumed to start from the standard position. 
8014    The display is not updated in any way.
8015    */
8016 void
8017 ParseGameHistory(game)
8018      char *game;
8019 {
8020     ChessMove moveType;
8021     int fromX, fromY, toX, toY, boardIndex;
8022     char promoChar;
8023     char *p, *q;
8024     char buf[MSG_SIZ];
8025
8026     if (appData.debugMode)
8027       fprintf(debugFP, "Parsing game history: %s\n", game);
8028
8029     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8030     gameInfo.site = StrSave(appData.icsHost);
8031     gameInfo.date = PGNDate();
8032     gameInfo.round = StrSave("-");
8033
8034     /* Parse out names of players */
8035     while (*game == ' ') game++;
8036     p = buf;
8037     while (*game != ' ') *p++ = *game++;
8038     *p = NULLCHAR;
8039     gameInfo.white = StrSave(buf);
8040     while (*game == ' ') game++;
8041     p = buf;
8042     while (*game != ' ' && *game != '\n') *p++ = *game++;
8043     *p = NULLCHAR;
8044     gameInfo.black = StrSave(buf);
8045
8046     /* Parse moves */
8047     boardIndex = blackPlaysFirst ? 1 : 0;
8048     yynewstr(game);
8049     for (;;) {
8050         yyboardindex = boardIndex;
8051         moveType = (ChessMove) yylex();
8052         switch (moveType) {
8053           case IllegalMove:             /* maybe suicide chess, etc. */
8054   if (appData.debugMode) {
8055     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8056     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8057     setbuf(debugFP, NULL);
8058   }
8059           case WhitePromotionChancellor:
8060           case BlackPromotionChancellor:
8061           case WhitePromotionArchbishop:
8062           case BlackPromotionArchbishop:
8063           case WhitePromotionQueen:
8064           case BlackPromotionQueen:
8065           case WhitePromotionRook:
8066           case BlackPromotionRook:
8067           case WhitePromotionBishop:
8068           case BlackPromotionBishop:
8069           case WhitePromotionKnight:
8070           case BlackPromotionKnight:
8071           case WhitePromotionKing:
8072           case BlackPromotionKing:
8073           case NormalMove:
8074           case WhiteCapturesEnPassant:
8075           case BlackCapturesEnPassant:
8076           case WhiteKingSideCastle:
8077           case WhiteQueenSideCastle:
8078           case BlackKingSideCastle:
8079           case BlackQueenSideCastle:
8080           case WhiteKingSideCastleWild:
8081           case WhiteQueenSideCastleWild:
8082           case BlackKingSideCastleWild:
8083           case BlackQueenSideCastleWild:
8084           /* PUSH Fabien */
8085           case WhiteHSideCastleFR:
8086           case WhiteASideCastleFR:
8087           case BlackHSideCastleFR:
8088           case BlackASideCastleFR:
8089           /* POP Fabien */
8090             fromX = currentMoveString[0] - AAA;
8091             fromY = currentMoveString[1] - ONE;
8092             toX = currentMoveString[2] - AAA;
8093             toY = currentMoveString[3] - ONE;
8094             promoChar = currentMoveString[4];
8095             break;
8096           case WhiteDrop:
8097           case BlackDrop:
8098             fromX = moveType == WhiteDrop ?
8099               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8100             (int) CharToPiece(ToLower(currentMoveString[0]));
8101             fromY = DROP_RANK;
8102             toX = currentMoveString[2] - AAA;
8103             toY = currentMoveString[3] - ONE;
8104             promoChar = NULLCHAR;
8105             break;
8106           case AmbiguousMove:
8107             /* bug? */
8108             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8109   if (appData.debugMode) {
8110     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8111     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8112     setbuf(debugFP, NULL);
8113   }
8114             DisplayError(buf, 0);
8115             return;
8116           case ImpossibleMove:
8117             /* bug? */
8118             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8119   if (appData.debugMode) {
8120     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8121     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8122     setbuf(debugFP, NULL);
8123   }
8124             DisplayError(buf, 0);
8125             return;
8126           case (ChessMove) 0:   /* end of file */
8127             if (boardIndex < backwardMostMove) {
8128                 /* Oops, gap.  How did that happen? */
8129                 DisplayError(_("Gap in move list"), 0);
8130                 return;
8131             }
8132             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8133             if (boardIndex > forwardMostMove) {
8134                 forwardMostMove = boardIndex;
8135             }
8136             return;
8137           case ElapsedTime:
8138             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8139                 strcat(parseList[boardIndex-1], " ");
8140                 strcat(parseList[boardIndex-1], yy_text);
8141             }
8142             continue;
8143           case Comment:
8144           case PGNTag:
8145           case NAG:
8146           default:
8147             /* ignore */
8148             continue;
8149           case WhiteWins:
8150           case BlackWins:
8151           case GameIsDrawn:
8152           case GameUnfinished:
8153             if (gameMode == IcsExamining) {
8154                 if (boardIndex < backwardMostMove) {
8155                     /* Oops, gap.  How did that happen? */
8156                     return;
8157                 }
8158                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8159                 return;
8160             }
8161             gameInfo.result = moveType;
8162             p = strchr(yy_text, '{');
8163             if (p == NULL) p = strchr(yy_text, '(');
8164             if (p == NULL) {
8165                 p = yy_text;
8166                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8167             } else {
8168                 q = strchr(p, *p == '{' ? '}' : ')');
8169                 if (q != NULL) *q = NULLCHAR;
8170                 p++;
8171             }
8172             gameInfo.resultDetails = StrSave(p);
8173             continue;
8174         }
8175         if (boardIndex >= forwardMostMove &&
8176             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8177             backwardMostMove = blackPlaysFirst ? 1 : 0;
8178             return;
8179         }
8180         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8181                                  fromY, fromX, toY, toX, promoChar,
8182                                  parseList[boardIndex]);
8183         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8184         /* currentMoveString is set as a side-effect of yylex */
8185         strcpy(moveList[boardIndex], currentMoveString);
8186         strcat(moveList[boardIndex], "\n");
8187         boardIndex++;
8188         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8189         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8190           case MT_NONE:
8191           case MT_STALEMATE:
8192           default:
8193             break;
8194           case MT_CHECK:
8195             if(gameInfo.variant != VariantShogi)
8196                 strcat(parseList[boardIndex - 1], "+");
8197             break;
8198           case MT_CHECKMATE:
8199           case MT_STAINMATE:
8200             strcat(parseList[boardIndex - 1], "#");
8201             break;
8202         }
8203     }
8204 }
8205
8206
8207 /* Apply a move to the given board  */
8208 void
8209 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8210      int fromX, fromY, toX, toY;
8211      int promoChar;
8212      Board board;
8213 {
8214   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8215   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8216
8217     /* [HGM] compute & store e.p. status and castling rights for new position */
8218     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8219     { int i;
8220
8221       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8222       oldEP = (signed char)board[EP_STATUS];
8223       board[EP_STATUS] = EP_NONE;
8224
8225       if( board[toY][toX] != EmptySquare ) 
8226            board[EP_STATUS] = EP_CAPTURE;  
8227
8228       if( board[fromY][fromX] == WhitePawn ) {
8229            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8230                board[EP_STATUS] = EP_PAWN_MOVE;
8231            if( toY-fromY==2) {
8232                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8233                         gameInfo.variant != VariantBerolina || toX < fromX)
8234                       board[EP_STATUS] = toX | berolina;
8235                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8236                         gameInfo.variant != VariantBerolina || toX > fromX) 
8237                       board[EP_STATUS] = toX;
8238            }
8239       } else 
8240       if( board[fromY][fromX] == BlackPawn ) {
8241            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8242                board[EP_STATUS] = EP_PAWN_MOVE; 
8243            if( toY-fromY== -2) {
8244                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8245                         gameInfo.variant != VariantBerolina || toX < fromX)
8246                       board[EP_STATUS] = toX | berolina;
8247                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8248                         gameInfo.variant != VariantBerolina || toX > fromX) 
8249                       board[EP_STATUS] = toX;
8250            }
8251        }
8252
8253        for(i=0; i<nrCastlingRights; i++) {
8254            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8255               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8256              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8257        }
8258
8259     }
8260
8261   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8262   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8263        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8264          
8265   if (fromX == toX && fromY == toY) return;
8266
8267   if (fromY == DROP_RANK) {
8268         /* must be first */
8269         piece = board[toY][toX] = (ChessSquare) fromX;
8270   } else {
8271      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8272      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8273      if(gameInfo.variant == VariantKnightmate)
8274          king += (int) WhiteUnicorn - (int) WhiteKing;
8275
8276     /* Code added by Tord: */
8277     /* FRC castling assumed when king captures friendly rook. */
8278     if (board[fromY][fromX] == WhiteKing &&
8279              board[toY][toX] == WhiteRook) {
8280       board[fromY][fromX] = EmptySquare;
8281       board[toY][toX] = EmptySquare;
8282       if(toX > fromX) {
8283         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8284       } else {
8285         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8286       }
8287     } else if (board[fromY][fromX] == BlackKing &&
8288                board[toY][toX] == BlackRook) {
8289       board[fromY][fromX] = EmptySquare;
8290       board[toY][toX] = EmptySquare;
8291       if(toX > fromX) {
8292         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8293       } else {
8294         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8295       }
8296     /* End of code added by Tord */
8297
8298     } else if (board[fromY][fromX] == king
8299         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8300         && toY == fromY && toX > fromX+1) {
8301         board[fromY][fromX] = EmptySquare;
8302         board[toY][toX] = king;
8303         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8304         board[fromY][BOARD_RGHT-1] = EmptySquare;
8305     } else if (board[fromY][fromX] == king
8306         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8307                && toY == fromY && toX < fromX-1) {
8308         board[fromY][fromX] = EmptySquare;
8309         board[toY][toX] = king;
8310         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8311         board[fromY][BOARD_LEFT] = EmptySquare;
8312     } else if (board[fromY][fromX] == WhitePawn
8313                && toY >= BOARD_HEIGHT-promoRank
8314                && gameInfo.variant != VariantXiangqi
8315                ) {
8316         /* white pawn promotion */
8317         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8318         if (board[toY][toX] == EmptySquare) {
8319             board[toY][toX] = WhiteQueen;
8320         }
8321         if(gameInfo.variant==VariantBughouse ||
8322            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8323             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8324         board[fromY][fromX] = EmptySquare;
8325     } else if ((fromY == BOARD_HEIGHT-4)
8326                && (toX != fromX)
8327                && gameInfo.variant != VariantXiangqi
8328                && gameInfo.variant != VariantBerolina
8329                && (board[fromY][fromX] == WhitePawn)
8330                && (board[toY][toX] == EmptySquare)) {
8331         board[fromY][fromX] = EmptySquare;
8332         board[toY][toX] = WhitePawn;
8333         captured = board[toY - 1][toX];
8334         board[toY - 1][toX] = EmptySquare;
8335     } else if ((fromY == BOARD_HEIGHT-4)
8336                && (toX == fromX)
8337                && gameInfo.variant == VariantBerolina
8338                && (board[fromY][fromX] == WhitePawn)
8339                && (board[toY][toX] == EmptySquare)) {
8340         board[fromY][fromX] = EmptySquare;
8341         board[toY][toX] = WhitePawn;
8342         if(oldEP & EP_BEROLIN_A) {
8343                 captured = board[fromY][fromX-1];
8344                 board[fromY][fromX-1] = EmptySquare;
8345         }else{  captured = board[fromY][fromX+1];
8346                 board[fromY][fromX+1] = EmptySquare;
8347         }
8348     } else if (board[fromY][fromX] == king
8349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8350                && toY == fromY && toX > fromX+1) {
8351         board[fromY][fromX] = EmptySquare;
8352         board[toY][toX] = king;
8353         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8354         board[fromY][BOARD_RGHT-1] = EmptySquare;
8355     } else if (board[fromY][fromX] == king
8356         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8357                && toY == fromY && toX < fromX-1) {
8358         board[fromY][fromX] = EmptySquare;
8359         board[toY][toX] = king;
8360         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8361         board[fromY][BOARD_LEFT] = EmptySquare;
8362     } else if (fromY == 7 && fromX == 3
8363                && board[fromY][fromX] == BlackKing
8364                && toY == 7 && toX == 5) {
8365         board[fromY][fromX] = EmptySquare;
8366         board[toY][toX] = BlackKing;
8367         board[fromY][7] = EmptySquare;
8368         board[toY][4] = BlackRook;
8369     } else if (fromY == 7 && fromX == 3
8370                && board[fromY][fromX] == BlackKing
8371                && toY == 7 && toX == 1) {
8372         board[fromY][fromX] = EmptySquare;
8373         board[toY][toX] = BlackKing;
8374         board[fromY][0] = EmptySquare;
8375         board[toY][2] = BlackRook;
8376     } else if (board[fromY][fromX] == BlackPawn
8377                && toY < promoRank
8378                && gameInfo.variant != VariantXiangqi
8379                ) {
8380         /* black pawn promotion */
8381         board[toY][toX] = CharToPiece(ToLower(promoChar));
8382         if (board[toY][toX] == EmptySquare) {
8383             board[toY][toX] = BlackQueen;
8384         }
8385         if(gameInfo.variant==VariantBughouse ||
8386            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8387             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8388         board[fromY][fromX] = EmptySquare;
8389     } else if ((fromY == 3)
8390                && (toX != fromX)
8391                && gameInfo.variant != VariantXiangqi
8392                && gameInfo.variant != VariantBerolina
8393                && (board[fromY][fromX] == BlackPawn)
8394                && (board[toY][toX] == EmptySquare)) {
8395         board[fromY][fromX] = EmptySquare;
8396         board[toY][toX] = BlackPawn;
8397         captured = board[toY + 1][toX];
8398         board[toY + 1][toX] = EmptySquare;
8399     } else if ((fromY == 3)
8400                && (toX == fromX)
8401                && gameInfo.variant == VariantBerolina
8402                && (board[fromY][fromX] == BlackPawn)
8403                && (board[toY][toX] == EmptySquare)) {
8404         board[fromY][fromX] = EmptySquare;
8405         board[toY][toX] = BlackPawn;
8406         if(oldEP & EP_BEROLIN_A) {
8407                 captured = board[fromY][fromX-1];
8408                 board[fromY][fromX-1] = EmptySquare;
8409         }else{  captured = board[fromY][fromX+1];
8410                 board[fromY][fromX+1] = EmptySquare;
8411         }
8412     } else {
8413         board[toY][toX] = board[fromY][fromX];
8414         board[fromY][fromX] = EmptySquare;
8415     }
8416
8417     /* [HGM] now we promote for Shogi, if needed */
8418     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8419         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8420   }
8421
8422     if (gameInfo.holdingsWidth != 0) {
8423
8424       /* !!A lot more code needs to be written to support holdings  */
8425       /* [HGM] OK, so I have written it. Holdings are stored in the */
8426       /* penultimate board files, so they are automaticlly stored   */
8427       /* in the game history.                                       */
8428       if (fromY == DROP_RANK) {
8429         /* Delete from holdings, by decreasing count */
8430         /* and erasing image if necessary            */
8431         p = (int) fromX;
8432         if(p < (int) BlackPawn) { /* white drop */
8433              p -= (int)WhitePawn;
8434                  p = PieceToNumber((ChessSquare)p);
8435              if(p >= gameInfo.holdingsSize) p = 0;
8436              if(--board[p][BOARD_WIDTH-2] <= 0)
8437                   board[p][BOARD_WIDTH-1] = EmptySquare;
8438              if((int)board[p][BOARD_WIDTH-2] < 0)
8439                         board[p][BOARD_WIDTH-2] = 0;
8440         } else {                  /* black drop */
8441              p -= (int)BlackPawn;
8442                  p = PieceToNumber((ChessSquare)p);
8443              if(p >= gameInfo.holdingsSize) p = 0;
8444              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8445                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8446              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8447                         board[BOARD_HEIGHT-1-p][1] = 0;
8448         }
8449       }
8450       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8451           && gameInfo.variant != VariantBughouse        ) {
8452         /* [HGM] holdings: Add to holdings, if holdings exist */
8453         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8454                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8455                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8456         }
8457         p = (int) captured;
8458         if (p >= (int) BlackPawn) {
8459           p -= (int)BlackPawn;
8460           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8461                   /* in Shogi restore piece to its original  first */
8462                   captured = (ChessSquare) (DEMOTED captured);
8463                   p = DEMOTED p;
8464           }
8465           p = PieceToNumber((ChessSquare)p);
8466           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8467           board[p][BOARD_WIDTH-2]++;
8468           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8469         } else {
8470           p -= (int)WhitePawn;
8471           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8472                   captured = (ChessSquare) (DEMOTED captured);
8473                   p = DEMOTED p;
8474           }
8475           p = PieceToNumber((ChessSquare)p);
8476           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8477           board[BOARD_HEIGHT-1-p][1]++;
8478           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8479         }
8480       }
8481     } else if (gameInfo.variant == VariantAtomic) {
8482       if (captured != EmptySquare) {
8483         int y, x;
8484         for (y = toY-1; y <= toY+1; y++) {
8485           for (x = toX-1; x <= toX+1; x++) {
8486             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8487                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8488               board[y][x] = EmptySquare;
8489             }
8490           }
8491         }
8492         board[toY][toX] = EmptySquare;
8493       }
8494     }
8495     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8496         /* [HGM] Shogi promotions */
8497         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8498     }
8499
8500     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8501                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8502         // [HGM] superchess: take promotion piece out of holdings
8503         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8504         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8505             if(!--board[k][BOARD_WIDTH-2])
8506                 board[k][BOARD_WIDTH-1] = EmptySquare;
8507         } else {
8508             if(!--board[BOARD_HEIGHT-1-k][1])
8509                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8510         }
8511     }
8512
8513 }
8514
8515 /* Updates forwardMostMove */
8516 void
8517 MakeMove(fromX, fromY, toX, toY, promoChar)
8518      int fromX, fromY, toX, toY;
8519      int promoChar;
8520 {
8521 //    forwardMostMove++; // [HGM] bare: moved downstream
8522
8523     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8524         int timeLeft; static int lastLoadFlag=0; int king, piece;
8525         piece = boards[forwardMostMove][fromY][fromX];
8526         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8527         if(gameInfo.variant == VariantKnightmate)
8528             king += (int) WhiteUnicorn - (int) WhiteKing;
8529         if(forwardMostMove == 0) {
8530             if(blackPlaysFirst) 
8531                 fprintf(serverMoves, "%s;", second.tidy);
8532             fprintf(serverMoves, "%s;", first.tidy);
8533             if(!blackPlaysFirst) 
8534                 fprintf(serverMoves, "%s;", second.tidy);
8535         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8536         lastLoadFlag = loadFlag;
8537         // print base move
8538         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8539         // print castling suffix
8540         if( toY == fromY && piece == king ) {
8541             if(toX-fromX > 1)
8542                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8543             if(fromX-toX >1)
8544                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8545         }
8546         // e.p. suffix
8547         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8548              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8549              boards[forwardMostMove][toY][toX] == EmptySquare
8550              && fromX != toX )
8551                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8552         // promotion suffix
8553         if(promoChar != NULLCHAR)
8554                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8555         if(!loadFlag) {
8556             fprintf(serverMoves, "/%d/%d",
8557                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8558             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8559             else                      timeLeft = blackTimeRemaining/1000;
8560             fprintf(serverMoves, "/%d", timeLeft);
8561         }
8562         fflush(serverMoves);
8563     }
8564
8565     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8566       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8567                         0, 1);
8568       return;
8569     }
8570     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8571     if (commentList[forwardMostMove+1] != NULL) {
8572         free(commentList[forwardMostMove+1]);
8573         commentList[forwardMostMove+1] = NULL;
8574     }
8575     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8576     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8577     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8578     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8579     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8580     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8581     gameInfo.result = GameUnfinished;
8582     if (gameInfo.resultDetails != NULL) {
8583         free(gameInfo.resultDetails);
8584         gameInfo.resultDetails = NULL;
8585     }
8586     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8587                               moveList[forwardMostMove - 1]);
8588     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8589                              PosFlags(forwardMostMove - 1),
8590                              fromY, fromX, toY, toX, promoChar,
8591                              parseList[forwardMostMove - 1]);
8592     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8593       case MT_NONE:
8594       case MT_STALEMATE:
8595       default:
8596         break;
8597       case MT_CHECK:
8598         if(gameInfo.variant != VariantShogi)
8599             strcat(parseList[forwardMostMove - 1], "+");
8600         break;
8601       case MT_CHECKMATE:
8602       case MT_STAINMATE:
8603         strcat(parseList[forwardMostMove - 1], "#");
8604         break;
8605     }
8606     if (appData.debugMode) {
8607         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8608     }
8609
8610 }
8611
8612 /* Updates currentMove if not pausing */
8613 void
8614 ShowMove(fromX, fromY, toX, toY)
8615 {
8616     int instant = (gameMode == PlayFromGameFile) ?
8617         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8618     if(appData.noGUI) return;
8619     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8620         if (!instant) {
8621             if (forwardMostMove == currentMove + 1) {
8622                 AnimateMove(boards[forwardMostMove - 1],
8623                             fromX, fromY, toX, toY);
8624             }
8625             if (appData.highlightLastMove) {
8626                 SetHighlights(fromX, fromY, toX, toY);
8627             }
8628         }
8629         currentMove = forwardMostMove;
8630     }
8631
8632     if (instant) return;
8633
8634     DisplayMove(currentMove - 1);
8635     DrawPosition(FALSE, boards[currentMove]);
8636     DisplayBothClocks();
8637     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8638 }
8639
8640 void SendEgtPath(ChessProgramState *cps)
8641 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8642         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8643
8644         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8645
8646         while(*p) {
8647             char c, *q = name+1, *r, *s;
8648
8649             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8650             while(*p && *p != ',') *q++ = *p++;
8651             *q++ = ':'; *q = 0;
8652             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8653                 strcmp(name, ",nalimov:") == 0 ) {
8654                 // take nalimov path from the menu-changeable option first, if it is defined
8655                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8656                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8657             } else
8658             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8659                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8660                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8661                 s = r = StrStr(s, ":") + 1; // beginning of path info
8662                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8663                 c = *r; *r = 0;             // temporarily null-terminate path info
8664                     *--q = 0;               // strip of trailig ':' from name
8665                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8666                 *r = c;
8667                 SendToProgram(buf,cps);     // send egtbpath command for this format
8668             }
8669             if(*p == ',') p++; // read away comma to position for next format name
8670         }
8671 }
8672
8673 void
8674 InitChessProgram(cps, setup)
8675      ChessProgramState *cps;
8676      int setup; /* [HGM] needed to setup FRC opening position */
8677 {
8678     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8679     if (appData.noChessProgram) return;
8680     hintRequested = FALSE;
8681     bookRequested = FALSE;
8682
8683     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8684     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8685     if(cps->memSize) { /* [HGM] memory */
8686         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8687         SendToProgram(buf, cps);
8688     }
8689     SendEgtPath(cps); /* [HGM] EGT */
8690     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8691         sprintf(buf, "cores %d\n", appData.smpCores);
8692         SendToProgram(buf, cps);
8693     }
8694
8695     SendToProgram(cps->initString, cps);
8696     if (gameInfo.variant != VariantNormal &&
8697         gameInfo.variant != VariantLoadable
8698         /* [HGM] also send variant if board size non-standard */
8699         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8700                                             ) {
8701       char *v = VariantName(gameInfo.variant);
8702       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8703         /* [HGM] in protocol 1 we have to assume all variants valid */
8704         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8705         DisplayFatalError(buf, 0, 1);
8706         return;
8707       }
8708
8709       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8710       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8711       if( gameInfo.variant == VariantXiangqi )
8712            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8713       if( gameInfo.variant == VariantShogi )
8714            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8715       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8716            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8717       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8718                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8719            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8720       if( gameInfo.variant == VariantCourier )
8721            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8722       if( gameInfo.variant == VariantSuper )
8723            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8724       if( gameInfo.variant == VariantGreat )
8725            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8726
8727       if(overruled) {
8728            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8729                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8730            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8731            if(StrStr(cps->variants, b) == NULL) { 
8732                // specific sized variant not known, check if general sizing allowed
8733                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8734                    if(StrStr(cps->variants, "boardsize") == NULL) {
8735                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8736                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8737                        DisplayFatalError(buf, 0, 1);
8738                        return;
8739                    }
8740                    /* [HGM] here we really should compare with the maximum supported board size */
8741                }
8742            }
8743       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8744       sprintf(buf, "variant %s\n", b);
8745       SendToProgram(buf, cps);
8746     }
8747     currentlyInitializedVariant = gameInfo.variant;
8748
8749     /* [HGM] send opening position in FRC to first engine */
8750     if(setup) {
8751           SendToProgram("force\n", cps);
8752           SendBoard(cps, 0);
8753           /* engine is now in force mode! Set flag to wake it up after first move. */
8754           setboardSpoiledMachineBlack = 1;
8755     }
8756
8757     if (cps->sendICS) {
8758       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8759       SendToProgram(buf, cps);
8760     }
8761     cps->maybeThinking = FALSE;
8762     cps->offeredDraw = 0;
8763     if (!appData.icsActive) {
8764         SendTimeControl(cps, movesPerSession, timeControl,
8765                         timeIncrement, appData.searchDepth,
8766                         searchTime);
8767     }
8768     if (appData.showThinking 
8769         // [HGM] thinking: four options require thinking output to be sent
8770         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8771                                 ) {
8772         SendToProgram("post\n", cps);
8773     }
8774     SendToProgram("hard\n", cps);
8775     if (!appData.ponderNextMove) {
8776         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8777            it without being sure what state we are in first.  "hard"
8778            is not a toggle, so that one is OK.
8779          */
8780         SendToProgram("easy\n", cps);
8781     }
8782     if (cps->usePing) {
8783       sprintf(buf, "ping %d\n", ++cps->lastPing);
8784       SendToProgram(buf, cps);
8785     }
8786     cps->initDone = TRUE;
8787 }   
8788
8789
8790 void
8791 StartChessProgram(cps)
8792      ChessProgramState *cps;
8793 {
8794     char buf[MSG_SIZ];
8795     int err;
8796
8797     if (appData.noChessProgram) return;
8798     cps->initDone = FALSE;
8799
8800     if (strcmp(cps->host, "localhost") == 0) {
8801         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8802     } else if (*appData.remoteShell == NULLCHAR) {
8803         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8804     } else {
8805         if (*appData.remoteUser == NULLCHAR) {
8806           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8807                     cps->program);
8808         } else {
8809           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8810                     cps->host, appData.remoteUser, cps->program);
8811         }
8812         err = StartChildProcess(buf, "", &cps->pr);
8813     }
8814     
8815     if (err != 0) {
8816         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8817         DisplayFatalError(buf, err, 1);
8818         cps->pr = NoProc;
8819         cps->isr = NULL;
8820         return;
8821     }
8822     
8823     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8824     if (cps->protocolVersion > 1) {
8825       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8826       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8827       cps->comboCnt = 0;  //                and values of combo boxes
8828       SendToProgram(buf, cps);
8829     } else {
8830       SendToProgram("xboard\n", cps);
8831     }
8832 }
8833
8834
8835 void
8836 TwoMachinesEventIfReady P((void))
8837 {
8838   if (first.lastPing != first.lastPong) {
8839     DisplayMessage("", _("Waiting for first chess program"));
8840     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8841     return;
8842   }
8843   if (second.lastPing != second.lastPong) {
8844     DisplayMessage("", _("Waiting for second chess program"));
8845     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8846     return;
8847   }
8848   ThawUI();
8849   TwoMachinesEvent();
8850 }
8851
8852 void
8853 NextMatchGame P((void))
8854 {
8855     int index; /* [HGM] autoinc: step load index during match */
8856     Reset(FALSE, TRUE);
8857     if (*appData.loadGameFile != NULLCHAR) {
8858         index = appData.loadGameIndex;
8859         if(index < 0) { // [HGM] autoinc
8860             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8861             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8862         } 
8863         LoadGameFromFile(appData.loadGameFile,
8864                          index,
8865                          appData.loadGameFile, FALSE);
8866     } else if (*appData.loadPositionFile != NULLCHAR) {
8867         index = appData.loadPositionIndex;
8868         if(index < 0) { // [HGM] autoinc
8869             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8870             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8871         } 
8872         LoadPositionFromFile(appData.loadPositionFile,
8873                              index,
8874                              appData.loadPositionFile);
8875     }
8876     TwoMachinesEventIfReady();
8877 }
8878
8879 void UserAdjudicationEvent( int result )
8880 {
8881     ChessMove gameResult = GameIsDrawn;
8882
8883     if( result > 0 ) {
8884         gameResult = WhiteWins;
8885     }
8886     else if( result < 0 ) {
8887         gameResult = BlackWins;
8888     }
8889
8890     if( gameMode == TwoMachinesPlay ) {
8891         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8892     }
8893 }
8894
8895
8896 // [HGM] save: calculate checksum of game to make games easily identifiable
8897 int StringCheckSum(char *s)
8898 {
8899         int i = 0;
8900         if(s==NULL) return 0;
8901         while(*s) i = i*259 + *s++;
8902         return i;
8903 }
8904
8905 int GameCheckSum()
8906 {
8907         int i, sum=0;
8908         for(i=backwardMostMove; i<forwardMostMove; i++) {
8909                 sum += pvInfoList[i].depth;
8910                 sum += StringCheckSum(parseList[i]);
8911                 sum += StringCheckSum(commentList[i]);
8912                 sum *= 261;
8913         }
8914         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8915         return sum + StringCheckSum(commentList[i]);
8916 } // end of save patch
8917
8918 void
8919 GameEnds(result, resultDetails, whosays)
8920      ChessMove result;
8921      char *resultDetails;
8922      int whosays;
8923 {
8924     GameMode nextGameMode;
8925     int isIcsGame;
8926     char buf[MSG_SIZ];
8927
8928     if(endingGame) return; /* [HGM] crash: forbid recursion */
8929     endingGame = 1;
8930     if(twoBoards) { // [HGM] dual: switch back to one board
8931         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8932         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8933     }
8934     if (appData.debugMode) {
8935       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8936               result, resultDetails ? resultDetails : "(null)", whosays);
8937     }
8938
8939     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8940
8941     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8942         /* If we are playing on ICS, the server decides when the
8943            game is over, but the engine can offer to draw, claim 
8944            a draw, or resign. 
8945          */
8946 #if ZIPPY
8947         if (appData.zippyPlay && first.initDone) {
8948             if (result == GameIsDrawn) {
8949                 /* In case draw still needs to be claimed */
8950                 SendToICS(ics_prefix);
8951                 SendToICS("draw\n");
8952             } else if (StrCaseStr(resultDetails, "resign")) {
8953                 SendToICS(ics_prefix);
8954                 SendToICS("resign\n");
8955             }
8956         }
8957 #endif
8958         endingGame = 0; /* [HGM] crash */
8959         return;
8960     }
8961
8962     /* If we're loading the game from a file, stop */
8963     if (whosays == GE_FILE) {
8964       (void) StopLoadGameTimer();
8965       gameFileFP = NULL;
8966     }
8967
8968     /* Cancel draw offers */
8969     first.offeredDraw = second.offeredDraw = 0;
8970
8971     /* If this is an ICS game, only ICS can really say it's done;
8972        if not, anyone can. */
8973     isIcsGame = (gameMode == IcsPlayingWhite || 
8974                  gameMode == IcsPlayingBlack || 
8975                  gameMode == IcsObserving    || 
8976                  gameMode == IcsExamining);
8977
8978     if (!isIcsGame || whosays == GE_ICS) {
8979         /* OK -- not an ICS game, or ICS said it was done */
8980         StopClocks();
8981         if (!isIcsGame && !appData.noChessProgram) 
8982           SetUserThinkingEnables();
8983     
8984         /* [HGM] if a machine claims the game end we verify this claim */
8985         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8986             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8987                 char claimer;
8988                 ChessMove trueResult = (ChessMove) -1;
8989
8990                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8991                                             first.twoMachinesColor[0] :
8992                                             second.twoMachinesColor[0] ;
8993
8994                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8995                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8996                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8997                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8998                 } else
8999                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9000                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9001                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9002                 } else
9003                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9004                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9005                 }
9006
9007                 // now verify win claims, but not in drop games, as we don't understand those yet
9008                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9009                                                  || gameInfo.variant == VariantGreat) &&
9010                     (result == WhiteWins && claimer == 'w' ||
9011                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9012                       if (appData.debugMode) {
9013                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9014                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9015                       }
9016                       if(result != trueResult) {
9017                               sprintf(buf, "False win claim: '%s'", resultDetails);
9018                               result = claimer == 'w' ? BlackWins : WhiteWins;
9019                               resultDetails = buf;
9020                       }
9021                 } else
9022                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9023                     && (forwardMostMove <= backwardMostMove ||
9024                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9025                         (claimer=='b')==(forwardMostMove&1))
9026                                                                                   ) {
9027                       /* [HGM] verify: draws that were not flagged are false claims */
9028                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9029                       result = claimer == 'w' ? BlackWins : WhiteWins;
9030                       resultDetails = buf;
9031                 }
9032                 /* (Claiming a loss is accepted no questions asked!) */
9033             }
9034             /* [HGM] bare: don't allow bare King to win */
9035             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9036                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9037                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9038                && result != GameIsDrawn)
9039             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9040                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9041                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9042                         if(p >= 0 && p <= (int)WhiteKing) k++;
9043                 }
9044                 if (appData.debugMode) {
9045                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9046                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9047                 }
9048                 if(k <= 1) {
9049                         result = GameIsDrawn;
9050                         sprintf(buf, "%s but bare king", resultDetails);
9051                         resultDetails = buf;
9052                 }
9053             }
9054         }
9055
9056
9057         if(serverMoves != NULL && !loadFlag) { char c = '=';
9058             if(result==WhiteWins) c = '+';
9059             if(result==BlackWins) c = '-';
9060             if(resultDetails != NULL)
9061                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9062         }
9063         if (resultDetails != NULL) {
9064             gameInfo.result = result;
9065             gameInfo.resultDetails = StrSave(resultDetails);
9066
9067             /* display last move only if game was not loaded from file */
9068             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9069                 DisplayMove(currentMove - 1);
9070     
9071             if (forwardMostMove != 0) {
9072                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9073                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9074                                                                 ) {
9075                     if (*appData.saveGameFile != NULLCHAR) {
9076                         SaveGameToFile(appData.saveGameFile, TRUE);
9077                     } else if (appData.autoSaveGames) {
9078                         AutoSaveGame();
9079                     }
9080                     if (*appData.savePositionFile != NULLCHAR) {
9081                         SavePositionToFile(appData.savePositionFile);
9082                     }
9083                 }
9084             }
9085
9086             /* Tell program how game ended in case it is learning */
9087             /* [HGM] Moved this to after saving the PGN, just in case */
9088             /* engine died and we got here through time loss. In that */
9089             /* case we will get a fatal error writing the pipe, which */
9090             /* would otherwise lose us the PGN.                       */
9091             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9092             /* output during GameEnds should never be fatal anymore   */
9093             if (gameMode == MachinePlaysWhite ||
9094                 gameMode == MachinePlaysBlack ||
9095                 gameMode == TwoMachinesPlay ||
9096                 gameMode == IcsPlayingWhite ||
9097                 gameMode == IcsPlayingBlack ||
9098                 gameMode == BeginningOfGame) {
9099                 char buf[MSG_SIZ];
9100                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9101                         resultDetails);
9102                 if (first.pr != NoProc) {
9103                     SendToProgram(buf, &first);
9104                 }
9105                 if (second.pr != NoProc &&
9106                     gameMode == TwoMachinesPlay) {
9107                     SendToProgram(buf, &second);
9108                 }
9109             }
9110         }
9111
9112         if (appData.icsActive) {
9113             if (appData.quietPlay &&
9114                 (gameMode == IcsPlayingWhite ||
9115                  gameMode == IcsPlayingBlack)) {
9116                 SendToICS(ics_prefix);
9117                 SendToICS("set shout 1\n");
9118             }
9119             nextGameMode = IcsIdle;
9120             ics_user_moved = FALSE;
9121             /* clean up premove.  It's ugly when the game has ended and the
9122              * premove highlights are still on the board.
9123              */
9124             if (gotPremove) {
9125               gotPremove = FALSE;
9126               ClearPremoveHighlights();
9127               DrawPosition(FALSE, boards[currentMove]);
9128             }
9129             if (whosays == GE_ICS) {
9130                 switch (result) {
9131                 case WhiteWins:
9132                     if (gameMode == IcsPlayingWhite)
9133                         PlayIcsWinSound();
9134                     else if(gameMode == IcsPlayingBlack)
9135                         PlayIcsLossSound();
9136                     break;
9137                 case BlackWins:
9138                     if (gameMode == IcsPlayingBlack)
9139                         PlayIcsWinSound();
9140                     else if(gameMode == IcsPlayingWhite)
9141                         PlayIcsLossSound();
9142                     break;
9143                 case GameIsDrawn:
9144                     PlayIcsDrawSound();
9145                     break;
9146                 default:
9147                     PlayIcsUnfinishedSound();
9148                 }
9149             }
9150         } else if (gameMode == EditGame ||
9151                    gameMode == PlayFromGameFile || 
9152                    gameMode == AnalyzeMode || 
9153                    gameMode == AnalyzeFile) {
9154             nextGameMode = gameMode;
9155         } else {
9156             nextGameMode = EndOfGame;
9157         }
9158         pausing = FALSE;
9159         ModeHighlight();
9160     } else {
9161         nextGameMode = gameMode;
9162     }
9163
9164     if (appData.noChessProgram) {
9165         gameMode = nextGameMode;
9166         ModeHighlight();
9167         endingGame = 0; /* [HGM] crash */
9168         return;
9169     }
9170
9171     if (first.reuse) {
9172         /* Put first chess program into idle state */
9173         if (first.pr != NoProc &&
9174             (gameMode == MachinePlaysWhite ||
9175              gameMode == MachinePlaysBlack ||
9176              gameMode == TwoMachinesPlay ||
9177              gameMode == IcsPlayingWhite ||
9178              gameMode == IcsPlayingBlack ||
9179              gameMode == BeginningOfGame)) {
9180             SendToProgram("force\n", &first);
9181             if (first.usePing) {
9182               char buf[MSG_SIZ];
9183               sprintf(buf, "ping %d\n", ++first.lastPing);
9184               SendToProgram(buf, &first);
9185             }
9186         }
9187     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9188         /* Kill off first chess program */
9189         if (first.isr != NULL)
9190           RemoveInputSource(first.isr);
9191         first.isr = NULL;
9192     
9193         if (first.pr != NoProc) {
9194             ExitAnalyzeMode();
9195             DoSleep( appData.delayBeforeQuit );
9196             SendToProgram("quit\n", &first);
9197             DoSleep( appData.delayAfterQuit );
9198             DestroyChildProcess(first.pr, first.useSigterm);
9199         }
9200         first.pr = NoProc;
9201     }
9202     if (second.reuse) {
9203         /* Put second chess program into idle state */
9204         if (second.pr != NoProc &&
9205             gameMode == TwoMachinesPlay) {
9206             SendToProgram("force\n", &second);
9207             if (second.usePing) {
9208               char buf[MSG_SIZ];
9209               sprintf(buf, "ping %d\n", ++second.lastPing);
9210               SendToProgram(buf, &second);
9211             }
9212         }
9213     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9214         /* Kill off second chess program */
9215         if (second.isr != NULL)
9216           RemoveInputSource(second.isr);
9217         second.isr = NULL;
9218     
9219         if (second.pr != NoProc) {
9220             DoSleep( appData.delayBeforeQuit );
9221             SendToProgram("quit\n", &second);
9222             DoSleep( appData.delayAfterQuit );
9223             DestroyChildProcess(second.pr, second.useSigterm);
9224         }
9225         second.pr = NoProc;
9226     }
9227
9228     if (matchMode && gameMode == TwoMachinesPlay) {
9229         switch (result) {
9230         case WhiteWins:
9231           if (first.twoMachinesColor[0] == 'w') {
9232             first.matchWins++;
9233           } else {
9234             second.matchWins++;
9235           }
9236           break;
9237         case BlackWins:
9238           if (first.twoMachinesColor[0] == 'b') {
9239             first.matchWins++;
9240           } else {
9241             second.matchWins++;
9242           }
9243           break;
9244         default:
9245           break;
9246         }
9247         if (matchGame < appData.matchGames) {
9248             char *tmp;
9249             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9250                 tmp = first.twoMachinesColor;
9251                 first.twoMachinesColor = second.twoMachinesColor;
9252                 second.twoMachinesColor = tmp;
9253             }
9254             gameMode = nextGameMode;
9255             matchGame++;
9256             if(appData.matchPause>10000 || appData.matchPause<10)
9257                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9258             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9259             endingGame = 0; /* [HGM] crash */
9260             return;
9261         } else {
9262             char buf[MSG_SIZ];
9263             gameMode = nextGameMode;
9264             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9265                     first.tidy, second.tidy,
9266                     first.matchWins, second.matchWins,
9267                     appData.matchGames - (first.matchWins + second.matchWins));
9268             DisplayFatalError(buf, 0, 0);
9269         }
9270     }
9271     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9272         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9273       ExitAnalyzeMode();
9274     gameMode = nextGameMode;
9275     ModeHighlight();
9276     endingGame = 0;  /* [HGM] crash */
9277 }
9278
9279 /* Assumes program was just initialized (initString sent).
9280    Leaves program in force mode. */
9281 void
9282 FeedMovesToProgram(cps, upto) 
9283      ChessProgramState *cps;
9284      int upto;
9285 {
9286     int i;
9287     
9288     if (appData.debugMode)
9289       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9290               startedFromSetupPosition ? "position and " : "",
9291               backwardMostMove, upto, cps->which);
9292     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9293         // [HGM] variantswitch: make engine aware of new variant
9294         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9295                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9296         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9297         SendToProgram(buf, cps);
9298         currentlyInitializedVariant = gameInfo.variant;
9299     }
9300     SendToProgram("force\n", cps);
9301     if (startedFromSetupPosition) {
9302         SendBoard(cps, backwardMostMove);
9303     if (appData.debugMode) {
9304         fprintf(debugFP, "feedMoves\n");
9305     }
9306     }
9307     for (i = backwardMostMove; i < upto; i++) {
9308         SendMoveToProgram(i, cps);
9309     }
9310 }
9311
9312
9313 void
9314 ResurrectChessProgram()
9315 {
9316      /* The chess program may have exited.
9317         If so, restart it and feed it all the moves made so far. */
9318
9319     if (appData.noChessProgram || first.pr != NoProc) return;
9320     
9321     StartChessProgram(&first);
9322     InitChessProgram(&first, FALSE);
9323     FeedMovesToProgram(&first, currentMove);
9324
9325     if (!first.sendTime) {
9326         /* can't tell gnuchess what its clock should read,
9327            so we bow to its notion. */
9328         ResetClocks();
9329         timeRemaining[0][currentMove] = whiteTimeRemaining;
9330         timeRemaining[1][currentMove] = blackTimeRemaining;
9331     }
9332
9333     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9334                 appData.icsEngineAnalyze) && first.analysisSupport) {
9335       SendToProgram("analyze\n", &first);
9336       first.analyzing = TRUE;
9337     }
9338 }
9339
9340 /*
9341  * Button procedures
9342  */
9343 void
9344 Reset(redraw, init)
9345      int redraw, init;
9346 {
9347     int i;
9348
9349     if (appData.debugMode) {
9350         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9351                 redraw, init, gameMode);
9352     }
9353     CleanupTail(); // [HGM] vari: delete any stored variations
9354     pausing = pauseExamInvalid = FALSE;
9355     startedFromSetupPosition = blackPlaysFirst = FALSE;
9356     firstMove = TRUE;
9357     whiteFlag = blackFlag = FALSE;
9358     userOfferedDraw = FALSE;
9359     hintRequested = bookRequested = FALSE;
9360     first.maybeThinking = FALSE;
9361     second.maybeThinking = FALSE;
9362     first.bookSuspend = FALSE; // [HGM] book
9363     second.bookSuspend = FALSE;
9364     thinkOutput[0] = NULLCHAR;
9365     lastHint[0] = NULLCHAR;
9366     ClearGameInfo(&gameInfo);
9367     gameInfo.variant = StringToVariant(appData.variant);
9368     ics_user_moved = ics_clock_paused = FALSE;
9369     ics_getting_history = H_FALSE;
9370     ics_gamenum = -1;
9371     white_holding[0] = black_holding[0] = NULLCHAR;
9372     ClearProgramStats();
9373     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9374     
9375     ResetFrontEnd();
9376     ClearHighlights();
9377     flipView = appData.flipView;
9378     ClearPremoveHighlights();
9379     gotPremove = FALSE;
9380     alarmSounded = FALSE;
9381
9382     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9383     if(appData.serverMovesName != NULL) {
9384         /* [HGM] prepare to make moves file for broadcasting */
9385         clock_t t = clock();
9386         if(serverMoves != NULL) fclose(serverMoves);
9387         serverMoves = fopen(appData.serverMovesName, "r");
9388         if(serverMoves != NULL) {
9389             fclose(serverMoves);
9390             /* delay 15 sec before overwriting, so all clients can see end */
9391             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9392         }
9393         serverMoves = fopen(appData.serverMovesName, "w");
9394     }
9395
9396     ExitAnalyzeMode();
9397     gameMode = BeginningOfGame;
9398     ModeHighlight();
9399     if(appData.icsActive) gameInfo.variant = VariantNormal;
9400     currentMove = forwardMostMove = backwardMostMove = 0;
9401     InitPosition(redraw);
9402     for (i = 0; i < MAX_MOVES; i++) {
9403         if (commentList[i] != NULL) {
9404             free(commentList[i]);
9405             commentList[i] = NULL;
9406         }
9407     }
9408     ResetClocks();
9409     timeRemaining[0][0] = whiteTimeRemaining;
9410     timeRemaining[1][0] = blackTimeRemaining;
9411     if (first.pr == NULL) {
9412         StartChessProgram(&first);
9413     }
9414     if (init) {
9415             InitChessProgram(&first, startedFromSetupPosition);
9416     }
9417     DisplayTitle("");
9418     DisplayMessage("", "");
9419     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9420     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9421 }
9422
9423 void
9424 AutoPlayGameLoop()
9425 {
9426     for (;;) {
9427         if (!AutoPlayOneMove())
9428           return;
9429         if (matchMode || appData.timeDelay == 0)
9430           continue;
9431         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9432           return;
9433         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9434         break;
9435     }
9436 }
9437
9438
9439 int
9440 AutoPlayOneMove()
9441 {
9442     int fromX, fromY, toX, toY;
9443
9444     if (appData.debugMode) {
9445       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9446     }
9447
9448     if (gameMode != PlayFromGameFile)
9449       return FALSE;
9450
9451     if (currentMove >= forwardMostMove) {
9452       gameMode = EditGame;
9453       ModeHighlight();
9454
9455       /* [AS] Clear current move marker at the end of a game */
9456       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9457
9458       return FALSE;
9459     }
9460     
9461     toX = moveList[currentMove][2] - AAA;
9462     toY = moveList[currentMove][3] - ONE;
9463
9464     if (moveList[currentMove][1] == '@') {
9465         if (appData.highlightLastMove) {
9466             SetHighlights(-1, -1, toX, toY);
9467         }
9468     } else {
9469         fromX = moveList[currentMove][0] - AAA;
9470         fromY = moveList[currentMove][1] - ONE;
9471
9472         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9473
9474         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9475
9476         if (appData.highlightLastMove) {
9477             SetHighlights(fromX, fromY, toX, toY);
9478         }
9479     }
9480     DisplayMove(currentMove);
9481     SendMoveToProgram(currentMove++, &first);
9482     DisplayBothClocks();
9483     DrawPosition(FALSE, boards[currentMove]);
9484     // [HGM] PV info: always display, routine tests if empty
9485     DisplayComment(currentMove - 1, commentList[currentMove]);
9486     return TRUE;
9487 }
9488
9489
9490 int
9491 LoadGameOneMove(readAhead)
9492      ChessMove readAhead;
9493 {
9494     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9495     char promoChar = NULLCHAR;
9496     ChessMove moveType;
9497     char move[MSG_SIZ];
9498     char *p, *q;
9499     
9500     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9501         gameMode != AnalyzeMode && gameMode != Training) {
9502         gameFileFP = NULL;
9503         return FALSE;
9504     }
9505     
9506     yyboardindex = forwardMostMove;
9507     if (readAhead != (ChessMove)0) {
9508       moveType = readAhead;
9509     } else {
9510       if (gameFileFP == NULL)
9511           return FALSE;
9512       moveType = (ChessMove) yylex();
9513     }
9514     
9515     done = FALSE;
9516     switch (moveType) {
9517       case Comment:
9518         if (appData.debugMode) 
9519           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9520         p = yy_text;
9521
9522         /* append the comment but don't display it */
9523         AppendComment(currentMove, p, FALSE);
9524         return TRUE;
9525
9526       case WhiteCapturesEnPassant:
9527       case BlackCapturesEnPassant:
9528       case WhitePromotionChancellor:
9529       case BlackPromotionChancellor:
9530       case WhitePromotionArchbishop:
9531       case BlackPromotionArchbishop:
9532       case WhitePromotionCentaur:
9533       case BlackPromotionCentaur:
9534       case WhitePromotionQueen:
9535       case BlackPromotionQueen:
9536       case WhitePromotionRook:
9537       case BlackPromotionRook:
9538       case WhitePromotionBishop:
9539       case BlackPromotionBishop:
9540       case WhitePromotionKnight:
9541       case BlackPromotionKnight:
9542       case WhitePromotionKing:
9543       case BlackPromotionKing:
9544       case NormalMove:
9545       case WhiteKingSideCastle:
9546       case WhiteQueenSideCastle:
9547       case BlackKingSideCastle:
9548       case BlackQueenSideCastle:
9549       case WhiteKingSideCastleWild:
9550       case WhiteQueenSideCastleWild:
9551       case BlackKingSideCastleWild:
9552       case BlackQueenSideCastleWild:
9553       /* PUSH Fabien */
9554       case WhiteHSideCastleFR:
9555       case WhiteASideCastleFR:
9556       case BlackHSideCastleFR:
9557       case BlackASideCastleFR:
9558       /* POP Fabien */
9559         if (appData.debugMode)
9560           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9561         fromX = currentMoveString[0] - AAA;
9562         fromY = currentMoveString[1] - ONE;
9563         toX = currentMoveString[2] - AAA;
9564         toY = currentMoveString[3] - ONE;
9565         promoChar = currentMoveString[4];
9566         break;
9567
9568       case WhiteDrop:
9569       case BlackDrop:
9570         if (appData.debugMode)
9571           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9572         fromX = moveType == WhiteDrop ?
9573           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9574         (int) CharToPiece(ToLower(currentMoveString[0]));
9575         fromY = DROP_RANK;
9576         toX = currentMoveString[2] - AAA;
9577         toY = currentMoveString[3] - ONE;
9578         break;
9579
9580       case WhiteWins:
9581       case BlackWins:
9582       case GameIsDrawn:
9583       case GameUnfinished:
9584         if (appData.debugMode)
9585           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9586         p = strchr(yy_text, '{');
9587         if (p == NULL) p = strchr(yy_text, '(');
9588         if (p == NULL) {
9589             p = yy_text;
9590             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9591         } else {
9592             q = strchr(p, *p == '{' ? '}' : ')');
9593             if (q != NULL) *q = NULLCHAR;
9594             p++;
9595         }
9596         GameEnds(moveType, p, GE_FILE);
9597         done = TRUE;
9598         if (cmailMsgLoaded) {
9599             ClearHighlights();
9600             flipView = WhiteOnMove(currentMove);
9601             if (moveType == GameUnfinished) flipView = !flipView;
9602             if (appData.debugMode)
9603               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9604         }
9605         break;
9606
9607       case (ChessMove) 0:       /* end of file */
9608         if (appData.debugMode)
9609           fprintf(debugFP, "Parser hit end of file\n");
9610         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9611           case MT_NONE:
9612           case MT_CHECK:
9613             break;
9614           case MT_CHECKMATE:
9615           case MT_STAINMATE:
9616             if (WhiteOnMove(currentMove)) {
9617                 GameEnds(BlackWins, "Black mates", GE_FILE);
9618             } else {
9619                 GameEnds(WhiteWins, "White mates", GE_FILE);
9620             }
9621             break;
9622           case MT_STALEMATE:
9623             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9624             break;
9625         }
9626         done = TRUE;
9627         break;
9628
9629       case MoveNumberOne:
9630         if (lastLoadGameStart == GNUChessGame) {
9631             /* GNUChessGames have numbers, but they aren't move numbers */
9632             if (appData.debugMode)
9633               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9634                       yy_text, (int) moveType);
9635             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9636         }
9637         /* else fall thru */
9638
9639       case XBoardGame:
9640       case GNUChessGame:
9641       case PGNTag:
9642         /* Reached start of next game in file */
9643         if (appData.debugMode)
9644           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9645         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9646           case MT_NONE:
9647           case MT_CHECK:
9648             break;
9649           case MT_CHECKMATE:
9650           case MT_STAINMATE:
9651             if (WhiteOnMove(currentMove)) {
9652                 GameEnds(BlackWins, "Black mates", GE_FILE);
9653             } else {
9654                 GameEnds(WhiteWins, "White mates", GE_FILE);
9655             }
9656             break;
9657           case MT_STALEMATE:
9658             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9659             break;
9660         }
9661         done = TRUE;
9662         break;
9663
9664       case PositionDiagram:     /* should not happen; ignore */
9665       case ElapsedTime:         /* ignore */
9666       case NAG:                 /* ignore */
9667         if (appData.debugMode)
9668           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9669                   yy_text, (int) moveType);
9670         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9671
9672       case IllegalMove:
9673         if (appData.testLegality) {
9674             if (appData.debugMode)
9675               fprintf(debugFP, "Parsed IllegalMove: %s\n", 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         } else {
9682             if (appData.debugMode)
9683               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9684                       yy_text, currentMoveString);
9685             fromX = currentMoveString[0] - AAA;
9686             fromY = currentMoveString[1] - ONE;
9687             toX = currentMoveString[2] - AAA;
9688             toY = currentMoveString[3] - ONE;
9689             promoChar = currentMoveString[4];
9690         }
9691         break;
9692
9693       case AmbiguousMove:
9694         if (appData.debugMode)
9695           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9696         sprintf(move, _("Ambiguous move: %d.%s%s"),
9697                 (forwardMostMove / 2) + 1,
9698                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9699         DisplayError(move, 0);
9700         done = TRUE;
9701         break;
9702
9703       default:
9704       case ImpossibleMove:
9705         if (appData.debugMode)
9706           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9707         sprintf(move, _("Illegal move: %d.%s%s"),
9708                 (forwardMostMove / 2) + 1,
9709                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9710         DisplayError(move, 0);
9711         done = TRUE;
9712         break;
9713     }
9714
9715     if (done) {
9716         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9717             DrawPosition(FALSE, boards[currentMove]);
9718             DisplayBothClocks();
9719             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9720               DisplayComment(currentMove - 1, commentList[currentMove]);
9721         }
9722         (void) StopLoadGameTimer();
9723         gameFileFP = NULL;
9724         cmailOldMove = forwardMostMove;
9725         return FALSE;
9726     } else {
9727         /* currentMoveString is set as a side-effect of yylex */
9728         strcat(currentMoveString, "\n");
9729         strcpy(moveList[forwardMostMove], currentMoveString);
9730         
9731         thinkOutput[0] = NULLCHAR;
9732         MakeMove(fromX, fromY, toX, toY, promoChar);
9733         currentMove = forwardMostMove;
9734         return TRUE;
9735     }
9736 }
9737
9738 /* Load the nth game from the given file */
9739 int
9740 LoadGameFromFile(filename, n, title, useList)
9741      char *filename;
9742      int n;
9743      char *title;
9744      /*Boolean*/ int useList;
9745 {
9746     FILE *f;
9747     char buf[MSG_SIZ];
9748
9749     if (strcmp(filename, "-") == 0) {
9750         f = stdin;
9751         title = "stdin";
9752     } else {
9753         f = fopen(filename, "rb");
9754         if (f == NULL) {
9755           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9756             DisplayError(buf, errno);
9757             return FALSE;
9758         }
9759     }
9760     if (fseek(f, 0, 0) == -1) {
9761         /* f is not seekable; probably a pipe */
9762         useList = FALSE;
9763     }
9764     if (useList && n == 0) {
9765         int error = GameListBuild(f);
9766         if (error) {
9767             DisplayError(_("Cannot build game list"), error);
9768         } else if (!ListEmpty(&gameList) &&
9769                    ((ListGame *) gameList.tailPred)->number > 1) {
9770             GameListPopUp(f, title);
9771             return TRUE;
9772         }
9773         GameListDestroy();
9774         n = 1;
9775     }
9776     if (n == 0) n = 1;
9777     return LoadGame(f, n, title, FALSE);
9778 }
9779
9780
9781 void
9782 MakeRegisteredMove()
9783 {
9784     int fromX, fromY, toX, toY;
9785     char promoChar;
9786     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9787         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9788           case CMAIL_MOVE:
9789           case CMAIL_DRAW:
9790             if (appData.debugMode)
9791               fprintf(debugFP, "Restoring %s for game %d\n",
9792                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9793     
9794             thinkOutput[0] = NULLCHAR;
9795             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9796             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9797             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9798             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9799             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9800             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9801             MakeMove(fromX, fromY, toX, toY, promoChar);
9802             ShowMove(fromX, fromY, toX, toY);
9803               
9804             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9805               case MT_NONE:
9806               case MT_CHECK:
9807                 break;
9808                 
9809               case MT_CHECKMATE:
9810               case MT_STAINMATE:
9811                 if (WhiteOnMove(currentMove)) {
9812                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9813                 } else {
9814                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9815                 }
9816                 break;
9817                 
9818               case MT_STALEMATE:
9819                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9820                 break;
9821             }
9822
9823             break;
9824             
9825           case CMAIL_RESIGN:
9826             if (WhiteOnMove(currentMove)) {
9827                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9828             } else {
9829                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9830             }
9831             break;
9832             
9833           case CMAIL_ACCEPT:
9834             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9835             break;
9836               
9837           default:
9838             break;
9839         }
9840     }
9841
9842     return;
9843 }
9844
9845 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9846 int
9847 CmailLoadGame(f, gameNumber, title, useList)
9848      FILE *f;
9849      int gameNumber;
9850      char *title;
9851      int useList;
9852 {
9853     int retVal;
9854
9855     if (gameNumber > nCmailGames) {
9856         DisplayError(_("No more games in this message"), 0);
9857         return FALSE;
9858     }
9859     if (f == lastLoadGameFP) {
9860         int offset = gameNumber - lastLoadGameNumber;
9861         if (offset == 0) {
9862             cmailMsg[0] = NULLCHAR;
9863             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9864                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9865                 nCmailMovesRegistered--;
9866             }
9867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9868             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9869                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9870             }
9871         } else {
9872             if (! RegisterMove()) return FALSE;
9873         }
9874     }
9875
9876     retVal = LoadGame(f, gameNumber, title, useList);
9877
9878     /* Make move registered during previous look at this game, if any */
9879     MakeRegisteredMove();
9880
9881     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9882         commentList[currentMove]
9883           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9884         DisplayComment(currentMove - 1, commentList[currentMove]);
9885     }
9886
9887     return retVal;
9888 }
9889
9890 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9891 int
9892 ReloadGame(offset)
9893      int offset;
9894 {
9895     int gameNumber = lastLoadGameNumber + offset;
9896     if (lastLoadGameFP == NULL) {
9897         DisplayError(_("No game has been loaded yet"), 0);
9898         return FALSE;
9899     }
9900     if (gameNumber <= 0) {
9901         DisplayError(_("Can't back up any further"), 0);
9902         return FALSE;
9903     }
9904     if (cmailMsgLoaded) {
9905         return CmailLoadGame(lastLoadGameFP, gameNumber,
9906                              lastLoadGameTitle, lastLoadGameUseList);
9907     } else {
9908         return LoadGame(lastLoadGameFP, gameNumber,
9909                         lastLoadGameTitle, lastLoadGameUseList);
9910     }
9911 }
9912
9913
9914
9915 /* Load the nth game from open file f */
9916 int
9917 LoadGame(f, gameNumber, title, useList)
9918      FILE *f;
9919      int gameNumber;
9920      char *title;
9921      int useList;
9922 {
9923     ChessMove cm;
9924     char buf[MSG_SIZ];
9925     int gn = gameNumber;
9926     ListGame *lg = NULL;
9927     int numPGNTags = 0;
9928     int err;
9929     GameMode oldGameMode;
9930     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9931
9932     if (appData.debugMode) 
9933         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9934
9935     if (gameMode == Training )
9936         SetTrainingModeOff();
9937
9938     oldGameMode = gameMode;
9939     if (gameMode != BeginningOfGame) {
9940       Reset(FALSE, TRUE);
9941     }
9942
9943     gameFileFP = f;
9944     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9945         fclose(lastLoadGameFP);
9946     }
9947
9948     if (useList) {
9949         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9950         
9951         if (lg) {
9952             fseek(f, lg->offset, 0);
9953             GameListHighlight(gameNumber);
9954             gn = 1;
9955         }
9956         else {
9957             DisplayError(_("Game number out of range"), 0);
9958             return FALSE;
9959         }
9960     } else {
9961         GameListDestroy();
9962         if (fseek(f, 0, 0) == -1) {
9963             if (f == lastLoadGameFP ?
9964                 gameNumber == lastLoadGameNumber + 1 :
9965                 gameNumber == 1) {
9966                 gn = 1;
9967             } else {
9968                 DisplayError(_("Can't seek on game file"), 0);
9969                 return FALSE;
9970             }
9971         }
9972     }
9973     lastLoadGameFP = f;
9974     lastLoadGameNumber = gameNumber;
9975     strcpy(lastLoadGameTitle, title);
9976     lastLoadGameUseList = useList;
9977
9978     yynewfile(f);
9979
9980     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9981       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9982                 lg->gameInfo.black);
9983             DisplayTitle(buf);
9984     } else if (*title != NULLCHAR) {
9985         if (gameNumber > 1) {
9986             sprintf(buf, "%s %d", title, gameNumber);
9987             DisplayTitle(buf);
9988         } else {
9989             DisplayTitle(title);
9990         }
9991     }
9992
9993     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9994         gameMode = PlayFromGameFile;
9995         ModeHighlight();
9996     }
9997
9998     currentMove = forwardMostMove = backwardMostMove = 0;
9999     CopyBoard(boards[0], initialPosition);
10000     StopClocks();
10001
10002     /*
10003      * Skip the first gn-1 games in the file.
10004      * Also skip over anything that precedes an identifiable 
10005      * start of game marker, to avoid being confused by 
10006      * garbage at the start of the file.  Currently 
10007      * recognized start of game markers are the move number "1",
10008      * the pattern "gnuchess .* game", the pattern
10009      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10010      * A game that starts with one of the latter two patterns
10011      * will also have a move number 1, possibly
10012      * following a position diagram.
10013      * 5-4-02: Let's try being more lenient and allowing a game to
10014      * start with an unnumbered move.  Does that break anything?
10015      */
10016     cm = lastLoadGameStart = (ChessMove) 0;
10017     while (gn > 0) {
10018         yyboardindex = forwardMostMove;
10019         cm = (ChessMove) yylex();
10020         switch (cm) {
10021           case (ChessMove) 0:
10022             if (cmailMsgLoaded) {
10023                 nCmailGames = CMAIL_MAX_GAMES - gn;
10024             } else {
10025                 Reset(TRUE, TRUE);
10026                 DisplayError(_("Game not found in file"), 0);
10027             }
10028             return FALSE;
10029
10030           case GNUChessGame:
10031           case XBoardGame:
10032             gn--;
10033             lastLoadGameStart = cm;
10034             break;
10035             
10036           case MoveNumberOne:
10037             switch (lastLoadGameStart) {
10038               case GNUChessGame:
10039               case XBoardGame:
10040               case PGNTag:
10041                 break;
10042               case MoveNumberOne:
10043               case (ChessMove) 0:
10044                 gn--;           /* count this game */
10045                 lastLoadGameStart = cm;
10046                 break;
10047               default:
10048                 /* impossible */
10049                 break;
10050             }
10051             break;
10052
10053           case PGNTag:
10054             switch (lastLoadGameStart) {
10055               case GNUChessGame:
10056               case PGNTag:
10057               case MoveNumberOne:
10058               case (ChessMove) 0:
10059                 gn--;           /* count this game */
10060                 lastLoadGameStart = cm;
10061                 break;
10062               case XBoardGame:
10063                 lastLoadGameStart = cm; /* game counted already */
10064                 break;
10065               default:
10066                 /* impossible */
10067                 break;
10068             }
10069             if (gn > 0) {
10070                 do {
10071                     yyboardindex = forwardMostMove;
10072                     cm = (ChessMove) yylex();
10073                 } while (cm == PGNTag || cm == Comment);
10074             }
10075             break;
10076
10077           case WhiteWins:
10078           case BlackWins:
10079           case GameIsDrawn:
10080             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10081                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10082                     != CMAIL_OLD_RESULT) {
10083                     nCmailResults ++ ;
10084                     cmailResult[  CMAIL_MAX_GAMES
10085                                 - gn - 1] = CMAIL_OLD_RESULT;
10086                 }
10087             }
10088             break;
10089
10090           case NormalMove:
10091             /* Only a NormalMove can be at the start of a game
10092              * without a position diagram. */
10093             if (lastLoadGameStart == (ChessMove) 0) {
10094               gn--;
10095               lastLoadGameStart = MoveNumberOne;
10096             }
10097             break;
10098
10099           default:
10100             break;
10101         }
10102     }
10103     
10104     if (appData.debugMode)
10105       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10106
10107     if (cm == XBoardGame) {
10108         /* Skip any header junk before position diagram and/or move 1 */
10109         for (;;) {
10110             yyboardindex = forwardMostMove;
10111             cm = (ChessMove) yylex();
10112
10113             if (cm == (ChessMove) 0 ||
10114                 cm == GNUChessGame || cm == XBoardGame) {
10115                 /* Empty game; pretend end-of-file and handle later */
10116                 cm = (ChessMove) 0;
10117                 break;
10118             }
10119
10120             if (cm == MoveNumberOne || cm == PositionDiagram ||
10121                 cm == PGNTag || cm == Comment)
10122               break;
10123         }
10124     } else if (cm == GNUChessGame) {
10125         if (gameInfo.event != NULL) {
10126             free(gameInfo.event);
10127         }
10128         gameInfo.event = StrSave(yy_text);
10129     }   
10130
10131     startedFromSetupPosition = FALSE;
10132     while (cm == PGNTag) {
10133         if (appData.debugMode) 
10134           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10135         err = ParsePGNTag(yy_text, &gameInfo);
10136         if (!err) numPGNTags++;
10137
10138         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10139         if(gameInfo.variant != oldVariant) {
10140             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10141             InitPosition(TRUE);
10142             oldVariant = gameInfo.variant;
10143             if (appData.debugMode) 
10144               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10145         }
10146
10147
10148         if (gameInfo.fen != NULL) {
10149           Board initial_position;
10150           startedFromSetupPosition = TRUE;
10151           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10152             Reset(TRUE, TRUE);
10153             DisplayError(_("Bad FEN position in file"), 0);
10154             return FALSE;
10155           }
10156           CopyBoard(boards[0], initial_position);
10157           if (blackPlaysFirst) {
10158             currentMove = forwardMostMove = backwardMostMove = 1;
10159             CopyBoard(boards[1], initial_position);
10160             strcpy(moveList[0], "");
10161             strcpy(parseList[0], "");
10162             timeRemaining[0][1] = whiteTimeRemaining;
10163             timeRemaining[1][1] = blackTimeRemaining;
10164             if (commentList[0] != NULL) {
10165               commentList[1] = commentList[0];
10166               commentList[0] = NULL;
10167             }
10168           } else {
10169             currentMove = forwardMostMove = backwardMostMove = 0;
10170           }
10171           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10172           {   int i;
10173               initialRulePlies = FENrulePlies;
10174               for( i=0; i< nrCastlingRights; i++ )
10175                   initialRights[i] = initial_position[CASTLING][i];
10176           }
10177           yyboardindex = forwardMostMove;
10178           free(gameInfo.fen);
10179           gameInfo.fen = NULL;
10180         }
10181
10182         yyboardindex = forwardMostMove;
10183         cm = (ChessMove) yylex();
10184
10185         /* Handle comments interspersed among the tags */
10186         while (cm == Comment) {
10187             char *p;
10188             if (appData.debugMode) 
10189               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10190             p = yy_text;
10191             AppendComment(currentMove, p, FALSE);
10192             yyboardindex = forwardMostMove;
10193             cm = (ChessMove) yylex();
10194         }
10195     }
10196
10197     /* don't rely on existence of Event tag since if game was
10198      * pasted from clipboard the Event tag may not exist
10199      */
10200     if (numPGNTags > 0){
10201         char *tags;
10202         if (gameInfo.variant == VariantNormal) {
10203           VariantClass v = StringToVariant(gameInfo.event);
10204           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10205           if(v < VariantShogi) gameInfo.variant = v;
10206         }
10207         if (!matchMode) {
10208           if( appData.autoDisplayTags ) {
10209             tags = PGNTags(&gameInfo);
10210             TagsPopUp(tags, CmailMsg());
10211             free(tags);
10212           }
10213         }
10214     } else {
10215         /* Make something up, but don't display it now */
10216         SetGameInfo();
10217         TagsPopDown();
10218     }
10219
10220     if (cm == PositionDiagram) {
10221         int i, j;
10222         char *p;
10223         Board initial_position;
10224
10225         if (appData.debugMode)
10226           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10227
10228         if (!startedFromSetupPosition) {
10229             p = yy_text;
10230             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10231               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10232                 switch (*p) {
10233                   case '[':
10234                   case '-':
10235                   case ' ':
10236                   case '\t':
10237                   case '\n':
10238                   case '\r':
10239                     break;
10240                   default:
10241                     initial_position[i][j++] = CharToPiece(*p);
10242                     break;
10243                 }
10244             while (*p == ' ' || *p == '\t' ||
10245                    *p == '\n' || *p == '\r') p++;
10246         
10247             if (strncmp(p, "black", strlen("black"))==0)
10248               blackPlaysFirst = TRUE;
10249             else
10250               blackPlaysFirst = FALSE;
10251             startedFromSetupPosition = TRUE;
10252         
10253             CopyBoard(boards[0], initial_position);
10254             if (blackPlaysFirst) {
10255                 currentMove = forwardMostMove = backwardMostMove = 1;
10256                 CopyBoard(boards[1], initial_position);
10257                 strcpy(moveList[0], "");
10258                 strcpy(parseList[0], "");
10259                 timeRemaining[0][1] = whiteTimeRemaining;
10260                 timeRemaining[1][1] = blackTimeRemaining;
10261                 if (commentList[0] != NULL) {
10262                     commentList[1] = commentList[0];
10263                     commentList[0] = NULL;
10264                 }
10265             } else {
10266                 currentMove = forwardMostMove = backwardMostMove = 0;
10267             }
10268         }
10269         yyboardindex = forwardMostMove;
10270         cm = (ChessMove) yylex();
10271     }
10272
10273     if (first.pr == NoProc) {
10274         StartChessProgram(&first);
10275     }
10276     InitChessProgram(&first, FALSE);
10277     SendToProgram("force\n", &first);
10278     if (startedFromSetupPosition) {
10279         SendBoard(&first, forwardMostMove);
10280     if (appData.debugMode) {
10281         fprintf(debugFP, "Load Game\n");
10282     }
10283         DisplayBothClocks();
10284     }      
10285
10286     /* [HGM] server: flag to write setup moves in broadcast file as one */
10287     loadFlag = appData.suppressLoadMoves;
10288
10289     while (cm == Comment) {
10290         char *p;
10291         if (appData.debugMode) 
10292           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10293         p = yy_text;
10294         AppendComment(currentMove, p, FALSE);
10295         yyboardindex = forwardMostMove;
10296         cm = (ChessMove) yylex();
10297     }
10298
10299     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10300         cm == WhiteWins || cm == BlackWins ||
10301         cm == GameIsDrawn || cm == GameUnfinished) {
10302         DisplayMessage("", _("No moves in game"));
10303         if (cmailMsgLoaded) {
10304             if (appData.debugMode)
10305               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10306             ClearHighlights();
10307             flipView = FALSE;
10308         }
10309         DrawPosition(FALSE, boards[currentMove]);
10310         DisplayBothClocks();
10311         gameMode = EditGame;
10312         ModeHighlight();
10313         gameFileFP = NULL;
10314         cmailOldMove = 0;
10315         return TRUE;
10316     }
10317
10318     // [HGM] PV info: routine tests if comment empty
10319     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10320         DisplayComment(currentMove - 1, commentList[currentMove]);
10321     }
10322     if (!matchMode && appData.timeDelay != 0) 
10323       DrawPosition(FALSE, boards[currentMove]);
10324
10325     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10326       programStats.ok_to_send = 1;
10327     }
10328
10329     /* if the first token after the PGN tags is a move
10330      * and not move number 1, retrieve it from the parser 
10331      */
10332     if (cm != MoveNumberOne)
10333         LoadGameOneMove(cm);
10334
10335     /* load the remaining moves from the file */
10336     while (LoadGameOneMove((ChessMove)0)) {
10337       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10338       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10339     }
10340
10341     /* rewind to the start of the game */
10342     currentMove = backwardMostMove;
10343
10344     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10345
10346     if (oldGameMode == AnalyzeFile ||
10347         oldGameMode == AnalyzeMode) {
10348       AnalyzeFileEvent();
10349     }
10350
10351     if (matchMode || appData.timeDelay == 0) {
10352       ToEndEvent();
10353       gameMode = EditGame;
10354       ModeHighlight();
10355     } else if (appData.timeDelay > 0) {
10356       AutoPlayGameLoop();
10357     }
10358
10359     if (appData.debugMode) 
10360         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10361
10362     loadFlag = 0; /* [HGM] true game starts */
10363     return TRUE;
10364 }
10365
10366 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10367 int
10368 ReloadPosition(offset)
10369      int offset;
10370 {
10371     int positionNumber = lastLoadPositionNumber + offset;
10372     if (lastLoadPositionFP == NULL) {
10373         DisplayError(_("No position has been loaded yet"), 0);
10374         return FALSE;
10375     }
10376     if (positionNumber <= 0) {
10377         DisplayError(_("Can't back up any further"), 0);
10378         return FALSE;
10379     }
10380     return LoadPosition(lastLoadPositionFP, positionNumber,
10381                         lastLoadPositionTitle);
10382 }
10383
10384 /* Load the nth position from the given file */
10385 int
10386 LoadPositionFromFile(filename, n, title)
10387      char *filename;
10388      int n;
10389      char *title;
10390 {
10391     FILE *f;
10392     char buf[MSG_SIZ];
10393
10394     if (strcmp(filename, "-") == 0) {
10395         return LoadPosition(stdin, n, "stdin");
10396     } else {
10397         f = fopen(filename, "rb");
10398         if (f == NULL) {
10399             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10400             DisplayError(buf, errno);
10401             return FALSE;
10402         } else {
10403             return LoadPosition(f, n, title);
10404         }
10405     }
10406 }
10407
10408 /* Load the nth position from the given open file, and close it */
10409 int
10410 LoadPosition(f, positionNumber, title)
10411      FILE *f;
10412      int positionNumber;
10413      char *title;
10414 {
10415     char *p, line[MSG_SIZ];
10416     Board initial_position;
10417     int i, j, fenMode, pn;
10418     
10419     if (gameMode == Training )
10420         SetTrainingModeOff();
10421
10422     if (gameMode != BeginningOfGame) {
10423         Reset(FALSE, TRUE);
10424     }
10425     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10426         fclose(lastLoadPositionFP);
10427     }
10428     if (positionNumber == 0) positionNumber = 1;
10429     lastLoadPositionFP = f;
10430     lastLoadPositionNumber = positionNumber;
10431     strcpy(lastLoadPositionTitle, title);
10432     if (first.pr == NoProc) {
10433       StartChessProgram(&first);
10434       InitChessProgram(&first, FALSE);
10435     }    
10436     pn = positionNumber;
10437     if (positionNumber < 0) {
10438         /* Negative position number means to seek to that byte offset */
10439         if (fseek(f, -positionNumber, 0) == -1) {
10440             DisplayError(_("Can't seek on position file"), 0);
10441             return FALSE;
10442         };
10443         pn = 1;
10444     } else {
10445         if (fseek(f, 0, 0) == -1) {
10446             if (f == lastLoadPositionFP ?
10447                 positionNumber == lastLoadPositionNumber + 1 :
10448                 positionNumber == 1) {
10449                 pn = 1;
10450             } else {
10451                 DisplayError(_("Can't seek on position file"), 0);
10452                 return FALSE;
10453             }
10454         }
10455     }
10456     /* See if this file is FEN or old-style xboard */
10457     if (fgets(line, MSG_SIZ, f) == NULL) {
10458         DisplayError(_("Position not found in file"), 0);
10459         return FALSE;
10460     }
10461     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10462     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10463
10464     if (pn >= 2) {
10465         if (fenMode || line[0] == '#') pn--;
10466         while (pn > 0) {
10467             /* skip positions before number pn */
10468             if (fgets(line, MSG_SIZ, f) == NULL) {
10469                 Reset(TRUE, TRUE);
10470                 DisplayError(_("Position not found in file"), 0);
10471                 return FALSE;
10472             }
10473             if (fenMode || line[0] == '#') pn--;
10474         }
10475     }
10476
10477     if (fenMode) {
10478         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10479             DisplayError(_("Bad FEN position in file"), 0);
10480             return FALSE;
10481         }
10482     } else {
10483         (void) fgets(line, MSG_SIZ, f);
10484         (void) fgets(line, MSG_SIZ, f);
10485     
10486         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10487             (void) fgets(line, MSG_SIZ, f);
10488             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10489                 if (*p == ' ')
10490                   continue;
10491                 initial_position[i][j++] = CharToPiece(*p);
10492             }
10493         }
10494     
10495         blackPlaysFirst = FALSE;
10496         if (!feof(f)) {
10497             (void) fgets(line, MSG_SIZ, f);
10498             if (strncmp(line, "black", strlen("black"))==0)
10499               blackPlaysFirst = TRUE;
10500         }
10501     }
10502     startedFromSetupPosition = TRUE;
10503     
10504     SendToProgram("force\n", &first);
10505     CopyBoard(boards[0], initial_position);
10506     if (blackPlaysFirst) {
10507         currentMove = forwardMostMove = backwardMostMove = 1;
10508         strcpy(moveList[0], "");
10509         strcpy(parseList[0], "");
10510         CopyBoard(boards[1], initial_position);
10511         DisplayMessage("", _("Black to play"));
10512     } else {
10513         currentMove = forwardMostMove = backwardMostMove = 0;
10514         DisplayMessage("", _("White to play"));
10515     }
10516     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10517     SendBoard(&first, forwardMostMove);
10518     if (appData.debugMode) {
10519 int i, j;
10520   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10521   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10522         fprintf(debugFP, "Load Position\n");
10523     }
10524
10525     if (positionNumber > 1) {
10526         sprintf(line, "%s %d", title, positionNumber);
10527         DisplayTitle(line);
10528     } else {
10529         DisplayTitle(title);
10530     }
10531     gameMode = EditGame;
10532     ModeHighlight();
10533     ResetClocks();
10534     timeRemaining[0][1] = whiteTimeRemaining;
10535     timeRemaining[1][1] = blackTimeRemaining;
10536     DrawPosition(FALSE, boards[currentMove]);
10537    
10538     return TRUE;
10539 }
10540
10541
10542 void
10543 CopyPlayerNameIntoFileName(dest, src)
10544      char **dest, *src;
10545 {
10546     while (*src != NULLCHAR && *src != ',') {
10547         if (*src == ' ') {
10548             *(*dest)++ = '_';
10549             src++;
10550         } else {
10551             *(*dest)++ = *src++;
10552         }
10553     }
10554 }
10555
10556 char *DefaultFileName(ext)
10557      char *ext;
10558 {
10559     static char def[MSG_SIZ];
10560     char *p;
10561
10562     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10563         p = def;
10564         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10565         *p++ = '-';
10566         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10567         *p++ = '.';
10568         strcpy(p, ext);
10569     } else {
10570         def[0] = NULLCHAR;
10571     }
10572     return def;
10573 }
10574
10575 /* Save the current game to the given file */
10576 int
10577 SaveGameToFile(filename, append)
10578      char *filename;
10579      int append;
10580 {
10581     FILE *f;
10582     char buf[MSG_SIZ];
10583
10584     if (strcmp(filename, "-") == 0) {
10585         return SaveGame(stdout, 0, NULL);
10586     } else {
10587         f = fopen(filename, append ? "a" : "w");
10588         if (f == NULL) {
10589             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10590             DisplayError(buf, errno);
10591             return FALSE;
10592         } else {
10593             return SaveGame(f, 0, NULL);
10594         }
10595     }
10596 }
10597
10598 char *
10599 SavePart(str)
10600      char *str;
10601 {
10602     static char buf[MSG_SIZ];
10603     char *p;
10604     
10605     p = strchr(str, ' ');
10606     if (p == NULL) return str;
10607     strncpy(buf, str, p - str);
10608     buf[p - str] = NULLCHAR;
10609     return buf;
10610 }
10611
10612 #define PGN_MAX_LINE 75
10613
10614 #define PGN_SIDE_WHITE  0
10615 #define PGN_SIDE_BLACK  1
10616
10617 /* [AS] */
10618 static int FindFirstMoveOutOfBook( int side )
10619 {
10620     int result = -1;
10621
10622     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10623         int index = backwardMostMove;
10624         int has_book_hit = 0;
10625
10626         if( (index % 2) != side ) {
10627             index++;
10628         }
10629
10630         while( index < forwardMostMove ) {
10631             /* Check to see if engine is in book */
10632             int depth = pvInfoList[index].depth;
10633             int score = pvInfoList[index].score;
10634             int in_book = 0;
10635
10636             if( depth <= 2 ) {
10637                 in_book = 1;
10638             }
10639             else if( score == 0 && depth == 63 ) {
10640                 in_book = 1; /* Zappa */
10641             }
10642             else if( score == 2 && depth == 99 ) {
10643                 in_book = 1; /* Abrok */
10644             }
10645
10646             has_book_hit += in_book;
10647
10648             if( ! in_book ) {
10649                 result = index;
10650
10651                 break;
10652             }
10653
10654             index += 2;
10655         }
10656     }
10657
10658     return result;
10659 }
10660
10661 /* [AS] */
10662 void GetOutOfBookInfo( char * buf )
10663 {
10664     int oob[2];
10665     int i;
10666     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10667
10668     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10669     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10670
10671     *buf = '\0';
10672
10673     if( oob[0] >= 0 || oob[1] >= 0 ) {
10674         for( i=0; i<2; i++ ) {
10675             int idx = oob[i];
10676
10677             if( idx >= 0 ) {
10678                 if( i > 0 && oob[0] >= 0 ) {
10679                     strcat( buf, "   " );
10680                 }
10681
10682                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10683                 sprintf( buf+strlen(buf), "%s%.2f", 
10684                     pvInfoList[idx].score >= 0 ? "+" : "",
10685                     pvInfoList[idx].score / 100.0 );
10686             }
10687         }
10688     }
10689 }
10690
10691 /* Save game in PGN style and close the file */
10692 int
10693 SaveGamePGN(f)
10694      FILE *f;
10695 {
10696     int i, offset, linelen, newblock;
10697     time_t tm;
10698 //    char *movetext;
10699     char numtext[32];
10700     int movelen, numlen, blank;
10701     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10702
10703     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10704     
10705     tm = time((time_t *) NULL);
10706     
10707     PrintPGNTags(f, &gameInfo);
10708     
10709     if (backwardMostMove > 0 || startedFromSetupPosition) {
10710         char *fen = PositionToFEN(backwardMostMove, NULL);
10711         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10712         fprintf(f, "\n{--------------\n");
10713         PrintPosition(f, backwardMostMove);
10714         fprintf(f, "--------------}\n");
10715         free(fen);
10716     }
10717     else {
10718         /* [AS] Out of book annotation */
10719         if( appData.saveOutOfBookInfo ) {
10720             char buf[64];
10721
10722             GetOutOfBookInfo( buf );
10723
10724             if( buf[0] != '\0' ) {
10725                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10726             }
10727         }
10728
10729         fprintf(f, "\n");
10730     }
10731
10732     i = backwardMostMove;
10733     linelen = 0;
10734     newblock = TRUE;
10735
10736     while (i < forwardMostMove) {
10737         /* Print comments preceding this move */
10738         if (commentList[i] != NULL) {
10739             if (linelen > 0) fprintf(f, "\n");
10740             fprintf(f, "%s", commentList[i]);
10741             linelen = 0;
10742             newblock = TRUE;
10743         }
10744
10745         /* Format move number */
10746         if ((i % 2) == 0) {
10747             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10748         } else {
10749             if (newblock) {
10750                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10751             } else {
10752                 numtext[0] = NULLCHAR;
10753             }
10754         }
10755         numlen = strlen(numtext);
10756         newblock = FALSE;
10757
10758         /* Print move number */
10759         blank = linelen > 0 && numlen > 0;
10760         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10761             fprintf(f, "\n");
10762             linelen = 0;
10763             blank = 0;
10764         }
10765         if (blank) {
10766             fprintf(f, " ");
10767             linelen++;
10768         }
10769         fprintf(f, "%s", numtext);
10770         linelen += numlen;
10771
10772         /* Get move */
10773         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10774         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10775
10776         /* Print move */
10777         blank = linelen > 0 && movelen > 0;
10778         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10779             fprintf(f, "\n");
10780             linelen = 0;
10781             blank = 0;
10782         }
10783         if (blank) {
10784             fprintf(f, " ");
10785             linelen++;
10786         }
10787         fprintf(f, "%s", move_buffer);
10788         linelen += movelen;
10789
10790         /* [AS] Add PV info if present */
10791         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10792             /* [HGM] add time */
10793             char buf[MSG_SIZ]; int seconds;
10794
10795             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10796
10797             if( seconds <= 0) buf[0] = 0; else
10798             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10799                 seconds = (seconds + 4)/10; // round to full seconds
10800                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10801                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10802             }
10803
10804             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10805                 pvInfoList[i].score >= 0 ? "+" : "",
10806                 pvInfoList[i].score / 100.0,
10807                 pvInfoList[i].depth,
10808                 buf );
10809
10810             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10811
10812             /* Print score/depth */
10813             blank = linelen > 0 && movelen > 0;
10814             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10815                 fprintf(f, "\n");
10816                 linelen = 0;
10817                 blank = 0;
10818             }
10819             if (blank) {
10820                 fprintf(f, " ");
10821                 linelen++;
10822             }
10823             fprintf(f, "%s", move_buffer);
10824             linelen += movelen;
10825         }
10826
10827         i++;
10828     }
10829     
10830     /* Start a new line */
10831     if (linelen > 0) fprintf(f, "\n");
10832
10833     /* Print comments after last move */
10834     if (commentList[i] != NULL) {
10835         fprintf(f, "%s\n", commentList[i]);
10836     }
10837
10838     /* Print result */
10839     if (gameInfo.resultDetails != NULL &&
10840         gameInfo.resultDetails[0] != NULLCHAR) {
10841         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10842                 PGNResult(gameInfo.result));
10843     } else {
10844         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10845     }
10846
10847     fclose(f);
10848     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10849     return TRUE;
10850 }
10851
10852 /* Save game in old style and close the file */
10853 int
10854 SaveGameOldStyle(f)
10855      FILE *f;
10856 {
10857     int i, offset;
10858     time_t tm;
10859     
10860     tm = time((time_t *) NULL);
10861     
10862     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10863     PrintOpponents(f);
10864     
10865     if (backwardMostMove > 0 || startedFromSetupPosition) {
10866         fprintf(f, "\n[--------------\n");
10867         PrintPosition(f, backwardMostMove);
10868         fprintf(f, "--------------]\n");
10869     } else {
10870         fprintf(f, "\n");
10871     }
10872
10873     i = backwardMostMove;
10874     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10875
10876     while (i < forwardMostMove) {
10877         if (commentList[i] != NULL) {
10878             fprintf(f, "[%s]\n", commentList[i]);
10879         }
10880
10881         if ((i % 2) == 1) {
10882             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10883             i++;
10884         } else {
10885             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10886             i++;
10887             if (commentList[i] != NULL) {
10888                 fprintf(f, "\n");
10889                 continue;
10890             }
10891             if (i >= forwardMostMove) {
10892                 fprintf(f, "\n");
10893                 break;
10894             }
10895             fprintf(f, "%s\n", parseList[i]);
10896             i++;
10897         }
10898     }
10899     
10900     if (commentList[i] != NULL) {
10901         fprintf(f, "[%s]\n", commentList[i]);
10902     }
10903
10904     /* This isn't really the old style, but it's close enough */
10905     if (gameInfo.resultDetails != NULL &&
10906         gameInfo.resultDetails[0] != NULLCHAR) {
10907         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10908                 gameInfo.resultDetails);
10909     } else {
10910         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10911     }
10912
10913     fclose(f);
10914     return TRUE;
10915 }
10916
10917 /* Save the current game to open file f and close the file */
10918 int
10919 SaveGame(f, dummy, dummy2)
10920      FILE *f;
10921      int dummy;
10922      char *dummy2;
10923 {
10924     if (gameMode == EditPosition) EditPositionDone(TRUE);
10925     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10926     if (appData.oldSaveStyle)
10927       return SaveGameOldStyle(f);
10928     else
10929       return SaveGamePGN(f);
10930 }
10931
10932 /* Save the current position to the given file */
10933 int
10934 SavePositionToFile(filename)
10935      char *filename;
10936 {
10937     FILE *f;
10938     char buf[MSG_SIZ];
10939
10940     if (strcmp(filename, "-") == 0) {
10941         return SavePosition(stdout, 0, NULL);
10942     } else {
10943         f = fopen(filename, "a");
10944         if (f == NULL) {
10945             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10946             DisplayError(buf, errno);
10947             return FALSE;
10948         } else {
10949             SavePosition(f, 0, NULL);
10950             return TRUE;
10951         }
10952     }
10953 }
10954
10955 /* Save the current position to the given open file and close the file */
10956 int
10957 SavePosition(f, dummy, dummy2)
10958      FILE *f;
10959      int dummy;
10960      char *dummy2;
10961 {
10962     time_t tm;
10963     char *fen;
10964     
10965     if (gameMode == EditPosition) EditPositionDone(TRUE);
10966     if (appData.oldSaveStyle) {
10967         tm = time((time_t *) NULL);
10968     
10969         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10970         PrintOpponents(f);
10971         fprintf(f, "[--------------\n");
10972         PrintPosition(f, currentMove);
10973         fprintf(f, "--------------]\n");
10974     } else {
10975         fen = PositionToFEN(currentMove, NULL);
10976         fprintf(f, "%s\n", fen);
10977         free(fen);
10978     }
10979     fclose(f);
10980     return TRUE;
10981 }
10982
10983 void
10984 ReloadCmailMsgEvent(unregister)
10985      int unregister;
10986 {
10987 #if !WIN32
10988     static char *inFilename = NULL;
10989     static char *outFilename;
10990     int i;
10991     struct stat inbuf, outbuf;
10992     int status;
10993     
10994     /* Any registered moves are unregistered if unregister is set, */
10995     /* i.e. invoked by the signal handler */
10996     if (unregister) {
10997         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10998             cmailMoveRegistered[i] = FALSE;
10999             if (cmailCommentList[i] != NULL) {
11000                 free(cmailCommentList[i]);
11001                 cmailCommentList[i] = NULL;
11002             }
11003         }
11004         nCmailMovesRegistered = 0;
11005     }
11006
11007     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11008         cmailResult[i] = CMAIL_NOT_RESULT;
11009     }
11010     nCmailResults = 0;
11011
11012     if (inFilename == NULL) {
11013         /* Because the filenames are static they only get malloced once  */
11014         /* and they never get freed                                      */
11015         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11016         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11017
11018         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11019         sprintf(outFilename, "%s.out", appData.cmailGameName);
11020     }
11021     
11022     status = stat(outFilename, &outbuf);
11023     if (status < 0) {
11024         cmailMailedMove = FALSE;
11025     } else {
11026         status = stat(inFilename, &inbuf);
11027         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11028     }
11029     
11030     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11031        counts the games, notes how each one terminated, etc.
11032        
11033        It would be nice to remove this kludge and instead gather all
11034        the information while building the game list.  (And to keep it
11035        in the game list nodes instead of having a bunch of fixed-size
11036        parallel arrays.)  Note this will require getting each game's
11037        termination from the PGN tags, as the game list builder does
11038        not process the game moves.  --mann
11039        */
11040     cmailMsgLoaded = TRUE;
11041     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11042     
11043     /* Load first game in the file or popup game menu */
11044     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11045
11046 #endif /* !WIN32 */
11047     return;
11048 }
11049
11050 int
11051 RegisterMove()
11052 {
11053     FILE *f;
11054     char string[MSG_SIZ];
11055
11056     if (   cmailMailedMove
11057         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11058         return TRUE;            /* Allow free viewing  */
11059     }
11060
11061     /* Unregister move to ensure that we don't leave RegisterMove        */
11062     /* with the move registered when the conditions for registering no   */
11063     /* longer hold                                                       */
11064     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11065         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11066         nCmailMovesRegistered --;
11067
11068         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11069           {
11070               free(cmailCommentList[lastLoadGameNumber - 1]);
11071               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11072           }
11073     }
11074
11075     if (cmailOldMove == -1) {
11076         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11077         return FALSE;
11078     }
11079
11080     if (currentMove > cmailOldMove + 1) {
11081         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11082         return FALSE;
11083     }
11084
11085     if (currentMove < cmailOldMove) {
11086         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11087         return FALSE;
11088     }
11089
11090     if (forwardMostMove > currentMove) {
11091         /* Silently truncate extra moves */
11092         TruncateGame();
11093     }
11094
11095     if (   (currentMove == cmailOldMove + 1)
11096         || (   (currentMove == cmailOldMove)
11097             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11098                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11099         if (gameInfo.result != GameUnfinished) {
11100             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11101         }
11102
11103         if (commentList[currentMove] != NULL) {
11104             cmailCommentList[lastLoadGameNumber - 1]
11105               = StrSave(commentList[currentMove]);
11106         }
11107         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11108
11109         if (appData.debugMode)
11110           fprintf(debugFP, "Saving %s for game %d\n",
11111                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11112
11113         sprintf(string,
11114                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11115         
11116         f = fopen(string, "w");
11117         if (appData.oldSaveStyle) {
11118             SaveGameOldStyle(f); /* also closes the file */
11119             
11120             sprintf(string, "%s.pos.out", appData.cmailGameName);
11121             f = fopen(string, "w");
11122             SavePosition(f, 0, NULL); /* also closes the file */
11123         } else {
11124             fprintf(f, "{--------------\n");
11125             PrintPosition(f, currentMove);
11126             fprintf(f, "--------------}\n\n");
11127             
11128             SaveGame(f, 0, NULL); /* also closes the file*/
11129         }
11130         
11131         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11132         nCmailMovesRegistered ++;
11133     } else if (nCmailGames == 1) {
11134         DisplayError(_("You have not made a move yet"), 0);
11135         return FALSE;
11136     }
11137
11138     return TRUE;
11139 }
11140
11141 void
11142 MailMoveEvent()
11143 {
11144 #if !WIN32
11145     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11146     FILE *commandOutput;
11147     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11148     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11149     int nBuffers;
11150     int i;
11151     int archived;
11152     char *arcDir;
11153
11154     if (! cmailMsgLoaded) {
11155         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11156         return;
11157     }
11158
11159     if (nCmailGames == nCmailResults) {
11160         DisplayError(_("No unfinished games"), 0);
11161         return;
11162     }
11163
11164 #if CMAIL_PROHIBIT_REMAIL
11165     if (cmailMailedMove) {
11166         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);
11167         DisplayError(msg, 0);
11168         return;
11169     }
11170 #endif
11171
11172     if (! (cmailMailedMove || RegisterMove())) return;
11173     
11174     if (   cmailMailedMove
11175         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11176         sprintf(string, partCommandString,
11177                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11178         commandOutput = popen(string, "r");
11179
11180         if (commandOutput == NULL) {
11181             DisplayError(_("Failed to invoke cmail"), 0);
11182         } else {
11183             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11184                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11185             }
11186             if (nBuffers > 1) {
11187                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11188                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11189                 nBytes = MSG_SIZ - 1;
11190             } else {
11191                 (void) memcpy(msg, buffer, nBytes);
11192             }
11193             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11194
11195             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11196                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11197
11198                 archived = TRUE;
11199                 for (i = 0; i < nCmailGames; i ++) {
11200                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11201                         archived = FALSE;
11202                     }
11203                 }
11204                 if (   archived
11205                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11206                         != NULL)) {
11207                     sprintf(buffer, "%s/%s.%s.archive",
11208                             arcDir,
11209                             appData.cmailGameName,
11210                             gameInfo.date);
11211                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11212                     cmailMsgLoaded = FALSE;
11213                 }
11214             }
11215
11216             DisplayInformation(msg);
11217             pclose(commandOutput);
11218         }
11219     } else {
11220         if ((*cmailMsg) != '\0') {
11221             DisplayInformation(cmailMsg);
11222         }
11223     }
11224
11225     return;
11226 #endif /* !WIN32 */
11227 }
11228
11229 char *
11230 CmailMsg()
11231 {
11232 #if WIN32
11233     return NULL;
11234 #else
11235     int  prependComma = 0;
11236     char number[5];
11237     char string[MSG_SIZ];       /* Space for game-list */
11238     int  i;
11239     
11240     if (!cmailMsgLoaded) return "";
11241
11242     if (cmailMailedMove) {
11243         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11244     } else {
11245         /* Create a list of games left */
11246         sprintf(string, "[");
11247         for (i = 0; i < nCmailGames; i ++) {
11248             if (! (   cmailMoveRegistered[i]
11249                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11250                 if (prependComma) {
11251                     sprintf(number, ",%d", i + 1);
11252                 } else {
11253                     sprintf(number, "%d", i + 1);
11254                     prependComma = 1;
11255                 }
11256                 
11257                 strcat(string, number);
11258             }
11259         }
11260         strcat(string, "]");
11261
11262         if (nCmailMovesRegistered + nCmailResults == 0) {
11263             switch (nCmailGames) {
11264               case 1:
11265                 sprintf(cmailMsg,
11266                         _("Still need to make move for game\n"));
11267                 break;
11268                 
11269               case 2:
11270                 sprintf(cmailMsg,
11271                         _("Still need to make moves for both games\n"));
11272                 break;
11273                 
11274               default:
11275                 sprintf(cmailMsg,
11276                         _("Still need to make moves for all %d games\n"),
11277                         nCmailGames);
11278                 break;
11279             }
11280         } else {
11281             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11282               case 1:
11283                 sprintf(cmailMsg,
11284                         _("Still need to make a move for game %s\n"),
11285                         string);
11286                 break;
11287                 
11288               case 0:
11289                 if (nCmailResults == nCmailGames) {
11290                     sprintf(cmailMsg, _("No unfinished games\n"));
11291                 } else {
11292                     sprintf(cmailMsg, _("Ready to send mail\n"));
11293                 }
11294                 break;
11295                 
11296               default:
11297                 sprintf(cmailMsg,
11298                         _("Still need to make moves for games %s\n"),
11299                         string);
11300             }
11301         }
11302     }
11303     return cmailMsg;
11304 #endif /* WIN32 */
11305 }
11306
11307 void
11308 ResetGameEvent()
11309 {
11310     if (gameMode == Training)
11311       SetTrainingModeOff();
11312
11313     Reset(TRUE, TRUE);
11314     cmailMsgLoaded = FALSE;
11315     if (appData.icsActive) {
11316       SendToICS(ics_prefix);
11317       SendToICS("refresh\n");
11318     }
11319 }
11320
11321 void
11322 ExitEvent(status)
11323      int status;
11324 {
11325     exiting++;
11326     if (exiting > 2) {
11327       /* Give up on clean exit */
11328       exit(status);
11329     }
11330     if (exiting > 1) {
11331       /* Keep trying for clean exit */
11332       return;
11333     }
11334
11335     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11336
11337     if (telnetISR != NULL) {
11338       RemoveInputSource(telnetISR);
11339     }
11340     if (icsPR != NoProc) {
11341       DestroyChildProcess(icsPR, TRUE);
11342     }
11343
11344     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11345     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11346
11347     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11348     /* make sure this other one finishes before killing it!                  */
11349     if(endingGame) { int count = 0;
11350         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11351         while(endingGame && count++ < 10) DoSleep(1);
11352         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11353     }
11354
11355     /* Kill off chess programs */
11356     if (first.pr != NoProc) {
11357         ExitAnalyzeMode();
11358         
11359         DoSleep( appData.delayBeforeQuit );
11360         SendToProgram("quit\n", &first);
11361         DoSleep( appData.delayAfterQuit );
11362         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11363     }
11364     if (second.pr != NoProc) {
11365         DoSleep( appData.delayBeforeQuit );
11366         SendToProgram("quit\n", &second);
11367         DoSleep( appData.delayAfterQuit );
11368         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11369     }
11370     if (first.isr != NULL) {
11371         RemoveInputSource(first.isr);
11372     }
11373     if (second.isr != NULL) {
11374         RemoveInputSource(second.isr);
11375     }
11376
11377     ShutDownFrontEnd();
11378     exit(status);
11379 }
11380
11381 void
11382 PauseEvent()
11383 {
11384     if (appData.debugMode)
11385         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11386     if (pausing) {
11387         pausing = FALSE;
11388         ModeHighlight();
11389         if (gameMode == MachinePlaysWhite ||
11390             gameMode == MachinePlaysBlack) {
11391             StartClocks();
11392         } else {
11393             DisplayBothClocks();
11394         }
11395         if (gameMode == PlayFromGameFile) {
11396             if (appData.timeDelay >= 0) 
11397                 AutoPlayGameLoop();
11398         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11399             Reset(FALSE, TRUE);
11400             SendToICS(ics_prefix);
11401             SendToICS("refresh\n");
11402         } else if (currentMove < forwardMostMove) {
11403             ForwardInner(forwardMostMove);
11404         }
11405         pauseExamInvalid = FALSE;
11406     } else {
11407         switch (gameMode) {
11408           default:
11409             return;
11410           case IcsExamining:
11411             pauseExamForwardMostMove = forwardMostMove;
11412             pauseExamInvalid = FALSE;
11413             /* fall through */
11414           case IcsObserving:
11415           case IcsPlayingWhite:
11416           case IcsPlayingBlack:
11417             pausing = TRUE;
11418             ModeHighlight();
11419             return;
11420           case PlayFromGameFile:
11421             (void) StopLoadGameTimer();
11422             pausing = TRUE;
11423             ModeHighlight();
11424             break;
11425           case BeginningOfGame:
11426             if (appData.icsActive) return;
11427             /* else fall through */
11428           case MachinePlaysWhite:
11429           case MachinePlaysBlack:
11430           case TwoMachinesPlay:
11431             if (forwardMostMove == 0)
11432               return;           /* don't pause if no one has moved */
11433             if ((gameMode == MachinePlaysWhite &&
11434                  !WhiteOnMove(forwardMostMove)) ||
11435                 (gameMode == MachinePlaysBlack &&
11436                  WhiteOnMove(forwardMostMove))) {
11437                 StopClocks();
11438             }
11439             pausing = TRUE;
11440             ModeHighlight();
11441             break;
11442         }
11443     }
11444 }
11445
11446 void
11447 EditCommentEvent()
11448 {
11449     char title[MSG_SIZ];
11450
11451     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11452         strcpy(title, _("Edit comment"));
11453     } else {
11454         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11455                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11456                 parseList[currentMove - 1]);
11457     }
11458
11459     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11460 }
11461
11462
11463 void
11464 EditTagsEvent()
11465 {
11466     char *tags = PGNTags(&gameInfo);
11467     EditTagsPopUp(tags);
11468     free(tags);
11469 }
11470
11471 void
11472 AnalyzeModeEvent()
11473 {
11474     if (appData.noChessProgram || gameMode == AnalyzeMode)
11475       return;
11476
11477     if (gameMode != AnalyzeFile) {
11478         if (!appData.icsEngineAnalyze) {
11479                EditGameEvent();
11480                if (gameMode != EditGame) return;
11481         }
11482         ResurrectChessProgram();
11483         SendToProgram("analyze\n", &first);
11484         first.analyzing = TRUE;
11485         /*first.maybeThinking = TRUE;*/
11486         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11487         EngineOutputPopUp();
11488     }
11489     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11490     pausing = FALSE;
11491     ModeHighlight();
11492     SetGameInfo();
11493
11494     StartAnalysisClock();
11495     GetTimeMark(&lastNodeCountTime);
11496     lastNodeCount = 0;
11497 }
11498
11499 void
11500 AnalyzeFileEvent()
11501 {
11502     if (appData.noChessProgram || gameMode == AnalyzeFile)
11503       return;
11504
11505     if (gameMode != AnalyzeMode) {
11506         EditGameEvent();
11507         if (gameMode != EditGame) return;
11508         ResurrectChessProgram();
11509         SendToProgram("analyze\n", &first);
11510         first.analyzing = TRUE;
11511         /*first.maybeThinking = TRUE;*/
11512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11513         EngineOutputPopUp();
11514     }
11515     gameMode = AnalyzeFile;
11516     pausing = FALSE;
11517     ModeHighlight();
11518     SetGameInfo();
11519
11520     StartAnalysisClock();
11521     GetTimeMark(&lastNodeCountTime);
11522     lastNodeCount = 0;
11523 }
11524
11525 void
11526 MachineWhiteEvent()
11527 {
11528     char buf[MSG_SIZ];
11529     char *bookHit = NULL;
11530
11531     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11532       return;
11533
11534
11535     if (gameMode == PlayFromGameFile || 
11536         gameMode == TwoMachinesPlay  || 
11537         gameMode == Training         || 
11538         gameMode == AnalyzeMode      || 
11539         gameMode == EndOfGame)
11540         EditGameEvent();
11541
11542     if (gameMode == EditPosition) 
11543         EditPositionDone(TRUE);
11544
11545     if (!WhiteOnMove(currentMove)) {
11546         DisplayError(_("It is not White's turn"), 0);
11547         return;
11548     }
11549   
11550     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11551       ExitAnalyzeMode();
11552
11553     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11554         gameMode == AnalyzeFile)
11555         TruncateGame();
11556
11557     ResurrectChessProgram();    /* in case it isn't running */
11558     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11559         gameMode = MachinePlaysWhite;
11560         ResetClocks();
11561     } else
11562     gameMode = MachinePlaysWhite;
11563     pausing = FALSE;
11564     ModeHighlight();
11565     SetGameInfo();
11566     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11567     DisplayTitle(buf);
11568     if (first.sendName) {
11569       sprintf(buf, "name %s\n", gameInfo.black);
11570       SendToProgram(buf, &first);
11571     }
11572     if (first.sendTime) {
11573       if (first.useColors) {
11574         SendToProgram("black\n", &first); /*gnu kludge*/
11575       }
11576       SendTimeRemaining(&first, TRUE);
11577     }
11578     if (first.useColors) {
11579       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11580     }
11581     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11582     SetMachineThinkingEnables();
11583     first.maybeThinking = TRUE;
11584     StartClocks();
11585     firstMove = FALSE;
11586
11587     if (appData.autoFlipView && !flipView) {
11588       flipView = !flipView;
11589       DrawPosition(FALSE, NULL);
11590       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11591     }
11592
11593     if(bookHit) { // [HGM] book: simulate book reply
11594         static char bookMove[MSG_SIZ]; // a bit generous?
11595
11596         programStats.nodes = programStats.depth = programStats.time = 
11597         programStats.score = programStats.got_only_move = 0;
11598         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11599
11600         strcpy(bookMove, "move ");
11601         strcat(bookMove, bookHit);
11602         HandleMachineMove(bookMove, &first);
11603     }
11604 }
11605
11606 void
11607 MachineBlackEvent()
11608 {
11609     char buf[MSG_SIZ];
11610    char *bookHit = NULL;
11611
11612     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11613         return;
11614
11615
11616     if (gameMode == PlayFromGameFile || 
11617         gameMode == TwoMachinesPlay  || 
11618         gameMode == Training         || 
11619         gameMode == AnalyzeMode      || 
11620         gameMode == EndOfGame)
11621         EditGameEvent();
11622
11623     if (gameMode == EditPosition) 
11624         EditPositionDone(TRUE);
11625
11626     if (WhiteOnMove(currentMove)) {
11627         DisplayError(_("It is not Black's turn"), 0);
11628         return;
11629     }
11630     
11631     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11632       ExitAnalyzeMode();
11633
11634     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11635         gameMode == AnalyzeFile)
11636         TruncateGame();
11637
11638     ResurrectChessProgram();    /* in case it isn't running */
11639     gameMode = MachinePlaysBlack;
11640     pausing = FALSE;
11641     ModeHighlight();
11642     SetGameInfo();
11643     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11644     DisplayTitle(buf);
11645     if (first.sendName) {
11646       sprintf(buf, "name %s\n", gameInfo.white);
11647       SendToProgram(buf, &first);
11648     }
11649     if (first.sendTime) {
11650       if (first.useColors) {
11651         SendToProgram("white\n", &first); /*gnu kludge*/
11652       }
11653       SendTimeRemaining(&first, FALSE);
11654     }
11655     if (first.useColors) {
11656       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11657     }
11658     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11659     SetMachineThinkingEnables();
11660     first.maybeThinking = TRUE;
11661     StartClocks();
11662
11663     if (appData.autoFlipView && flipView) {
11664       flipView = !flipView;
11665       DrawPosition(FALSE, NULL);
11666       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11667     }
11668     if(bookHit) { // [HGM] book: simulate book reply
11669         static char bookMove[MSG_SIZ]; // a bit generous?
11670
11671         programStats.nodes = programStats.depth = programStats.time = 
11672         programStats.score = programStats.got_only_move = 0;
11673         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11674
11675         strcpy(bookMove, "move ");
11676         strcat(bookMove, bookHit);
11677         HandleMachineMove(bookMove, &first);
11678     }
11679 }
11680
11681
11682 void
11683 DisplayTwoMachinesTitle()
11684 {
11685     char buf[MSG_SIZ];
11686     if (appData.matchGames > 0) {
11687         if (first.twoMachinesColor[0] == 'w') {
11688             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689                     gameInfo.white, gameInfo.black,
11690                     first.matchWins, second.matchWins,
11691                     matchGame - 1 - (first.matchWins + second.matchWins));
11692         } else {
11693             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11694                     gameInfo.white, gameInfo.black,
11695                     second.matchWins, first.matchWins,
11696                     matchGame - 1 - (first.matchWins + second.matchWins));
11697         }
11698     } else {
11699         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11700     }
11701     DisplayTitle(buf);
11702 }
11703
11704 void
11705 TwoMachinesEvent P((void))
11706 {
11707     int i;
11708     char buf[MSG_SIZ];
11709     ChessProgramState *onmove;
11710     char *bookHit = NULL;
11711     
11712     if (appData.noChessProgram) return;
11713
11714     switch (gameMode) {
11715       case TwoMachinesPlay:
11716         return;
11717       case MachinePlaysWhite:
11718       case MachinePlaysBlack:
11719         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11720             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11721             return;
11722         }
11723         /* fall through */
11724       case BeginningOfGame:
11725       case PlayFromGameFile:
11726       case EndOfGame:
11727         EditGameEvent();
11728         if (gameMode != EditGame) return;
11729         break;
11730       case EditPosition:
11731         EditPositionDone(TRUE);
11732         break;
11733       case AnalyzeMode:
11734       case AnalyzeFile:
11735         ExitAnalyzeMode();
11736         break;
11737       case EditGame:
11738       default:
11739         break;
11740     }
11741
11742 //    forwardMostMove = currentMove;
11743     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11744     ResurrectChessProgram();    /* in case first program isn't running */
11745
11746     if (second.pr == NULL) {
11747         StartChessProgram(&second);
11748         if (second.protocolVersion == 1) {
11749           TwoMachinesEventIfReady();
11750         } else {
11751           /* kludge: allow timeout for initial "feature" command */
11752           FreezeUI();
11753           DisplayMessage("", _("Starting second chess program"));
11754           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11755         }
11756         return;
11757     }
11758     DisplayMessage("", "");
11759     InitChessProgram(&second, FALSE);
11760     SendToProgram("force\n", &second);
11761     if (startedFromSetupPosition) {
11762         SendBoard(&second, backwardMostMove);
11763     if (appData.debugMode) {
11764         fprintf(debugFP, "Two Machines\n");
11765     }
11766     }
11767     for (i = backwardMostMove; i < forwardMostMove; i++) {
11768         SendMoveToProgram(i, &second);
11769     }
11770
11771     gameMode = TwoMachinesPlay;
11772     pausing = FALSE;
11773     ModeHighlight();
11774     SetGameInfo();
11775     DisplayTwoMachinesTitle();
11776     firstMove = TRUE;
11777     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11778         onmove = &first;
11779     } else {
11780         onmove = &second;
11781     }
11782
11783     SendToProgram(first.computerString, &first);
11784     if (first.sendName) {
11785       sprintf(buf, "name %s\n", second.tidy);
11786       SendToProgram(buf, &first);
11787     }
11788     SendToProgram(second.computerString, &second);
11789     if (second.sendName) {
11790       sprintf(buf, "name %s\n", first.tidy);
11791       SendToProgram(buf, &second);
11792     }
11793
11794     ResetClocks();
11795     if (!first.sendTime || !second.sendTime) {
11796         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11797         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11798     }
11799     if (onmove->sendTime) {
11800       if (onmove->useColors) {
11801         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11802       }
11803       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11804     }
11805     if (onmove->useColors) {
11806       SendToProgram(onmove->twoMachinesColor, onmove);
11807     }
11808     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11809 //    SendToProgram("go\n", onmove);
11810     onmove->maybeThinking = TRUE;
11811     SetMachineThinkingEnables();
11812
11813     StartClocks();
11814
11815     if(bookHit) { // [HGM] book: simulate book reply
11816         static char bookMove[MSG_SIZ]; // a bit generous?
11817
11818         programStats.nodes = programStats.depth = programStats.time = 
11819         programStats.score = programStats.got_only_move = 0;
11820         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11821
11822         strcpy(bookMove, "move ");
11823         strcat(bookMove, bookHit);
11824         savedMessage = bookMove; // args for deferred call
11825         savedState = onmove;
11826         ScheduleDelayedEvent(DeferredBookMove, 1);
11827     }
11828 }
11829
11830 void
11831 TrainingEvent()
11832 {
11833     if (gameMode == Training) {
11834       SetTrainingModeOff();
11835       gameMode = PlayFromGameFile;
11836       DisplayMessage("", _("Training mode off"));
11837     } else {
11838       gameMode = Training;
11839       animateTraining = appData.animate;
11840
11841       /* make sure we are not already at the end of the game */
11842       if (currentMove < forwardMostMove) {
11843         SetTrainingModeOn();
11844         DisplayMessage("", _("Training mode on"));
11845       } else {
11846         gameMode = PlayFromGameFile;
11847         DisplayError(_("Already at end of game"), 0);
11848       }
11849     }
11850     ModeHighlight();
11851 }
11852
11853 void
11854 IcsClientEvent()
11855 {
11856     if (!appData.icsActive) return;
11857     switch (gameMode) {
11858       case IcsPlayingWhite:
11859       case IcsPlayingBlack:
11860       case IcsObserving:
11861       case IcsIdle:
11862       case BeginningOfGame:
11863       case IcsExamining:
11864         return;
11865
11866       case EditGame:
11867         break;
11868
11869       case EditPosition:
11870         EditPositionDone(TRUE);
11871         break;
11872
11873       case AnalyzeMode:
11874       case AnalyzeFile:
11875         ExitAnalyzeMode();
11876         break;
11877         
11878       default:
11879         EditGameEvent();
11880         break;
11881     }
11882
11883     gameMode = IcsIdle;
11884     ModeHighlight();
11885     return;
11886 }
11887
11888
11889 void
11890 EditGameEvent()
11891 {
11892     int i;
11893
11894     switch (gameMode) {
11895       case Training:
11896         SetTrainingModeOff();
11897         break;
11898       case MachinePlaysWhite:
11899       case MachinePlaysBlack:
11900       case BeginningOfGame:
11901         SendToProgram("force\n", &first);
11902         SetUserThinkingEnables();
11903         break;
11904       case PlayFromGameFile:
11905         (void) StopLoadGameTimer();
11906         if (gameFileFP != NULL) {
11907             gameFileFP = NULL;
11908         }
11909         break;
11910       case EditPosition:
11911         EditPositionDone(TRUE);
11912         break;
11913       case AnalyzeMode:
11914       case AnalyzeFile:
11915         ExitAnalyzeMode();
11916         SendToProgram("force\n", &first);
11917         break;
11918       case TwoMachinesPlay:
11919         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11920         ResurrectChessProgram();
11921         SetUserThinkingEnables();
11922         break;
11923       case EndOfGame:
11924         ResurrectChessProgram();
11925         break;
11926       case IcsPlayingBlack:
11927       case IcsPlayingWhite:
11928         DisplayError(_("Warning: You are still playing a game"), 0);
11929         break;
11930       case IcsObserving:
11931         DisplayError(_("Warning: You are still observing a game"), 0);
11932         break;
11933       case IcsExamining:
11934         DisplayError(_("Warning: You are still examining a game"), 0);
11935         break;
11936       case IcsIdle:
11937         break;
11938       case EditGame:
11939       default:
11940         return;
11941     }
11942     
11943     pausing = FALSE;
11944     StopClocks();
11945     first.offeredDraw = second.offeredDraw = 0;
11946
11947     if (gameMode == PlayFromGameFile) {
11948         whiteTimeRemaining = timeRemaining[0][currentMove];
11949         blackTimeRemaining = timeRemaining[1][currentMove];
11950         DisplayTitle("");
11951     }
11952
11953     if (gameMode == MachinePlaysWhite ||
11954         gameMode == MachinePlaysBlack ||
11955         gameMode == TwoMachinesPlay ||
11956         gameMode == EndOfGame) {
11957         i = forwardMostMove;
11958         while (i > currentMove) {
11959             SendToProgram("undo\n", &first);
11960             i--;
11961         }
11962         whiteTimeRemaining = timeRemaining[0][currentMove];
11963         blackTimeRemaining = timeRemaining[1][currentMove];
11964         DisplayBothClocks();
11965         if (whiteFlag || blackFlag) {
11966             whiteFlag = blackFlag = 0;
11967         }
11968         DisplayTitle("");
11969     }           
11970     
11971     gameMode = EditGame;
11972     ModeHighlight();
11973     SetGameInfo();
11974 }
11975
11976
11977 void
11978 EditPositionEvent()
11979 {
11980     if (gameMode == EditPosition) {
11981         EditGameEvent();
11982         return;
11983     }
11984     
11985     EditGameEvent();
11986     if (gameMode != EditGame) return;
11987     
11988     gameMode = EditPosition;
11989     ModeHighlight();
11990     SetGameInfo();
11991     if (currentMove > 0)
11992       CopyBoard(boards[0], boards[currentMove]);
11993     
11994     blackPlaysFirst = !WhiteOnMove(currentMove);
11995     ResetClocks();
11996     currentMove = forwardMostMove = backwardMostMove = 0;
11997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11998     DisplayMove(-1);
11999 }
12000
12001 void
12002 ExitAnalyzeMode()
12003 {
12004     /* [DM] icsEngineAnalyze - possible call from other functions */
12005     if (appData.icsEngineAnalyze) {
12006         appData.icsEngineAnalyze = FALSE;
12007
12008         DisplayMessage("",_("Close ICS engine analyze..."));
12009     }
12010     if (first.analysisSupport && first.analyzing) {
12011       SendToProgram("exit\n", &first);
12012       first.analyzing = FALSE;
12013     }
12014     thinkOutput[0] = NULLCHAR;
12015 }
12016
12017 void
12018 EditPositionDone(Boolean fakeRights)
12019 {
12020     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12021
12022     startedFromSetupPosition = TRUE;
12023     InitChessProgram(&first, FALSE);
12024     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12025       boards[0][EP_STATUS] = EP_NONE;
12026       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12027     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12028         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12029         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12030       } else boards[0][CASTLING][2] = NoRights;
12031     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12032         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12033         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12034       } else boards[0][CASTLING][5] = NoRights;
12035     }
12036     SendToProgram("force\n", &first);
12037     if (blackPlaysFirst) {
12038         strcpy(moveList[0], "");
12039         strcpy(parseList[0], "");
12040         currentMove = forwardMostMove = backwardMostMove = 1;
12041         CopyBoard(boards[1], boards[0]);
12042     } else {
12043         currentMove = forwardMostMove = backwardMostMove = 0;
12044     }
12045     SendBoard(&first, forwardMostMove);
12046     if (appData.debugMode) {
12047         fprintf(debugFP, "EditPosDone\n");
12048     }
12049     DisplayTitle("");
12050     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12051     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12052     gameMode = EditGame;
12053     ModeHighlight();
12054     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12055     ClearHighlights(); /* [AS] */
12056 }
12057
12058 /* Pause for `ms' milliseconds */
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12060 void
12061 TimeDelay(ms)
12062      long ms;
12063 {
12064     TimeMark m1, m2;
12065
12066     GetTimeMark(&m1);
12067     do {
12068         GetTimeMark(&m2);
12069     } while (SubtractTimeMarks(&m2, &m1) < ms);
12070 }
12071
12072 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12073 void
12074 SendMultiLineToICS(buf)
12075      char *buf;
12076 {
12077     char temp[MSG_SIZ+1], *p;
12078     int len;
12079
12080     len = strlen(buf);
12081     if (len > MSG_SIZ)
12082       len = MSG_SIZ;
12083   
12084     strncpy(temp, buf, len);
12085     temp[len] = 0;
12086
12087     p = temp;
12088     while (*p) {
12089         if (*p == '\n' || *p == '\r')
12090           *p = ' ';
12091         ++p;
12092     }
12093
12094     strcat(temp, "\n");
12095     SendToICS(temp);
12096     SendToPlayer(temp, strlen(temp));
12097 }
12098
12099 void
12100 SetWhiteToPlayEvent()
12101 {
12102     if (gameMode == EditPosition) {
12103         blackPlaysFirst = FALSE;
12104         DisplayBothClocks();    /* works because currentMove is 0 */
12105     } else if (gameMode == IcsExamining) {
12106         SendToICS(ics_prefix);
12107         SendToICS("tomove white\n");
12108     }
12109 }
12110
12111 void
12112 SetBlackToPlayEvent()
12113 {
12114     if (gameMode == EditPosition) {
12115         blackPlaysFirst = TRUE;
12116         currentMove = 1;        /* kludge */
12117         DisplayBothClocks();
12118         currentMove = 0;
12119     } else if (gameMode == IcsExamining) {
12120         SendToICS(ics_prefix);
12121         SendToICS("tomove black\n");
12122     }
12123 }
12124
12125 void
12126 EditPositionMenuEvent(selection, x, y)
12127      ChessSquare selection;
12128      int x, y;
12129 {
12130     char buf[MSG_SIZ];
12131     ChessSquare piece = boards[0][y][x];
12132
12133     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12134
12135     switch (selection) {
12136       case ClearBoard:
12137         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12138             SendToICS(ics_prefix);
12139             SendToICS("bsetup clear\n");
12140         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12141             SendToICS(ics_prefix);
12142             SendToICS("clearboard\n");
12143         } else {
12144             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12145                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12146                 for (y = 0; y < BOARD_HEIGHT; y++) {
12147                     if (gameMode == IcsExamining) {
12148                         if (boards[currentMove][y][x] != EmptySquare) {
12149                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12150                                     AAA + x, ONE + y);
12151                             SendToICS(buf);
12152                         }
12153                     } else {
12154                         boards[0][y][x] = p;
12155                     }
12156                 }
12157             }
12158         }
12159         if (gameMode == EditPosition) {
12160             DrawPosition(FALSE, boards[0]);
12161         }
12162         break;
12163
12164       case WhitePlay:
12165         SetWhiteToPlayEvent();
12166         break;
12167
12168       case BlackPlay:
12169         SetBlackToPlayEvent();
12170         break;
12171
12172       case EmptySquare:
12173         if (gameMode == IcsExamining) {
12174             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12175             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12176             SendToICS(buf);
12177         } else {
12178             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12179                 if(x == BOARD_LEFT-2) {
12180                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12181                     boards[0][y][1] = 0;
12182                 } else
12183                 if(x == BOARD_RGHT+1) {
12184                     if(y >= gameInfo.holdingsSize) break;
12185                     boards[0][y][BOARD_WIDTH-2] = 0;
12186                 } else break;
12187             }
12188             boards[0][y][x] = EmptySquare;
12189             DrawPosition(FALSE, boards[0]);
12190         }
12191         break;
12192
12193       case PromotePiece:
12194         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12195            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12196             selection = (ChessSquare) (PROMOTED piece);
12197         } else if(piece == EmptySquare) selection = WhiteSilver;
12198         else selection = (ChessSquare)((int)piece - 1);
12199         goto defaultlabel;
12200
12201       case DemotePiece:
12202         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12203            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12204             selection = (ChessSquare) (DEMOTED piece);
12205         } else if(piece == EmptySquare) selection = BlackSilver;
12206         else selection = (ChessSquare)((int)piece + 1);       
12207         goto defaultlabel;
12208
12209       case WhiteQueen:
12210       case BlackQueen:
12211         if(gameInfo.variant == VariantShatranj ||
12212            gameInfo.variant == VariantXiangqi  ||
12213            gameInfo.variant == VariantCourier  ||
12214            gameInfo.variant == VariantMakruk     )
12215             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12216         goto defaultlabel;
12217
12218       case WhiteKing:
12219       case BlackKing:
12220         if(gameInfo.variant == VariantXiangqi)
12221             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12222         if(gameInfo.variant == VariantKnightmate)
12223             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12224       default:
12225         defaultlabel:
12226         if (gameMode == IcsExamining) {
12227             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12228             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12229                     PieceToChar(selection), AAA + x, ONE + y);
12230             SendToICS(buf);
12231         } else {
12232             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12233                 int n;
12234                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12235                     n = PieceToNumber(selection - BlackPawn);
12236                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12237                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12238                     boards[0][BOARD_HEIGHT-1-n][1]++;
12239                 } else
12240                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12241                     n = PieceToNumber(selection);
12242                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12243                     boards[0][n][BOARD_WIDTH-1] = selection;
12244                     boards[0][n][BOARD_WIDTH-2]++;
12245                 }
12246             } else
12247             boards[0][y][x] = selection;
12248             DrawPosition(TRUE, boards[0]);
12249         }
12250         break;
12251     }
12252 }
12253
12254
12255 void
12256 DropMenuEvent(selection, x, y)
12257      ChessSquare selection;
12258      int x, y;
12259 {
12260     ChessMove moveType;
12261
12262     switch (gameMode) {
12263       case IcsPlayingWhite:
12264       case MachinePlaysBlack:
12265         if (!WhiteOnMove(currentMove)) {
12266             DisplayMoveError(_("It is Black's turn"));
12267             return;
12268         }
12269         moveType = WhiteDrop;
12270         break;
12271       case IcsPlayingBlack:
12272       case MachinePlaysWhite:
12273         if (WhiteOnMove(currentMove)) {
12274             DisplayMoveError(_("It is White's turn"));
12275             return;
12276         }
12277         moveType = BlackDrop;
12278         break;
12279       case EditGame:
12280         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12281         break;
12282       default:
12283         return;
12284     }
12285
12286     if (moveType == BlackDrop && selection < BlackPawn) {
12287       selection = (ChessSquare) ((int) selection
12288                                  + (int) BlackPawn - (int) WhitePawn);
12289     }
12290     if (boards[currentMove][y][x] != EmptySquare) {
12291         DisplayMoveError(_("That square is occupied"));
12292         return;
12293     }
12294
12295     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12296 }
12297
12298 void
12299 AcceptEvent()
12300 {
12301     /* Accept a pending offer of any kind from opponent */
12302     
12303     if (appData.icsActive) {
12304         SendToICS(ics_prefix);
12305         SendToICS("accept\n");
12306     } else if (cmailMsgLoaded) {
12307         if (currentMove == cmailOldMove &&
12308             commentList[cmailOldMove] != NULL &&
12309             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12310                    "Black offers a draw" : "White offers a draw")) {
12311             TruncateGame();
12312             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12314         } else {
12315             DisplayError(_("There is no pending offer on this move"), 0);
12316             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12317         }
12318     } else {
12319         /* Not used for offers from chess program */
12320     }
12321 }
12322
12323 void
12324 DeclineEvent()
12325 {
12326     /* Decline a pending offer of any kind from opponent */
12327     
12328     if (appData.icsActive) {
12329         SendToICS(ics_prefix);
12330         SendToICS("decline\n");
12331     } else if (cmailMsgLoaded) {
12332         if (currentMove == cmailOldMove &&
12333             commentList[cmailOldMove] != NULL &&
12334             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12335                    "Black offers a draw" : "White offers a draw")) {
12336 #ifdef NOTDEF
12337             AppendComment(cmailOldMove, "Draw declined", TRUE);
12338             DisplayComment(cmailOldMove - 1, "Draw declined");
12339 #endif /*NOTDEF*/
12340         } else {
12341             DisplayError(_("There is no pending offer on this move"), 0);
12342         }
12343     } else {
12344         /* Not used for offers from chess program */
12345     }
12346 }
12347
12348 void
12349 RematchEvent()
12350 {
12351     /* Issue ICS rematch command */
12352     if (appData.icsActive) {
12353         SendToICS(ics_prefix);
12354         SendToICS("rematch\n");
12355     }
12356 }
12357
12358 void
12359 CallFlagEvent()
12360 {
12361     /* Call your opponent's flag (claim a win on time) */
12362     if (appData.icsActive) {
12363         SendToICS(ics_prefix);
12364         SendToICS("flag\n");
12365     } else {
12366         switch (gameMode) {
12367           default:
12368             return;
12369           case MachinePlaysWhite:
12370             if (whiteFlag) {
12371                 if (blackFlag)
12372                   GameEnds(GameIsDrawn, "Both players ran out of time",
12373                            GE_PLAYER);
12374                 else
12375                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12376             } else {
12377                 DisplayError(_("Your opponent is not out of time"), 0);
12378             }
12379             break;
12380           case MachinePlaysBlack:
12381             if (blackFlag) {
12382                 if (whiteFlag)
12383                   GameEnds(GameIsDrawn, "Both players ran out of time",
12384                            GE_PLAYER);
12385                 else
12386                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12387             } else {
12388                 DisplayError(_("Your opponent is not out of time"), 0);
12389             }
12390             break;
12391         }
12392     }
12393 }
12394
12395 void
12396 DrawEvent()
12397 {
12398     /* Offer draw or accept pending draw offer from opponent */
12399     
12400     if (appData.icsActive) {
12401         /* Note: tournament rules require draw offers to be
12402            made after you make your move but before you punch
12403            your clock.  Currently ICS doesn't let you do that;
12404            instead, you immediately punch your clock after making
12405            a move, but you can offer a draw at any time. */
12406         
12407         SendToICS(ics_prefix);
12408         SendToICS("draw\n");
12409         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12410     } else if (cmailMsgLoaded) {
12411         if (currentMove == cmailOldMove &&
12412             commentList[cmailOldMove] != NULL &&
12413             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12414                    "Black offers a draw" : "White offers a draw")) {
12415             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12416             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12417         } else if (currentMove == cmailOldMove + 1) {
12418             char *offer = WhiteOnMove(cmailOldMove) ?
12419               "White offers a draw" : "Black offers a draw";
12420             AppendComment(currentMove, offer, TRUE);
12421             DisplayComment(currentMove - 1, offer);
12422             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12423         } else {
12424             DisplayError(_("You must make your move before offering a draw"), 0);
12425             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12426         }
12427     } else if (first.offeredDraw) {
12428         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12429     } else {
12430         if (first.sendDrawOffers) {
12431             SendToProgram("draw\n", &first);
12432             userOfferedDraw = TRUE;
12433         }
12434     }
12435 }
12436
12437 void
12438 AdjournEvent()
12439 {
12440     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12441     
12442     if (appData.icsActive) {
12443         SendToICS(ics_prefix);
12444         SendToICS("adjourn\n");
12445     } else {
12446         /* Currently GNU Chess doesn't offer or accept Adjourns */
12447     }
12448 }
12449
12450
12451 void
12452 AbortEvent()
12453 {
12454     /* Offer Abort or accept pending Abort offer from opponent */
12455     
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("abort\n");
12459     } else {
12460         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12461     }
12462 }
12463
12464 void
12465 ResignEvent()
12466 {
12467     /* Resign.  You can do this even if it's not your turn. */
12468     
12469     if (appData.icsActive) {
12470         SendToICS(ics_prefix);
12471         SendToICS("resign\n");
12472     } else {
12473         switch (gameMode) {
12474           case MachinePlaysWhite:
12475             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12476             break;
12477           case MachinePlaysBlack:
12478             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12479             break;
12480           case EditGame:
12481             if (cmailMsgLoaded) {
12482                 TruncateGame();
12483                 if (WhiteOnMove(cmailOldMove)) {
12484                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12485                 } else {
12486                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12487                 }
12488                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12489             }
12490             break;
12491           default:
12492             break;
12493         }
12494     }
12495 }
12496
12497
12498 void
12499 StopObservingEvent()
12500 {
12501     /* Stop observing current games */
12502     SendToICS(ics_prefix);
12503     SendToICS("unobserve\n");
12504 }
12505
12506 void
12507 StopExaminingEvent()
12508 {
12509     /* Stop observing current game */
12510     SendToICS(ics_prefix);
12511     SendToICS("unexamine\n");
12512 }
12513
12514 void
12515 ForwardInner(target)
12516      int target;
12517 {
12518     int limit;
12519
12520     if (appData.debugMode)
12521         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12522                 target, currentMove, forwardMostMove);
12523
12524     if (gameMode == EditPosition)
12525       return;
12526
12527     if (gameMode == PlayFromGameFile && !pausing)
12528       PauseEvent();
12529     
12530     if (gameMode == IcsExamining && pausing)
12531       limit = pauseExamForwardMostMove;
12532     else
12533       limit = forwardMostMove;
12534     
12535     if (target > limit) target = limit;
12536
12537     if (target > 0 && moveList[target - 1][0]) {
12538         int fromX, fromY, toX, toY;
12539         toX = moveList[target - 1][2] - AAA;
12540         toY = moveList[target - 1][3] - ONE;
12541         if (moveList[target - 1][1] == '@') {
12542             if (appData.highlightLastMove) {
12543                 SetHighlights(-1, -1, toX, toY);
12544             }
12545         } else {
12546             fromX = moveList[target - 1][0] - AAA;
12547             fromY = moveList[target - 1][1] - ONE;
12548             if (target == currentMove + 1) {
12549                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12550             }
12551             if (appData.highlightLastMove) {
12552                 SetHighlights(fromX, fromY, toX, toY);
12553             }
12554         }
12555     }
12556     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12557         gameMode == Training || gameMode == PlayFromGameFile || 
12558         gameMode == AnalyzeFile) {
12559         while (currentMove < target) {
12560             SendMoveToProgram(currentMove++, &first);
12561         }
12562     } else {
12563         currentMove = target;
12564     }
12565     
12566     if (gameMode == EditGame || gameMode == EndOfGame) {
12567         whiteTimeRemaining = timeRemaining[0][currentMove];
12568         blackTimeRemaining = timeRemaining[1][currentMove];
12569     }
12570     DisplayBothClocks();
12571     DisplayMove(currentMove - 1);
12572     DrawPosition(FALSE, boards[currentMove]);
12573     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12574     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12575         DisplayComment(currentMove - 1, commentList[currentMove]);
12576     }
12577 }
12578
12579
12580 void
12581 ForwardEvent()
12582 {
12583     if (gameMode == IcsExamining && !pausing) {
12584         SendToICS(ics_prefix);
12585         SendToICS("forward\n");
12586     } else {
12587         ForwardInner(currentMove + 1);
12588     }
12589 }
12590
12591 void
12592 ToEndEvent()
12593 {
12594     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12595         /* to optimze, we temporarily turn off analysis mode while we feed
12596          * the remaining moves to the engine. Otherwise we get analysis output
12597          * after each move.
12598          */ 
12599         if (first.analysisSupport) {
12600           SendToProgram("exit\nforce\n", &first);
12601           first.analyzing = FALSE;
12602         }
12603     }
12604         
12605     if (gameMode == IcsExamining && !pausing) {
12606         SendToICS(ics_prefix);
12607         SendToICS("forward 999999\n");
12608     } else {
12609         ForwardInner(forwardMostMove);
12610     }
12611
12612     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12613         /* we have fed all the moves, so reactivate analysis mode */
12614         SendToProgram("analyze\n", &first);
12615         first.analyzing = TRUE;
12616         /*first.maybeThinking = TRUE;*/
12617         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12618     }
12619 }
12620
12621 void
12622 BackwardInner(target)
12623      int target;
12624 {
12625     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12626
12627     if (appData.debugMode)
12628         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12629                 target, currentMove, forwardMostMove);
12630
12631     if (gameMode == EditPosition) return;
12632     if (currentMove <= backwardMostMove) {
12633         ClearHighlights();
12634         DrawPosition(full_redraw, boards[currentMove]);
12635         return;
12636     }
12637     if (gameMode == PlayFromGameFile && !pausing)
12638       PauseEvent();
12639     
12640     if (moveList[target][0]) {
12641         int fromX, fromY, toX, toY;
12642         toX = moveList[target][2] - AAA;
12643         toY = moveList[target][3] - ONE;
12644         if (moveList[target][1] == '@') {
12645             if (appData.highlightLastMove) {
12646                 SetHighlights(-1, -1, toX, toY);
12647             }
12648         } else {
12649             fromX = moveList[target][0] - AAA;
12650             fromY = moveList[target][1] - ONE;
12651             if (target == currentMove - 1) {
12652                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12653             }
12654             if (appData.highlightLastMove) {
12655                 SetHighlights(fromX, fromY, toX, toY);
12656             }
12657         }
12658     }
12659     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12660         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12661         while (currentMove > target) {
12662             SendToProgram("undo\n", &first);
12663             currentMove--;
12664         }
12665     } else {
12666         currentMove = target;
12667     }
12668     
12669     if (gameMode == EditGame || gameMode == EndOfGame) {
12670         whiteTimeRemaining = timeRemaining[0][currentMove];
12671         blackTimeRemaining = timeRemaining[1][currentMove];
12672     }
12673     DisplayBothClocks();
12674     DisplayMove(currentMove - 1);
12675     DrawPosition(full_redraw, boards[currentMove]);
12676     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12677     // [HGM] PV info: routine tests if comment empty
12678     DisplayComment(currentMove - 1, commentList[currentMove]);
12679 }
12680
12681 void
12682 BackwardEvent()
12683 {
12684     if (gameMode == IcsExamining && !pausing) {
12685         SendToICS(ics_prefix);
12686         SendToICS("backward\n");
12687     } else {
12688         BackwardInner(currentMove - 1);
12689     }
12690 }
12691
12692 void
12693 ToStartEvent()
12694 {
12695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12696         /* to optimize, we temporarily turn off analysis mode while we undo
12697          * all the moves. Otherwise we get analysis output after each undo.
12698          */ 
12699         if (first.analysisSupport) {
12700           SendToProgram("exit\nforce\n", &first);
12701           first.analyzing = FALSE;
12702         }
12703     }
12704
12705     if (gameMode == IcsExamining && !pausing) {
12706         SendToICS(ics_prefix);
12707         SendToICS("backward 999999\n");
12708     } else {
12709         BackwardInner(backwardMostMove);
12710     }
12711
12712     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12713         /* we have fed all the moves, so reactivate analysis mode */
12714         SendToProgram("analyze\n", &first);
12715         first.analyzing = TRUE;
12716         /*first.maybeThinking = TRUE;*/
12717         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12718     }
12719 }
12720
12721 void
12722 ToNrEvent(int to)
12723 {
12724   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12725   if (to >= forwardMostMove) to = forwardMostMove;
12726   if (to <= backwardMostMove) to = backwardMostMove;
12727   if (to < currentMove) {
12728     BackwardInner(to);
12729   } else {
12730     ForwardInner(to);
12731   }
12732 }
12733
12734 void
12735 RevertEvent(Boolean annotate)
12736 {
12737     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12738         return;
12739     }
12740     if (gameMode != IcsExamining) {
12741         DisplayError(_("You are not examining a game"), 0);
12742         return;
12743     }
12744     if (pausing) {
12745         DisplayError(_("You can't revert while pausing"), 0);
12746         return;
12747     }
12748     SendToICS(ics_prefix);
12749     SendToICS("revert\n");
12750 }
12751
12752 void
12753 RetractMoveEvent()
12754 {
12755     switch (gameMode) {
12756       case MachinePlaysWhite:
12757       case MachinePlaysBlack:
12758         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12759             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12760             return;
12761         }
12762         if (forwardMostMove < 2) return;
12763         currentMove = forwardMostMove = forwardMostMove - 2;
12764         whiteTimeRemaining = timeRemaining[0][currentMove];
12765         blackTimeRemaining = timeRemaining[1][currentMove];
12766         DisplayBothClocks();
12767         DisplayMove(currentMove - 1);
12768         ClearHighlights();/*!! could figure this out*/
12769         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12770         SendToProgram("remove\n", &first);
12771         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12772         break;
12773
12774       case BeginningOfGame:
12775       default:
12776         break;
12777
12778       case IcsPlayingWhite:
12779       case IcsPlayingBlack:
12780         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12781             SendToICS(ics_prefix);
12782             SendToICS("takeback 2\n");
12783         } else {
12784             SendToICS(ics_prefix);
12785             SendToICS("takeback 1\n");
12786         }
12787         break;
12788     }
12789 }
12790
12791 void
12792 MoveNowEvent()
12793 {
12794     ChessProgramState *cps;
12795
12796     switch (gameMode) {
12797       case MachinePlaysWhite:
12798         if (!WhiteOnMove(forwardMostMove)) {
12799             DisplayError(_("It is your turn"), 0);
12800             return;
12801         }
12802         cps = &first;
12803         break;
12804       case MachinePlaysBlack:
12805         if (WhiteOnMove(forwardMostMove)) {
12806             DisplayError(_("It is your turn"), 0);
12807             return;
12808         }
12809         cps = &first;
12810         break;
12811       case TwoMachinesPlay:
12812         if (WhiteOnMove(forwardMostMove) ==
12813             (first.twoMachinesColor[0] == 'w')) {
12814             cps = &first;
12815         } else {
12816             cps = &second;
12817         }
12818         break;
12819       case BeginningOfGame:
12820       default:
12821         return;
12822     }
12823     SendToProgram("?\n", cps);
12824 }
12825
12826 void
12827 TruncateGameEvent()
12828 {
12829     EditGameEvent();
12830     if (gameMode != EditGame) return;
12831     TruncateGame();
12832 }
12833
12834 void
12835 TruncateGame()
12836 {
12837     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12838     if (forwardMostMove > currentMove) {
12839         if (gameInfo.resultDetails != NULL) {
12840             free(gameInfo.resultDetails);
12841             gameInfo.resultDetails = NULL;
12842             gameInfo.result = GameUnfinished;
12843         }
12844         forwardMostMove = currentMove;
12845         HistorySet(parseList, backwardMostMove, forwardMostMove,
12846                    currentMove-1);
12847     }
12848 }
12849
12850 void
12851 HintEvent()
12852 {
12853     if (appData.noChessProgram) return;
12854     switch (gameMode) {
12855       case MachinePlaysWhite:
12856         if (WhiteOnMove(forwardMostMove)) {
12857             DisplayError(_("Wait until your turn"), 0);
12858             return;
12859         }
12860         break;
12861       case BeginningOfGame:
12862       case MachinePlaysBlack:
12863         if (!WhiteOnMove(forwardMostMove)) {
12864             DisplayError(_("Wait until your turn"), 0);
12865             return;
12866         }
12867         break;
12868       default:
12869         DisplayError(_("No hint available"), 0);
12870         return;
12871     }
12872     SendToProgram("hint\n", &first);
12873     hintRequested = TRUE;
12874 }
12875
12876 void
12877 BookEvent()
12878 {
12879     if (appData.noChessProgram) return;
12880     switch (gameMode) {
12881       case MachinePlaysWhite:
12882         if (WhiteOnMove(forwardMostMove)) {
12883             DisplayError(_("Wait until your turn"), 0);
12884             return;
12885         }
12886         break;
12887       case BeginningOfGame:
12888       case MachinePlaysBlack:
12889         if (!WhiteOnMove(forwardMostMove)) {
12890             DisplayError(_("Wait until your turn"), 0);
12891             return;
12892         }
12893         break;
12894       case EditPosition:
12895         EditPositionDone(TRUE);
12896         break;
12897       case TwoMachinesPlay:
12898         return;
12899       default:
12900         break;
12901     }
12902     SendToProgram("bk\n", &first);
12903     bookOutput[0] = NULLCHAR;
12904     bookRequested = TRUE;
12905 }
12906
12907 void
12908 AboutGameEvent()
12909 {
12910     char *tags = PGNTags(&gameInfo);
12911     TagsPopUp(tags, CmailMsg());
12912     free(tags);
12913 }
12914
12915 /* end button procedures */
12916
12917 void
12918 PrintPosition(fp, move)
12919      FILE *fp;
12920      int move;
12921 {
12922     int i, j;
12923     
12924     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12925         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12926             char c = PieceToChar(boards[move][i][j]);
12927             fputc(c == 'x' ? '.' : c, fp);
12928             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12929         }
12930     }
12931     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12932       fprintf(fp, "white to play\n");
12933     else
12934       fprintf(fp, "black to play\n");
12935 }
12936
12937 void
12938 PrintOpponents(fp)
12939      FILE *fp;
12940 {
12941     if (gameInfo.white != NULL) {
12942         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12943     } else {
12944         fprintf(fp, "\n");
12945     }
12946 }
12947
12948 /* Find last component of program's own name, using some heuristics */
12949 void
12950 TidyProgramName(prog, host, buf)
12951      char *prog, *host, buf[MSG_SIZ];
12952 {
12953     char *p, *q;
12954     int local = (strcmp(host, "localhost") == 0);
12955     while (!local && (p = strchr(prog, ';')) != NULL) {
12956         p++;
12957         while (*p == ' ') p++;
12958         prog = p;
12959     }
12960     if (*prog == '"' || *prog == '\'') {
12961         q = strchr(prog + 1, *prog);
12962     } else {
12963         q = strchr(prog, ' ');
12964     }
12965     if (q == NULL) q = prog + strlen(prog);
12966     p = q;
12967     while (p >= prog && *p != '/' && *p != '\\') p--;
12968     p++;
12969     if(p == prog && *p == '"') p++;
12970     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12971     memcpy(buf, p, q - p);
12972     buf[q - p] = NULLCHAR;
12973     if (!local) {
12974         strcat(buf, "@");
12975         strcat(buf, host);
12976     }
12977 }
12978
12979 char *
12980 TimeControlTagValue()
12981 {
12982     char buf[MSG_SIZ];
12983     if (!appData.clockMode) {
12984         strcpy(buf, "-");
12985     } else if (movesPerSession > 0) {
12986         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12987     } else if (timeIncrement == 0) {
12988         sprintf(buf, "%ld", timeControl/1000);
12989     } else {
12990         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12991     }
12992     return StrSave(buf);
12993 }
12994
12995 void
12996 SetGameInfo()
12997 {
12998     /* This routine is used only for certain modes */
12999     VariantClass v = gameInfo.variant;
13000     ChessMove r = GameUnfinished;
13001     char *p = NULL;
13002
13003     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13004         r = gameInfo.result; 
13005         p = gameInfo.resultDetails; 
13006         gameInfo.resultDetails = NULL;
13007     }
13008     ClearGameInfo(&gameInfo);
13009     gameInfo.variant = v;
13010
13011     switch (gameMode) {
13012       case MachinePlaysWhite:
13013         gameInfo.event = StrSave( appData.pgnEventHeader );
13014         gameInfo.site = StrSave(HostName());
13015         gameInfo.date = PGNDate();
13016         gameInfo.round = StrSave("-");
13017         gameInfo.white = StrSave(first.tidy);
13018         gameInfo.black = StrSave(UserName());
13019         gameInfo.timeControl = TimeControlTagValue();
13020         break;
13021
13022       case MachinePlaysBlack:
13023         gameInfo.event = StrSave( appData.pgnEventHeader );
13024         gameInfo.site = StrSave(HostName());
13025         gameInfo.date = PGNDate();
13026         gameInfo.round = StrSave("-");
13027         gameInfo.white = StrSave(UserName());
13028         gameInfo.black = StrSave(first.tidy);
13029         gameInfo.timeControl = TimeControlTagValue();
13030         break;
13031
13032       case TwoMachinesPlay:
13033         gameInfo.event = StrSave( appData.pgnEventHeader );
13034         gameInfo.site = StrSave(HostName());
13035         gameInfo.date = PGNDate();
13036         if (matchGame > 0) {
13037             char buf[MSG_SIZ];
13038             sprintf(buf, "%d", matchGame);
13039             gameInfo.round = StrSave(buf);
13040         } else {
13041             gameInfo.round = StrSave("-");
13042         }
13043         if (first.twoMachinesColor[0] == 'w') {
13044             gameInfo.white = StrSave(first.tidy);
13045             gameInfo.black = StrSave(second.tidy);
13046         } else {
13047             gameInfo.white = StrSave(second.tidy);
13048             gameInfo.black = StrSave(first.tidy);
13049         }
13050         gameInfo.timeControl = TimeControlTagValue();
13051         break;
13052
13053       case EditGame:
13054         gameInfo.event = StrSave("Edited game");
13055         gameInfo.site = StrSave(HostName());
13056         gameInfo.date = PGNDate();
13057         gameInfo.round = StrSave("-");
13058         gameInfo.white = StrSave("-");
13059         gameInfo.black = StrSave("-");
13060         gameInfo.result = r;
13061         gameInfo.resultDetails = p;
13062         break;
13063
13064       case EditPosition:
13065         gameInfo.event = StrSave("Edited position");
13066         gameInfo.site = StrSave(HostName());
13067         gameInfo.date = PGNDate();
13068         gameInfo.round = StrSave("-");
13069         gameInfo.white = StrSave("-");
13070         gameInfo.black = StrSave("-");
13071         break;
13072
13073       case IcsPlayingWhite:
13074       case IcsPlayingBlack:
13075       case IcsObserving:
13076       case IcsExamining:
13077         break;
13078
13079       case PlayFromGameFile:
13080         gameInfo.event = StrSave("Game from non-PGN file");
13081         gameInfo.site = StrSave(HostName());
13082         gameInfo.date = PGNDate();
13083         gameInfo.round = StrSave("-");
13084         gameInfo.white = StrSave("?");
13085         gameInfo.black = StrSave("?");
13086         break;
13087
13088       default:
13089         break;
13090     }
13091 }
13092
13093 void
13094 ReplaceComment(index, text)
13095      int index;
13096      char *text;
13097 {
13098     int len;
13099
13100     while (*text == '\n') text++;
13101     len = strlen(text);
13102     while (len > 0 && text[len - 1] == '\n') len--;
13103
13104     if (commentList[index] != NULL)
13105       free(commentList[index]);
13106
13107     if (len == 0) {
13108         commentList[index] = NULL;
13109         return;
13110     }
13111   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13112       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13113       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13114     commentList[index] = (char *) malloc(len + 2);
13115     strncpy(commentList[index], text, len);
13116     commentList[index][len] = '\n';
13117     commentList[index][len + 1] = NULLCHAR;
13118   } else { 
13119     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13120     char *p;
13121     commentList[index] = (char *) malloc(len + 6);
13122     strcpy(commentList[index], "{\n");
13123     strncpy(commentList[index]+2, text, len);
13124     commentList[index][len+2] = NULLCHAR;
13125     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13126     strcat(commentList[index], "\n}\n");
13127   }
13128 }
13129
13130 void
13131 CrushCRs(text)
13132      char *text;
13133 {
13134   char *p = text;
13135   char *q = text;
13136   char ch;
13137
13138   do {
13139     ch = *p++;
13140     if (ch == '\r') continue;
13141     *q++ = ch;
13142   } while (ch != '\0');
13143 }
13144
13145 void
13146 AppendComment(index, text, addBraces)
13147      int index;
13148      char *text;
13149      Boolean addBraces; // [HGM] braces: tells if we should add {}
13150 {
13151     int oldlen, len;
13152     char *old;
13153
13154 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13155     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13156
13157     CrushCRs(text);
13158     while (*text == '\n') text++;
13159     len = strlen(text);
13160     while (len > 0 && text[len - 1] == '\n') len--;
13161
13162     if (len == 0) return;
13163
13164     if (commentList[index] != NULL) {
13165         old = commentList[index];
13166         oldlen = strlen(old);
13167         while(commentList[index][oldlen-1] ==  '\n')
13168           commentList[index][--oldlen] = NULLCHAR;
13169         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13170         strcpy(commentList[index], old);
13171         free(old);
13172         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13173         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13174           if(addBraces) addBraces = FALSE; else { text++; len--; }
13175           while (*text == '\n') { text++; len--; }
13176           commentList[index][--oldlen] = NULLCHAR;
13177       }
13178         if(addBraces) strcat(commentList[index], "\n{\n");
13179         else          strcat(commentList[index], "\n");
13180         strcat(commentList[index], text);
13181         if(addBraces) strcat(commentList[index], "\n}\n");
13182         else          strcat(commentList[index], "\n");
13183     } else {
13184         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13185         if(addBraces)
13186              strcpy(commentList[index], "{\n");
13187         else commentList[index][0] = NULLCHAR;
13188         strcat(commentList[index], text);
13189         strcat(commentList[index], "\n");
13190         if(addBraces) strcat(commentList[index], "}\n");
13191     }
13192 }
13193
13194 static char * FindStr( char * text, char * sub_text )
13195 {
13196     char * result = strstr( text, sub_text );
13197
13198     if( result != NULL ) {
13199         result += strlen( sub_text );
13200     }
13201
13202     return result;
13203 }
13204
13205 /* [AS] Try to extract PV info from PGN comment */
13206 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13207 char *GetInfoFromComment( int index, char * text )
13208 {
13209     char * sep = text;
13210
13211     if( text != NULL && index > 0 ) {
13212         int score = 0;
13213         int depth = 0;
13214         int time = -1, sec = 0, deci;
13215         char * s_eval = FindStr( text, "[%eval " );
13216         char * s_emt = FindStr( text, "[%emt " );
13217
13218         if( s_eval != NULL || s_emt != NULL ) {
13219             /* New style */
13220             char delim;
13221
13222             if( s_eval != NULL ) {
13223                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13224                     return text;
13225                 }
13226
13227                 if( delim != ']' ) {
13228                     return text;
13229                 }
13230             }
13231
13232             if( s_emt != NULL ) {
13233             }
13234                 return text;
13235         }
13236         else {
13237             /* We expect something like: [+|-]nnn.nn/dd */
13238             int score_lo = 0;
13239
13240             if(*text != '{') return text; // [HGM] braces: must be normal comment
13241
13242             sep = strchr( text, '/' );
13243             if( sep == NULL || sep < (text+4) ) {
13244                 return text;
13245             }
13246
13247             time = -1; sec = -1; deci = -1;
13248             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13249                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13250                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13251                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13252                 return text;
13253             }
13254
13255             if( score_lo < 0 || score_lo >= 100 ) {
13256                 return text;
13257             }
13258
13259             if(sec >= 0) time = 600*time + 10*sec; else
13260             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13261
13262             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13263
13264             /* [HGM] PV time: now locate end of PV info */
13265             while( *++sep >= '0' && *sep <= '9'); // strip depth
13266             if(time >= 0)
13267             while( *++sep >= '0' && *sep <= '9'); // strip time
13268             if(sec >= 0)
13269             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13270             if(deci >= 0)
13271             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13272             while(*sep == ' ') sep++;
13273         }
13274
13275         if( depth <= 0 ) {
13276             return text;
13277         }
13278
13279         if( time < 0 ) {
13280             time = -1;
13281         }
13282
13283         pvInfoList[index-1].depth = depth;
13284         pvInfoList[index-1].score = score;
13285         pvInfoList[index-1].time  = 10*time; // centi-sec
13286         if(*sep == '}') *sep = 0; else *--sep = '{';
13287     }
13288     return sep;
13289 }
13290
13291 void
13292 SendToProgram(message, cps)
13293      char *message;
13294      ChessProgramState *cps;
13295 {
13296     int count, outCount, error;
13297     char buf[MSG_SIZ];
13298
13299     if (cps->pr == NULL) return;
13300     Attention(cps);
13301     
13302     if (appData.debugMode) {
13303         TimeMark now;
13304         GetTimeMark(&now);
13305         fprintf(debugFP, "%ld >%-6s: %s", 
13306                 SubtractTimeMarks(&now, &programStartTime),
13307                 cps->which, message);
13308     }
13309     
13310     count = strlen(message);
13311     outCount = OutputToProcess(cps->pr, message, count, &error);
13312     if (outCount < count && !exiting 
13313                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13314         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13315         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13316             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13317                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13318                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13319             } else {
13320                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13321             }
13322             gameInfo.resultDetails = StrSave(buf);
13323         }
13324         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13325     }
13326 }
13327
13328 void
13329 ReceiveFromProgram(isr, closure, message, count, error)
13330      InputSourceRef isr;
13331      VOIDSTAR closure;
13332      char *message;
13333      int count;
13334      int error;
13335 {
13336     char *end_str;
13337     char buf[MSG_SIZ];
13338     ChessProgramState *cps = (ChessProgramState *)closure;
13339
13340     if (isr != cps->isr) return; /* Killed intentionally */
13341     if (count <= 0) {
13342         if (count == 0) {
13343             sprintf(buf,
13344                     _("Error: %s chess program (%s) exited unexpectedly"),
13345                     cps->which, cps->program);
13346         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13347                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13348                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13349                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13350                 } else {
13351                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13352                 }
13353                 gameInfo.resultDetails = StrSave(buf);
13354             }
13355             RemoveInputSource(cps->isr);
13356             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13357         } else {
13358             sprintf(buf,
13359                     _("Error reading from %s chess program (%s)"),
13360                     cps->which, cps->program);
13361             RemoveInputSource(cps->isr);
13362
13363             /* [AS] Program is misbehaving badly... kill it */
13364             if( count == -2 ) {
13365                 DestroyChildProcess( cps->pr, 9 );
13366                 cps->pr = NoProc;
13367             }
13368
13369             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13370         }
13371         return;
13372     }
13373     
13374     if ((end_str = strchr(message, '\r')) != NULL)
13375       *end_str = NULLCHAR;
13376     if ((end_str = strchr(message, '\n')) != NULL)
13377       *end_str = NULLCHAR;
13378     
13379     if (appData.debugMode) {
13380         TimeMark now; int print = 1;
13381         char *quote = ""; char c; int i;
13382
13383         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13384                 char start = message[0];
13385                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13386                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13387                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13388                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13389                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13390                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13391                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13392                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13393                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13394                     print = (appData.engineComments >= 2);
13395                 }
13396                 message[0] = start; // restore original message
13397         }
13398         if(print) {
13399                 GetTimeMark(&now);
13400                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13401                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13402                         quote,
13403                         message);
13404         }
13405     }
13406
13407     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13408     if (appData.icsEngineAnalyze) {
13409         if (strstr(message, "whisper") != NULL ||
13410              strstr(message, "kibitz") != NULL || 
13411             strstr(message, "tellics") != NULL) return;
13412     }
13413
13414     HandleMachineMove(message, cps);
13415 }
13416
13417
13418 void
13419 SendTimeControl(cps, mps, tc, inc, sd, st)
13420      ChessProgramState *cps;
13421      int mps, inc, sd, st;
13422      long tc;
13423 {
13424     char buf[MSG_SIZ];
13425     int seconds;
13426
13427     if( timeControl_2 > 0 ) {
13428         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13429             tc = timeControl_2;
13430         }
13431     }
13432     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13433     inc /= cps->timeOdds;
13434     st  /= cps->timeOdds;
13435
13436     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13437
13438     if (st > 0) {
13439       /* Set exact time per move, normally using st command */
13440       if (cps->stKludge) {
13441         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13442         seconds = st % 60;
13443         if (seconds == 0) {
13444           sprintf(buf, "level 1 %d\n", st/60);
13445         } else {
13446           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13447         }
13448       } else {
13449         sprintf(buf, "st %d\n", st);
13450       }
13451     } else {
13452       /* Set conventional or incremental time control, using level command */
13453       if (seconds == 0) {
13454         /* Note old gnuchess bug -- minutes:seconds used to not work.
13455            Fixed in later versions, but still avoid :seconds
13456            when seconds is 0. */
13457         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13458       } else {
13459         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13460                 seconds, inc/1000);
13461       }
13462     }
13463     SendToProgram(buf, cps);
13464
13465     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13466     /* Orthogonally, limit search to given depth */
13467     if (sd > 0) {
13468       if (cps->sdKludge) {
13469         sprintf(buf, "depth\n%d\n", sd);
13470       } else {
13471         sprintf(buf, "sd %d\n", sd);
13472       }
13473       SendToProgram(buf, cps);
13474     }
13475
13476     if(cps->nps > 0) { /* [HGM] nps */
13477         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13478         else {
13479                 sprintf(buf, "nps %d\n", cps->nps);
13480               SendToProgram(buf, cps);
13481         }
13482     }
13483 }
13484
13485 ChessProgramState *WhitePlayer()
13486 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13487 {
13488     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13489        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13490         return &second;
13491     return &first;
13492 }
13493
13494 void
13495 SendTimeRemaining(cps, machineWhite)
13496      ChessProgramState *cps;
13497      int /*boolean*/ machineWhite;
13498 {
13499     char message[MSG_SIZ];
13500     long time, otime;
13501
13502     /* Note: this routine must be called when the clocks are stopped
13503        or when they have *just* been set or switched; otherwise
13504        it will be off by the time since the current tick started.
13505     */
13506     if (machineWhite) {
13507         time = whiteTimeRemaining / 10;
13508         otime = blackTimeRemaining / 10;
13509     } else {
13510         time = blackTimeRemaining / 10;
13511         otime = whiteTimeRemaining / 10;
13512     }
13513     /* [HGM] translate opponent's time by time-odds factor */
13514     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13515     if (appData.debugMode) {
13516         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13517     }
13518
13519     if (time <= 0) time = 1;
13520     if (otime <= 0) otime = 1;
13521     
13522     sprintf(message, "time %ld\n", time);
13523     SendToProgram(message, cps);
13524
13525     sprintf(message, "otim %ld\n", otime);
13526     SendToProgram(message, cps);
13527 }
13528
13529 int
13530 BoolFeature(p, name, loc, cps)
13531      char **p;
13532      char *name;
13533      int *loc;
13534      ChessProgramState *cps;
13535 {
13536   char buf[MSG_SIZ];
13537   int len = strlen(name);
13538   int val;
13539   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13540     (*p) += len + 1;
13541     sscanf(*p, "%d", &val);
13542     *loc = (val != 0);
13543     while (**p && **p != ' ') (*p)++;
13544     sprintf(buf, "accepted %s\n", name);
13545     SendToProgram(buf, cps);
13546     return TRUE;
13547   }
13548   return FALSE;
13549 }
13550
13551 int
13552 IntFeature(p, name, loc, cps)
13553      char **p;
13554      char *name;
13555      int *loc;
13556      ChessProgramState *cps;
13557 {
13558   char buf[MSG_SIZ];
13559   int len = strlen(name);
13560   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13561     (*p) += len + 1;
13562     sscanf(*p, "%d", loc);
13563     while (**p && **p != ' ') (*p)++;
13564     sprintf(buf, "accepted %s\n", name);
13565     SendToProgram(buf, cps);
13566     return TRUE;
13567   }
13568   return FALSE;
13569 }
13570
13571 int
13572 StringFeature(p, name, loc, cps)
13573      char **p;
13574      char *name;
13575      char loc[];
13576      ChessProgramState *cps;
13577 {
13578   char buf[MSG_SIZ];
13579   int len = strlen(name);
13580   if (strncmp((*p), name, len) == 0
13581       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13582     (*p) += len + 2;
13583     sscanf(*p, "%[^\"]", loc);
13584     while (**p && **p != '\"') (*p)++;
13585     if (**p == '\"') (*p)++;
13586     sprintf(buf, "accepted %s\n", name);
13587     SendToProgram(buf, cps);
13588     return TRUE;
13589   }
13590   return FALSE;
13591 }
13592
13593 int 
13594 ParseOption(Option *opt, ChessProgramState *cps)
13595 // [HGM] options: process the string that defines an engine option, and determine
13596 // name, type, default value, and allowed value range
13597 {
13598         char *p, *q, buf[MSG_SIZ];
13599         int n, min = (-1)<<31, max = 1<<31, def;
13600
13601         if(p = strstr(opt->name, " -spin ")) {
13602             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13603             if(max < min) max = min; // enforce consistency
13604             if(def < min) def = min;
13605             if(def > max) def = max;
13606             opt->value = def;
13607             opt->min = min;
13608             opt->max = max;
13609             opt->type = Spin;
13610         } else if((p = strstr(opt->name, " -slider "))) {
13611             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13612             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13613             if(max < min) max = min; // enforce consistency
13614             if(def < min) def = min;
13615             if(def > max) def = max;
13616             opt->value = def;
13617             opt->min = min;
13618             opt->max = max;
13619             opt->type = Spin; // Slider;
13620         } else if((p = strstr(opt->name, " -string "))) {
13621             opt->textValue = p+9;
13622             opt->type = TextBox;
13623         } else if((p = strstr(opt->name, " -file "))) {
13624             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13625             opt->textValue = p+7;
13626             opt->type = TextBox; // FileName;
13627         } else if((p = strstr(opt->name, " -path "))) {
13628             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13629             opt->textValue = p+7;
13630             opt->type = TextBox; // PathName;
13631         } else if(p = strstr(opt->name, " -check ")) {
13632             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13633             opt->value = (def != 0);
13634             opt->type = CheckBox;
13635         } else if(p = strstr(opt->name, " -combo ")) {
13636             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13637             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13638             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13639             opt->value = n = 0;
13640             while(q = StrStr(q, " /// ")) {
13641                 n++; *q = 0;    // count choices, and null-terminate each of them
13642                 q += 5;
13643                 if(*q == '*') { // remember default, which is marked with * prefix
13644                     q++;
13645                     opt->value = n;
13646                 }
13647                 cps->comboList[cps->comboCnt++] = q;
13648             }
13649             cps->comboList[cps->comboCnt++] = NULL;
13650             opt->max = n + 1;
13651             opt->type = ComboBox;
13652         } else if(p = strstr(opt->name, " -button")) {
13653             opt->type = Button;
13654         } else if(p = strstr(opt->name, " -save")) {
13655             opt->type = SaveButton;
13656         } else return FALSE;
13657         *p = 0; // terminate option name
13658         // now look if the command-line options define a setting for this engine option.
13659         if(cps->optionSettings && cps->optionSettings[0])
13660             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13661         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13662                 sprintf(buf, "option %s", p);
13663                 if(p = strstr(buf, ",")) *p = 0;
13664                 strcat(buf, "\n");
13665                 SendToProgram(buf, cps);
13666         }
13667         return TRUE;
13668 }
13669
13670 void
13671 FeatureDone(cps, val)
13672      ChessProgramState* cps;
13673      int val;
13674 {
13675   DelayedEventCallback cb = GetDelayedEvent();
13676   if ((cb == InitBackEnd3 && cps == &first) ||
13677       (cb == TwoMachinesEventIfReady && cps == &second)) {
13678     CancelDelayedEvent();
13679     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13680   }
13681   cps->initDone = val;
13682 }
13683
13684 /* Parse feature command from engine */
13685 void
13686 ParseFeatures(args, cps)
13687      char* args;
13688      ChessProgramState *cps;  
13689 {
13690   char *p = args;
13691   char *q;
13692   int val;
13693   char buf[MSG_SIZ];
13694
13695   for (;;) {
13696     while (*p == ' ') p++;
13697     if (*p == NULLCHAR) return;
13698
13699     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13700     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13701     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13702     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13703     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13704     if (BoolFeature(&p, "reuse", &val, cps)) {
13705       /* Engine can disable reuse, but can't enable it if user said no */
13706       if (!val) cps->reuse = FALSE;
13707       continue;
13708     }
13709     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13710     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13711       if (gameMode == TwoMachinesPlay) {
13712         DisplayTwoMachinesTitle();
13713       } else {
13714         DisplayTitle("");
13715       }
13716       continue;
13717     }
13718     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13719     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13720     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13721     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13722     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13723     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13724     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13725     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13726     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13727     if (IntFeature(&p, "done", &val, cps)) {
13728       FeatureDone(cps, val);
13729       continue;
13730     }
13731     /* Added by Tord: */
13732     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13733     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13734     /* End of additions by Tord */
13735
13736     /* [HGM] added features: */
13737     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13738     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13739     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13740     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13741     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13742     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13743     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13744         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13745             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13746             SendToProgram(buf, cps);
13747             continue;
13748         }
13749         if(cps->nrOptions >= MAX_OPTIONS) {
13750             cps->nrOptions--;
13751             sprintf(buf, "%s engine has too many options\n", cps->which);
13752             DisplayError(buf, 0);
13753         }
13754         continue;
13755     }
13756     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13757     /* End of additions by HGM */
13758
13759     /* unknown feature: complain and skip */
13760     q = p;
13761     while (*q && *q != '=') q++;
13762     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13763     SendToProgram(buf, cps);
13764     p = q;
13765     if (*p == '=') {
13766       p++;
13767       if (*p == '\"') {
13768         p++;
13769         while (*p && *p != '\"') p++;
13770         if (*p == '\"') p++;
13771       } else {
13772         while (*p && *p != ' ') p++;
13773       }
13774     }
13775   }
13776
13777 }
13778
13779 void
13780 PeriodicUpdatesEvent(newState)
13781      int newState;
13782 {
13783     if (newState == appData.periodicUpdates)
13784       return;
13785
13786     appData.periodicUpdates=newState;
13787
13788     /* Display type changes, so update it now */
13789 //    DisplayAnalysis();
13790
13791     /* Get the ball rolling again... */
13792     if (newState) {
13793         AnalysisPeriodicEvent(1);
13794         StartAnalysisClock();
13795     }
13796 }
13797
13798 void
13799 PonderNextMoveEvent(newState)
13800      int newState;
13801 {
13802     if (newState == appData.ponderNextMove) return;
13803     if (gameMode == EditPosition) EditPositionDone(TRUE);
13804     if (newState) {
13805         SendToProgram("hard\n", &first);
13806         if (gameMode == TwoMachinesPlay) {
13807             SendToProgram("hard\n", &second);
13808         }
13809     } else {
13810         SendToProgram("easy\n", &first);
13811         thinkOutput[0] = NULLCHAR;
13812         if (gameMode == TwoMachinesPlay) {
13813             SendToProgram("easy\n", &second);
13814         }
13815     }
13816     appData.ponderNextMove = newState;
13817 }
13818
13819 void
13820 NewSettingEvent(option, command, value)
13821      char *command;
13822      int option, value;
13823 {
13824     char buf[MSG_SIZ];
13825
13826     if (gameMode == EditPosition) EditPositionDone(TRUE);
13827     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13828     SendToProgram(buf, &first);
13829     if (gameMode == TwoMachinesPlay) {
13830         SendToProgram(buf, &second);
13831     }
13832 }
13833
13834 void
13835 ShowThinkingEvent()
13836 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13837 {
13838     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13839     int newState = appData.showThinking
13840         // [HGM] thinking: other features now need thinking output as well
13841         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13842     
13843     if (oldState == newState) return;
13844     oldState = newState;
13845     if (gameMode == EditPosition) EditPositionDone(TRUE);
13846     if (oldState) {
13847         SendToProgram("post\n", &first);
13848         if (gameMode == TwoMachinesPlay) {
13849             SendToProgram("post\n", &second);
13850         }
13851     } else {
13852         SendToProgram("nopost\n", &first);
13853         thinkOutput[0] = NULLCHAR;
13854         if (gameMode == TwoMachinesPlay) {
13855             SendToProgram("nopost\n", &second);
13856         }
13857     }
13858 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13859 }
13860
13861 void
13862 AskQuestionEvent(title, question, replyPrefix, which)
13863      char *title; char *question; char *replyPrefix; char *which;
13864 {
13865   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13866   if (pr == NoProc) return;
13867   AskQuestion(title, question, replyPrefix, pr);
13868 }
13869
13870 void
13871 DisplayMove(moveNumber)
13872      int moveNumber;
13873 {
13874     char message[MSG_SIZ];
13875     char res[MSG_SIZ];
13876     char cpThinkOutput[MSG_SIZ];
13877
13878     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13879     
13880     if (moveNumber == forwardMostMove - 1 || 
13881         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13882
13883         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13884
13885         if (strchr(cpThinkOutput, '\n')) {
13886             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13887         }
13888     } else {
13889         *cpThinkOutput = NULLCHAR;
13890     }
13891
13892     /* [AS] Hide thinking from human user */
13893     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13894         *cpThinkOutput = NULLCHAR;
13895         if( thinkOutput[0] != NULLCHAR ) {
13896             int i;
13897
13898             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13899                 cpThinkOutput[i] = '.';
13900             }
13901             cpThinkOutput[i] = NULLCHAR;
13902             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13903         }
13904     }
13905
13906     if (moveNumber == forwardMostMove - 1 &&
13907         gameInfo.resultDetails != NULL) {
13908         if (gameInfo.resultDetails[0] == NULLCHAR) {
13909             sprintf(res, " %s", PGNResult(gameInfo.result));
13910         } else {
13911             sprintf(res, " {%s} %s",
13912                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13913         }
13914     } else {
13915         res[0] = NULLCHAR;
13916     }
13917
13918     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13919         DisplayMessage(res, cpThinkOutput);
13920     } else {
13921         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13922                 WhiteOnMove(moveNumber) ? " " : ".. ",
13923                 parseList[moveNumber], res);
13924         DisplayMessage(message, cpThinkOutput);
13925     }
13926 }
13927
13928 void
13929 DisplayComment(moveNumber, text)
13930      int moveNumber;
13931      char *text;
13932 {
13933     char title[MSG_SIZ];
13934     char buf[8000]; // comment can be long!
13935     int score, depth;
13936     
13937     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13938       strcpy(title, "Comment");
13939     } else {
13940       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13941               WhiteOnMove(moveNumber) ? " " : ".. ",
13942               parseList[moveNumber]);
13943     }
13944     // [HGM] PV info: display PV info together with (or as) comment
13945     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13946       if(text == NULL) text = "";                                           
13947       score = pvInfoList[moveNumber].score;
13948       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13949               depth, (pvInfoList[moveNumber].time+50)/100, text);
13950       text = buf;
13951     }
13952     if (text != NULL && (appData.autoDisplayComment || commentUp))
13953         CommentPopUp(title, text);
13954 }
13955
13956 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13957  * might be busy thinking or pondering.  It can be omitted if your
13958  * gnuchess is configured to stop thinking immediately on any user
13959  * input.  However, that gnuchess feature depends on the FIONREAD
13960  * ioctl, which does not work properly on some flavors of Unix.
13961  */
13962 void
13963 Attention(cps)
13964      ChessProgramState *cps;
13965 {
13966 #if ATTENTION
13967     if (!cps->useSigint) return;
13968     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13969     switch (gameMode) {
13970       case MachinePlaysWhite:
13971       case MachinePlaysBlack:
13972       case TwoMachinesPlay:
13973       case IcsPlayingWhite:
13974       case IcsPlayingBlack:
13975       case AnalyzeMode:
13976       case AnalyzeFile:
13977         /* Skip if we know it isn't thinking */
13978         if (!cps->maybeThinking) return;
13979         if (appData.debugMode)
13980           fprintf(debugFP, "Interrupting %s\n", cps->which);
13981         InterruptChildProcess(cps->pr);
13982         cps->maybeThinking = FALSE;
13983         break;
13984       default:
13985         break;
13986     }
13987 #endif /*ATTENTION*/
13988 }
13989
13990 int
13991 CheckFlags()
13992 {
13993     if (whiteTimeRemaining <= 0) {
13994         if (!whiteFlag) {
13995             whiteFlag = TRUE;
13996             if (appData.icsActive) {
13997                 if (appData.autoCallFlag &&
13998                     gameMode == IcsPlayingBlack && !blackFlag) {
13999                   SendToICS(ics_prefix);
14000                   SendToICS("flag\n");
14001                 }
14002             } else {
14003                 if (blackFlag) {
14004                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14005                 } else {
14006                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14007                     if (appData.autoCallFlag) {
14008                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14009                         return TRUE;
14010                     }
14011                 }
14012             }
14013         }
14014     }
14015     if (blackTimeRemaining <= 0) {
14016         if (!blackFlag) {
14017             blackFlag = TRUE;
14018             if (appData.icsActive) {
14019                 if (appData.autoCallFlag &&
14020                     gameMode == IcsPlayingWhite && !whiteFlag) {
14021                   SendToICS(ics_prefix);
14022                   SendToICS("flag\n");
14023                 }
14024             } else {
14025                 if (whiteFlag) {
14026                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14027                 } else {
14028                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14029                     if (appData.autoCallFlag) {
14030                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14031                         return TRUE;
14032                     }
14033                 }
14034             }
14035         }
14036     }
14037     return FALSE;
14038 }
14039
14040 void
14041 CheckTimeControl()
14042 {
14043     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14044         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14045
14046     /*
14047      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14048      */
14049     if ( !WhiteOnMove(forwardMostMove) )
14050         /* White made time control */
14051         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14052         /* [HGM] time odds: correct new time quota for time odds! */
14053                                             / WhitePlayer()->timeOdds;
14054       else
14055         /* Black made time control */
14056         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14057                                             / WhitePlayer()->other->timeOdds;
14058 }
14059
14060 void
14061 DisplayBothClocks()
14062 {
14063     int wom = gameMode == EditPosition ?
14064       !blackPlaysFirst : WhiteOnMove(currentMove);
14065     DisplayWhiteClock(whiteTimeRemaining, wom);
14066     DisplayBlackClock(blackTimeRemaining, !wom);
14067 }
14068
14069
14070 /* Timekeeping seems to be a portability nightmare.  I think everyone
14071    has ftime(), but I'm really not sure, so I'm including some ifdefs
14072    to use other calls if you don't.  Clocks will be less accurate if
14073    you have neither ftime nor gettimeofday.
14074 */
14075
14076 /* VS 2008 requires the #include outside of the function */
14077 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14078 #include <sys/timeb.h>
14079 #endif
14080
14081 /* Get the current time as a TimeMark */
14082 void
14083 GetTimeMark(tm)
14084      TimeMark *tm;
14085 {
14086 #if HAVE_GETTIMEOFDAY
14087
14088     struct timeval timeVal;
14089     struct timezone timeZone;
14090
14091     gettimeofday(&timeVal, &timeZone);
14092     tm->sec = (long) timeVal.tv_sec; 
14093     tm->ms = (int) (timeVal.tv_usec / 1000L);
14094
14095 #else /*!HAVE_GETTIMEOFDAY*/
14096 #if HAVE_FTIME
14097
14098 // include <sys/timeb.h> / moved to just above start of function
14099     struct timeb timeB;
14100
14101     ftime(&timeB);
14102     tm->sec = (long) timeB.time;
14103     tm->ms = (int) timeB.millitm;
14104
14105 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14106     tm->sec = (long) time(NULL);
14107     tm->ms = 0;
14108 #endif
14109 #endif
14110 }
14111
14112 /* Return the difference in milliseconds between two
14113    time marks.  We assume the difference will fit in a long!
14114 */
14115 long
14116 SubtractTimeMarks(tm2, tm1)
14117      TimeMark *tm2, *tm1;
14118 {
14119     return 1000L*(tm2->sec - tm1->sec) +
14120            (long) (tm2->ms - tm1->ms);
14121 }
14122
14123
14124 /*
14125  * Code to manage the game clocks.
14126  *
14127  * In tournament play, black starts the clock and then white makes a move.
14128  * We give the human user a slight advantage if he is playing white---the
14129  * clocks don't run until he makes his first move, so it takes zero time.
14130  * Also, we don't account for network lag, so we could get out of sync
14131  * with GNU Chess's clock -- but then, referees are always right.  
14132  */
14133
14134 static TimeMark tickStartTM;
14135 static long intendedTickLength;
14136
14137 long
14138 NextTickLength(timeRemaining)
14139      long timeRemaining;
14140 {
14141     long nominalTickLength, nextTickLength;
14142
14143     if (timeRemaining > 0L && timeRemaining <= 10000L)
14144       nominalTickLength = 100L;
14145     else
14146       nominalTickLength = 1000L;
14147     nextTickLength = timeRemaining % nominalTickLength;
14148     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14149
14150     return nextTickLength;
14151 }
14152
14153 /* Adjust clock one minute up or down */
14154 void
14155 AdjustClock(Boolean which, int dir)
14156 {
14157     if(which) blackTimeRemaining += 60000*dir;
14158     else      whiteTimeRemaining += 60000*dir;
14159     DisplayBothClocks();
14160 }
14161
14162 /* Stop clocks and reset to a fresh time control */
14163 void
14164 ResetClocks() 
14165 {
14166     (void) StopClockTimer();
14167     if (appData.icsActive) {
14168         whiteTimeRemaining = blackTimeRemaining = 0;
14169     } else if (searchTime) {
14170         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14171         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14172     } else { /* [HGM] correct new time quote for time odds */
14173         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14174         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14175     }
14176     if (whiteFlag || blackFlag) {
14177         DisplayTitle("");
14178         whiteFlag = blackFlag = FALSE;
14179     }
14180     DisplayBothClocks();
14181 }
14182
14183 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14184
14185 /* Decrement running clock by amount of time that has passed */
14186 void
14187 DecrementClocks()
14188 {
14189     long timeRemaining;
14190     long lastTickLength, fudge;
14191     TimeMark now;
14192
14193     if (!appData.clockMode) return;
14194     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14195         
14196     GetTimeMark(&now);
14197
14198     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14199
14200     /* Fudge if we woke up a little too soon */
14201     fudge = intendedTickLength - lastTickLength;
14202     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14203
14204     if (WhiteOnMove(forwardMostMove)) {
14205         if(whiteNPS >= 0) lastTickLength = 0;
14206         timeRemaining = whiteTimeRemaining -= lastTickLength;
14207         DisplayWhiteClock(whiteTimeRemaining - fudge,
14208                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14209     } else {
14210         if(blackNPS >= 0) lastTickLength = 0;
14211         timeRemaining = blackTimeRemaining -= lastTickLength;
14212         DisplayBlackClock(blackTimeRemaining - fudge,
14213                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14214     }
14215
14216     if (CheckFlags()) return;
14217         
14218     tickStartTM = now;
14219     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14220     StartClockTimer(intendedTickLength);
14221
14222     /* if the time remaining has fallen below the alarm threshold, sound the
14223      * alarm. if the alarm has sounded and (due to a takeback or time control
14224      * with increment) the time remaining has increased to a level above the
14225      * threshold, reset the alarm so it can sound again. 
14226      */
14227     
14228     if (appData.icsActive && appData.icsAlarm) {
14229
14230         /* make sure we are dealing with the user's clock */
14231         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14232                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14233            )) return;
14234
14235         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14236             alarmSounded = FALSE;
14237         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14238             PlayAlarmSound();
14239             alarmSounded = TRUE;
14240         }
14241     }
14242 }
14243
14244
14245 /* A player has just moved, so stop the previously running
14246    clock and (if in clock mode) start the other one.
14247    We redisplay both clocks in case we're in ICS mode, because
14248    ICS gives us an update to both clocks after every move.
14249    Note that this routine is called *after* forwardMostMove
14250    is updated, so the last fractional tick must be subtracted
14251    from the color that is *not* on move now.
14252 */
14253 void
14254 SwitchClocks(int newMoveNr)
14255 {
14256     long lastTickLength;
14257     TimeMark now;
14258     int flagged = FALSE;
14259
14260     GetTimeMark(&now);
14261
14262     if (StopClockTimer() && appData.clockMode) {
14263         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14264         if (!WhiteOnMove(forwardMostMove)) {
14265             if(blackNPS >= 0) lastTickLength = 0;
14266             blackTimeRemaining -= lastTickLength;
14267            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14268 //         if(pvInfoList[forwardMostMove-1].time == -1)
14269                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14270                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14271         } else {
14272            if(whiteNPS >= 0) lastTickLength = 0;
14273            whiteTimeRemaining -= lastTickLength;
14274            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14275 //         if(pvInfoList[forwardMostMove-1].time == -1)
14276                  pvInfoList[forwardMostMove-1].time = 
14277                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14278         }
14279         flagged = CheckFlags();
14280     }
14281     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14282     CheckTimeControl();
14283
14284     if (flagged || !appData.clockMode) return;
14285
14286     switch (gameMode) {
14287       case MachinePlaysBlack:
14288       case MachinePlaysWhite:
14289       case BeginningOfGame:
14290         if (pausing) return;
14291         break;
14292
14293       case EditGame:
14294       case PlayFromGameFile:
14295       case IcsExamining:
14296         return;
14297
14298       default:
14299         break;
14300     }
14301
14302     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14303         if(WhiteOnMove(forwardMostMove))
14304              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14305         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14306     }
14307
14308     tickStartTM = now;
14309     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14310       whiteTimeRemaining : blackTimeRemaining);
14311     StartClockTimer(intendedTickLength);
14312 }
14313         
14314
14315 /* Stop both clocks */
14316 void
14317 StopClocks()
14318 {       
14319     long lastTickLength;
14320     TimeMark now;
14321
14322     if (!StopClockTimer()) return;
14323     if (!appData.clockMode) return;
14324
14325     GetTimeMark(&now);
14326
14327     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14328     if (WhiteOnMove(forwardMostMove)) {
14329         if(whiteNPS >= 0) lastTickLength = 0;
14330         whiteTimeRemaining -= lastTickLength;
14331         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14332     } else {
14333         if(blackNPS >= 0) lastTickLength = 0;
14334         blackTimeRemaining -= lastTickLength;
14335         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14336     }
14337     CheckFlags();
14338 }
14339         
14340 /* Start clock of player on move.  Time may have been reset, so
14341    if clock is already running, stop and restart it. */
14342 void
14343 StartClocks()
14344 {
14345     (void) StopClockTimer(); /* in case it was running already */
14346     DisplayBothClocks();
14347     if (CheckFlags()) return;
14348
14349     if (!appData.clockMode) return;
14350     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14351
14352     GetTimeMark(&tickStartTM);
14353     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14354       whiteTimeRemaining : blackTimeRemaining);
14355
14356    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14357     whiteNPS = blackNPS = -1; 
14358     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14359        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14360         whiteNPS = first.nps;
14361     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14362        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14363         blackNPS = first.nps;
14364     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14365         whiteNPS = second.nps;
14366     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14367         blackNPS = second.nps;
14368     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14369
14370     StartClockTimer(intendedTickLength);
14371 }
14372
14373 char *
14374 TimeString(ms)
14375      long ms;
14376 {
14377     long second, minute, hour, day;
14378     char *sign = "";
14379     static char buf[32];
14380     
14381     if (ms > 0 && ms <= 9900) {
14382       /* convert milliseconds to tenths, rounding up */
14383       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14384
14385       sprintf(buf, " %03.1f ", tenths/10.0);
14386       return buf;
14387     }
14388
14389     /* convert milliseconds to seconds, rounding up */
14390     /* use floating point to avoid strangeness of integer division
14391        with negative dividends on many machines */
14392     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14393
14394     if (second < 0) {
14395         sign = "-";
14396         second = -second;
14397     }
14398     
14399     day = second / (60 * 60 * 24);
14400     second = second % (60 * 60 * 24);
14401     hour = second / (60 * 60);
14402     second = second % (60 * 60);
14403     minute = second / 60;
14404     second = second % 60;
14405     
14406     if (day > 0)
14407       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14408               sign, day, hour, minute, second);
14409     else if (hour > 0)
14410       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14411     else
14412       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14413     
14414     return buf;
14415 }
14416
14417
14418 /*
14419  * This is necessary because some C libraries aren't ANSI C compliant yet.
14420  */
14421 char *
14422 StrStr(string, match)
14423      char *string, *match;
14424 {
14425     int i, length;
14426     
14427     length = strlen(match);
14428     
14429     for (i = strlen(string) - length; i >= 0; i--, string++)
14430       if (!strncmp(match, string, length))
14431         return string;
14432     
14433     return NULL;
14434 }
14435
14436 char *
14437 StrCaseStr(string, match)
14438      char *string, *match;
14439 {
14440     int i, j, length;
14441     
14442     length = strlen(match);
14443     
14444     for (i = strlen(string) - length; i >= 0; i--, string++) {
14445         for (j = 0; j < length; j++) {
14446             if (ToLower(match[j]) != ToLower(string[j]))
14447               break;
14448         }
14449         if (j == length) return string;
14450     }
14451
14452     return NULL;
14453 }
14454
14455 #ifndef _amigados
14456 int
14457 StrCaseCmp(s1, s2)
14458      char *s1, *s2;
14459 {
14460     char c1, c2;
14461     
14462     for (;;) {
14463         c1 = ToLower(*s1++);
14464         c2 = ToLower(*s2++);
14465         if (c1 > c2) return 1;
14466         if (c1 < c2) return -1;
14467         if (c1 == NULLCHAR) return 0;
14468     }
14469 }
14470
14471
14472 int
14473 ToLower(c)
14474      int c;
14475 {
14476     return isupper(c) ? tolower(c) : c;
14477 }
14478
14479
14480 int
14481 ToUpper(c)
14482      int c;
14483 {
14484     return islower(c) ? toupper(c) : c;
14485 }
14486 #endif /* !_amigados    */
14487
14488 char *
14489 StrSave(s)
14490      char *s;
14491 {
14492     char *ret;
14493
14494     if ((ret = (char *) malloc(strlen(s) + 1))) {
14495         strcpy(ret, s);
14496     }
14497     return ret;
14498 }
14499
14500 char *
14501 StrSavePtr(s, savePtr)
14502      char *s, **savePtr;
14503 {
14504     if (*savePtr) {
14505         free(*savePtr);
14506     }
14507     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14508         strcpy(*savePtr, s);
14509     }
14510     return(*savePtr);
14511 }
14512
14513 char *
14514 PGNDate()
14515 {
14516     time_t clock;
14517     struct tm *tm;
14518     char buf[MSG_SIZ];
14519
14520     clock = time((time_t *)NULL);
14521     tm = localtime(&clock);
14522     sprintf(buf, "%04d.%02d.%02d",
14523             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14524     return StrSave(buf);
14525 }
14526
14527
14528 char *
14529 PositionToFEN(move, overrideCastling)
14530      int move;
14531      char *overrideCastling;
14532 {
14533     int i, j, fromX, fromY, toX, toY;
14534     int whiteToPlay;
14535     char buf[128];
14536     char *p, *q;
14537     int emptycount;
14538     ChessSquare piece;
14539
14540     whiteToPlay = (gameMode == EditPosition) ?
14541       !blackPlaysFirst : (move % 2 == 0);
14542     p = buf;
14543
14544     /* Piece placement data */
14545     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14546         emptycount = 0;
14547         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14548             if (boards[move][i][j] == EmptySquare) {
14549                 emptycount++;
14550             } else { ChessSquare piece = boards[move][i][j];
14551                 if (emptycount > 0) {
14552                     if(emptycount<10) /* [HGM] can be >= 10 */
14553                         *p++ = '0' + emptycount;
14554                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14555                     emptycount = 0;
14556                 }
14557                 if(PieceToChar(piece) == '+') {
14558                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14559                     *p++ = '+';
14560                     piece = (ChessSquare)(DEMOTED piece);
14561                 } 
14562                 *p++ = PieceToChar(piece);
14563                 if(p[-1] == '~') {
14564                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14565                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14566                     *p++ = '~';
14567                 }
14568             }
14569         }
14570         if (emptycount > 0) {
14571             if(emptycount<10) /* [HGM] can be >= 10 */
14572                 *p++ = '0' + emptycount;
14573             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14574             emptycount = 0;
14575         }
14576         *p++ = '/';
14577     }
14578     *(p - 1) = ' ';
14579
14580     /* [HGM] print Crazyhouse or Shogi holdings */
14581     if( gameInfo.holdingsWidth ) {
14582         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14583         q = p;
14584         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14585             piece = boards[move][i][BOARD_WIDTH-1];
14586             if( piece != EmptySquare )
14587               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14588                   *p++ = PieceToChar(piece);
14589         }
14590         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14591             piece = boards[move][BOARD_HEIGHT-i-1][0];
14592             if( piece != EmptySquare )
14593               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14594                   *p++ = PieceToChar(piece);
14595         }
14596
14597         if( q == p ) *p++ = '-';
14598         *p++ = ']';
14599         *p++ = ' ';
14600     }
14601
14602     /* Active color */
14603     *p++ = whiteToPlay ? 'w' : 'b';
14604     *p++ = ' ';
14605
14606   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14607     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14608   } else {
14609   if(nrCastlingRights) {
14610      q = p;
14611      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14612        /* [HGM] write directly from rights */
14613            if(boards[move][CASTLING][2] != NoRights &&
14614               boards[move][CASTLING][0] != NoRights   )
14615                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14616            if(boards[move][CASTLING][2] != NoRights &&
14617               boards[move][CASTLING][1] != NoRights   )
14618                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14619            if(boards[move][CASTLING][5] != NoRights &&
14620               boards[move][CASTLING][3] != NoRights   )
14621                 *p++ = boards[move][CASTLING][3] + AAA;
14622            if(boards[move][CASTLING][5] != NoRights &&
14623               boards[move][CASTLING][4] != NoRights   )
14624                 *p++ = boards[move][CASTLING][4] + AAA;
14625      } else {
14626
14627         /* [HGM] write true castling rights */
14628         if( nrCastlingRights == 6 ) {
14629             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14630                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14631             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14632                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14633             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14634                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14635             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14636                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14637         }
14638      }
14639      if (q == p) *p++ = '-'; /* No castling rights */
14640      *p++ = ' ';
14641   }
14642
14643   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14644      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14645     /* En passant target square */
14646     if (move > backwardMostMove) {
14647         fromX = moveList[move - 1][0] - AAA;
14648         fromY = moveList[move - 1][1] - ONE;
14649         toX = moveList[move - 1][2] - AAA;
14650         toY = moveList[move - 1][3] - ONE;
14651         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14652             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14653             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14654             fromX == toX) {
14655             /* 2-square pawn move just happened */
14656             *p++ = toX + AAA;
14657             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14658         } else {
14659             *p++ = '-';
14660         }
14661     } else if(move == backwardMostMove) {
14662         // [HGM] perhaps we should always do it like this, and forget the above?
14663         if((signed char)boards[move][EP_STATUS] >= 0) {
14664             *p++ = boards[move][EP_STATUS] + AAA;
14665             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14666         } else {
14667             *p++ = '-';
14668         }
14669     } else {
14670         *p++ = '-';
14671     }
14672     *p++ = ' ';
14673   }
14674   }
14675
14676     /* [HGM] find reversible plies */
14677     {   int i = 0, j=move;
14678
14679         if (appData.debugMode) { int k;
14680             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14681             for(k=backwardMostMove; k<=forwardMostMove; k++)
14682                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14683
14684         }
14685
14686         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14687         if( j == backwardMostMove ) i += initialRulePlies;
14688         sprintf(p, "%d ", i);
14689         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14690     }
14691     /* Fullmove number */
14692     sprintf(p, "%d", (move / 2) + 1);
14693     
14694     return StrSave(buf);
14695 }
14696
14697 Boolean
14698 ParseFEN(board, blackPlaysFirst, fen)
14699     Board board;
14700      int *blackPlaysFirst;
14701      char *fen;
14702 {
14703     int i, j;
14704     char *p;
14705     int emptycount;
14706     ChessSquare piece;
14707
14708     p = fen;
14709
14710     /* [HGM] by default clear Crazyhouse holdings, if present */
14711     if(gameInfo.holdingsWidth) {
14712        for(i=0; i<BOARD_HEIGHT; i++) {
14713            board[i][0]             = EmptySquare; /* black holdings */
14714            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14715            board[i][1]             = (ChessSquare) 0; /* black counts */
14716            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14717        }
14718     }
14719
14720     /* Piece placement data */
14721     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14722         j = 0;
14723         for (;;) {
14724             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14725                 if (*p == '/') p++;
14726                 emptycount = gameInfo.boardWidth - j;
14727                 while (emptycount--)
14728                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14729                 break;
14730 #if(BOARD_FILES >= 10)
14731             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14732                 p++; emptycount=10;
14733                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14734                 while (emptycount--)
14735                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14736 #endif
14737             } else if (isdigit(*p)) {
14738                 emptycount = *p++ - '0';
14739                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14740                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14741                 while (emptycount--)
14742                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14743             } else if (*p == '+' || isalpha(*p)) {
14744                 if (j >= gameInfo.boardWidth) return FALSE;
14745                 if(*p=='+') {
14746                     piece = CharToPiece(*++p);
14747                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14748                     piece = (ChessSquare) (PROMOTED piece ); p++;
14749                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14750                 } else piece = CharToPiece(*p++);
14751
14752                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14753                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14754                     piece = (ChessSquare) (PROMOTED piece);
14755                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14756                     p++;
14757                 }
14758                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14759             } else {
14760                 return FALSE;
14761             }
14762         }
14763     }
14764     while (*p == '/' || *p == ' ') p++;
14765
14766     /* [HGM] look for Crazyhouse holdings here */
14767     while(*p==' ') p++;
14768     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14769         if(*p == '[') p++;
14770         if(*p == '-' ) *p++; /* empty holdings */ else {
14771             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14772             /* if we would allow FEN reading to set board size, we would   */
14773             /* have to add holdings and shift the board read so far here   */
14774             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14775                 *p++;
14776                 if((int) piece >= (int) BlackPawn ) {
14777                     i = (int)piece - (int)BlackPawn;
14778                     i = PieceToNumber((ChessSquare)i);
14779                     if( i >= gameInfo.holdingsSize ) return FALSE;
14780                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14781                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14782                 } else {
14783                     i = (int)piece - (int)WhitePawn;
14784                     i = PieceToNumber((ChessSquare)i);
14785                     if( i >= gameInfo.holdingsSize ) return FALSE;
14786                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14787                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14788                 }
14789             }
14790         }
14791         if(*p == ']') *p++;
14792     }
14793
14794     while(*p == ' ') p++;
14795
14796     /* Active color */
14797     switch (*p++) {
14798       case 'w':
14799         *blackPlaysFirst = FALSE;
14800         break;
14801       case 'b': 
14802         *blackPlaysFirst = TRUE;
14803         break;
14804       default:
14805         return FALSE;
14806     }
14807
14808     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14809     /* return the extra info in global variiables             */
14810
14811     /* set defaults in case FEN is incomplete */
14812     board[EP_STATUS] = EP_UNKNOWN;
14813     for(i=0; i<nrCastlingRights; i++ ) {
14814         board[CASTLING][i] =
14815             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14816     }   /* assume possible unless obviously impossible */
14817     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14818     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14819     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14820                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14821     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14822     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14823     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14824                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14825     FENrulePlies = 0;
14826
14827     while(*p==' ') p++;
14828     if(nrCastlingRights) {
14829       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14830           /* castling indicator present, so default becomes no castlings */
14831           for(i=0; i<nrCastlingRights; i++ ) {
14832                  board[CASTLING][i] = NoRights;
14833           }
14834       }
14835       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14836              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14837              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14838              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14839         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14840
14841         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14842             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14843             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14844         }
14845         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14846             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14847         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14848                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14849         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14850                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14851         switch(c) {
14852           case'K':
14853               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14854               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14855               board[CASTLING][2] = whiteKingFile;
14856               break;
14857           case'Q':
14858               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14859               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14860               board[CASTLING][2] = whiteKingFile;
14861               break;
14862           case'k':
14863               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14864               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14865               board[CASTLING][5] = blackKingFile;
14866               break;
14867           case'q':
14868               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14869               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14870               board[CASTLING][5] = blackKingFile;
14871           case '-':
14872               break;
14873           default: /* FRC castlings */
14874               if(c >= 'a') { /* black rights */
14875                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14876                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14877                   if(i == BOARD_RGHT) break;
14878                   board[CASTLING][5] = i;
14879                   c -= AAA;
14880                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14881                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14882                   if(c > i)
14883                       board[CASTLING][3] = c;
14884                   else
14885                       board[CASTLING][4] = c;
14886               } else { /* white rights */
14887                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14888                     if(board[0][i] == WhiteKing) break;
14889                   if(i == BOARD_RGHT) break;
14890                   board[CASTLING][2] = i;
14891                   c -= AAA - 'a' + 'A';
14892                   if(board[0][c] >= WhiteKing) break;
14893                   if(c > i)
14894                       board[CASTLING][0] = c;
14895                   else
14896                       board[CASTLING][1] = c;
14897               }
14898         }
14899       }
14900       for(i=0; i<nrCastlingRights; i++)
14901         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14902     if (appData.debugMode) {
14903         fprintf(debugFP, "FEN castling rights:");
14904         for(i=0; i<nrCastlingRights; i++)
14905         fprintf(debugFP, " %d", board[CASTLING][i]);
14906         fprintf(debugFP, "\n");
14907     }
14908
14909       while(*p==' ') p++;
14910     }
14911
14912     /* read e.p. field in games that know e.p. capture */
14913     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14914        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14915       if(*p=='-') {
14916         p++; board[EP_STATUS] = EP_NONE;
14917       } else {
14918          char c = *p++ - AAA;
14919
14920          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14921          if(*p >= '0' && *p <='9') *p++;
14922          board[EP_STATUS] = c;
14923       }
14924     }
14925
14926
14927     if(sscanf(p, "%d", &i) == 1) {
14928         FENrulePlies = i; /* 50-move ply counter */
14929         /* (The move number is still ignored)    */
14930     }
14931
14932     return TRUE;
14933 }
14934       
14935 void
14936 EditPositionPasteFEN(char *fen)
14937 {
14938   if (fen != NULL) {
14939     Board initial_position;
14940
14941     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14942       DisplayError(_("Bad FEN position in clipboard"), 0);
14943       return ;
14944     } else {
14945       int savedBlackPlaysFirst = blackPlaysFirst;
14946       EditPositionEvent();
14947       blackPlaysFirst = savedBlackPlaysFirst;
14948       CopyBoard(boards[0], initial_position);
14949       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14950       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14951       DisplayBothClocks();
14952       DrawPosition(FALSE, boards[currentMove]);
14953     }
14954   }
14955 }
14956
14957 static char cseq[12] = "\\   ";
14958
14959 Boolean set_cont_sequence(char *new_seq)
14960 {
14961     int len;
14962     Boolean ret;
14963
14964     // handle bad attempts to set the sequence
14965         if (!new_seq)
14966                 return 0; // acceptable error - no debug
14967
14968     len = strlen(new_seq);
14969     ret = (len > 0) && (len < sizeof(cseq));
14970     if (ret)
14971         strcpy(cseq, new_seq);
14972     else if (appData.debugMode)
14973         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14974     return ret;
14975 }
14976
14977 /*
14978     reformat a source message so words don't cross the width boundary.  internal
14979     newlines are not removed.  returns the wrapped size (no null character unless
14980     included in source message).  If dest is NULL, only calculate the size required
14981     for the dest buffer.  lp argument indicats line position upon entry, and it's
14982     passed back upon exit.
14983 */
14984 int wrap(char *dest, char *src, int count, int width, int *lp)
14985 {
14986     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14987
14988     cseq_len = strlen(cseq);
14989     old_line = line = *lp;
14990     ansi = len = clen = 0;
14991
14992     for (i=0; i < count; i++)
14993     {
14994         if (src[i] == '\033')
14995             ansi = 1;
14996
14997         // if we hit the width, back up
14998         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14999         {
15000             // store i & len in case the word is too long
15001             old_i = i, old_len = len;
15002
15003             // find the end of the last word
15004             while (i && src[i] != ' ' && src[i] != '\n')
15005             {
15006                 i--;
15007                 len--;
15008             }
15009
15010             // word too long?  restore i & len before splitting it
15011             if ((old_i-i+clen) >= width)
15012             {
15013                 i = old_i;
15014                 len = old_len;
15015             }
15016
15017             // extra space?
15018             if (i && src[i-1] == ' ')
15019                 len--;
15020
15021             if (src[i] != ' ' && src[i] != '\n')
15022             {
15023                 i--;
15024                 if (len)
15025                     len--;
15026             }
15027
15028             // now append the newline and continuation sequence
15029             if (dest)
15030                 dest[len] = '\n';
15031             len++;
15032             if (dest)
15033                 strncpy(dest+len, cseq, cseq_len);
15034             len += cseq_len;
15035             line = cseq_len;
15036             clen = cseq_len;
15037             continue;
15038         }
15039
15040         if (dest)
15041             dest[len] = src[i];
15042         len++;
15043         if (!ansi)
15044             line++;
15045         if (src[i] == '\n')
15046             line = 0;
15047         if (src[i] == 'm')
15048             ansi = 0;
15049     }
15050     if (dest && appData.debugMode)
15051     {
15052         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15053             count, width, line, len, *lp);
15054         show_bytes(debugFP, src, count);
15055         fprintf(debugFP, "\ndest: ");
15056         show_bytes(debugFP, dest, len);
15057         fprintf(debugFP, "\n");
15058     }
15059     *lp = dest ? line : old_line;
15060
15061     return len;
15062 }
15063
15064 // [HGM] vari: routines for shelving variations
15065
15066 void 
15067 PushTail(int firstMove, int lastMove)
15068 {
15069         int i, j, nrMoves = lastMove - firstMove;
15070
15071         if(appData.icsActive) { // only in local mode
15072                 forwardMostMove = currentMove; // mimic old ICS behavior
15073                 return;
15074         }
15075         if(storedGames >= MAX_VARIATIONS-1) return;
15076
15077         // push current tail of game on stack
15078         savedResult[storedGames] = gameInfo.result;
15079         savedDetails[storedGames] = gameInfo.resultDetails;
15080         gameInfo.resultDetails = NULL;
15081         savedFirst[storedGames] = firstMove;
15082         savedLast [storedGames] = lastMove;
15083         savedFramePtr[storedGames] = framePtr;
15084         framePtr -= nrMoves; // reserve space for the boards
15085         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15086             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15087             for(j=0; j<MOVE_LEN; j++)
15088                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15089             for(j=0; j<2*MOVE_LEN; j++)
15090                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15091             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15092             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15093             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15094             pvInfoList[firstMove+i-1].depth = 0;
15095             commentList[framePtr+i] = commentList[firstMove+i];
15096             commentList[firstMove+i] = NULL;
15097         }
15098
15099         storedGames++;
15100         forwardMostMove = firstMove; // truncate game so we can start variation
15101         if(storedGames == 1) GreyRevert(FALSE);
15102 }
15103
15104 Boolean
15105 PopTail(Boolean annotate)
15106 {
15107         int i, j, nrMoves;
15108         char buf[8000], moveBuf[20];
15109
15110         if(appData.icsActive) return FALSE; // only in local mode
15111         if(!storedGames) return FALSE; // sanity
15112         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15113
15114         storedGames--;
15115         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15116         nrMoves = savedLast[storedGames] - currentMove;
15117         if(annotate) {
15118                 int cnt = 10;
15119                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15120                 else strcpy(buf, "(");
15121                 for(i=currentMove; i<forwardMostMove; i++) {
15122                         if(WhiteOnMove(i))
15123                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15124                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15125                         strcat(buf, moveBuf);
15126                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15127                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15128                 }
15129                 strcat(buf, ")");
15130         }
15131         for(i=1; i<=nrMoves; i++) { // copy last variation back
15132             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15133             for(j=0; j<MOVE_LEN; j++)
15134                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15135             for(j=0; j<2*MOVE_LEN; j++)
15136                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15137             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15138             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15139             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15140             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15141             commentList[currentMove+i] = commentList[framePtr+i];
15142             commentList[framePtr+i] = NULL;
15143         }
15144         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15145         framePtr = savedFramePtr[storedGames];
15146         gameInfo.result = savedResult[storedGames];
15147         if(gameInfo.resultDetails != NULL) {
15148             free(gameInfo.resultDetails);
15149       }
15150         gameInfo.resultDetails = savedDetails[storedGames];
15151         forwardMostMove = currentMove + nrMoves;
15152         if(storedGames == 0) GreyRevert(TRUE);
15153         return TRUE;
15154 }
15155
15156 void 
15157 CleanupTail()
15158 {       // remove all shelved variations
15159         int i;
15160         for(i=0; i<storedGames; i++) {
15161             if(savedDetails[i])
15162                 free(savedDetails[i]);
15163             savedDetails[i] = NULL;
15164         }
15165         for(i=framePtr; i<MAX_MOVES; i++) {
15166                 if(commentList[i]) free(commentList[i]);
15167                 commentList[i] = NULL;
15168         }
15169         framePtr = MAX_MOVES-1;
15170         storedGames = 0;
15171 }
15172
15173 void
15174 LoadVariation(int index, char *text)
15175 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15176         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15177         int level = 0, move;
15178
15179         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15180         // first find outermost bracketing variation
15181         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15182             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15183                 if(*p == '{') wait = '}'; else
15184                 if(*p == '[') wait = ']'; else
15185                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15186                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15187             }
15188             if(*p == wait) wait = NULLCHAR; // closing ]} found
15189             p++;
15190         }
15191         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15192         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15193         end[1] = NULLCHAR; // clip off comment beyond variation
15194         ToNrEvent(currentMove-1);
15195         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15196         // kludge: use ParsePV() to append variation to game
15197         move = currentMove;
15198         ParsePV(start, TRUE);
15199         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15200         ClearPremoveHighlights();
15201         CommentPopDown();
15202         ToNrEvent(currentMove+1);
15203 }