9083ba95fe1e38fc56d79a5c1b8d2a0086c895e4
[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
1111     if (appData.icsActive) {
1112 #ifdef WIN32
1113         /* [DM] Make a console window if needed [HGM] merged ifs */
1114         ConsoleCreate(); 
1115 #endif
1116         err = establish();
1117         if (err != 0) {
1118             if (*appData.icsCommPort != NULLCHAR) {
1119                 sprintf(buf, _("Could not open comm port %s"),  
1120                         appData.icsCommPort);
1121             } else {
1122                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1123                         appData.icsHost, appData.icsPort);
1124             }
1125             DisplayFatalError(buf, err, 1);
1126             return;
1127         }
1128         SetICSMode();
1129         telnetISR =
1130           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1131         fromUserISR =
1132           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1133         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1134             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1135     } else if (appData.noChessProgram) {
1136         SetNCPMode();
1137     } else {
1138         SetGNUMode();
1139     }
1140
1141     if (*appData.cmailGameName != NULLCHAR) {
1142         SetCmailMode();
1143         OpenLoopback(&cmailPR);
1144         cmailISR =
1145           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1146     }
1147     
1148     ThawUI();
1149     DisplayMessage("", "");
1150     if (StrCaseCmp(appData.initialMode, "") == 0) {
1151       initialMode = BeginningOfGame;
1152     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1153       initialMode = TwoMachinesPlay;
1154     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1155       initialMode = AnalyzeFile; 
1156     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1157       initialMode = AnalyzeMode;
1158     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1159       initialMode = MachinePlaysWhite;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1161       initialMode = MachinePlaysBlack;
1162     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1163       initialMode = EditGame;
1164     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1165       initialMode = EditPosition;
1166     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1167       initialMode = Training;
1168     } else {
1169       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1170       DisplayFatalError(buf, 0, 2);
1171       return;
1172     }
1173
1174     if (appData.matchMode) {
1175         /* Set up machine vs. machine match */
1176         if (appData.noChessProgram) {
1177             DisplayFatalError(_("Can't have a match with no chess programs"),
1178                               0, 2);
1179             return;
1180         }
1181         matchMode = TRUE;
1182         matchGame = 1;
1183         if (*appData.loadGameFile != NULLCHAR) {
1184             int index = appData.loadGameIndex; // [HGM] autoinc
1185             if(index<0) lastIndex = index = 1;
1186             if (!LoadGameFromFile(appData.loadGameFile,
1187                                   index,
1188                                   appData.loadGameFile, FALSE)) {
1189                 DisplayFatalError(_("Bad game file"), 0, 1);
1190                 return;
1191             }
1192         } else if (*appData.loadPositionFile != NULLCHAR) {
1193             int index = appData.loadPositionIndex; // [HGM] autoinc
1194             if(index<0) lastIndex = index = 1;
1195             if (!LoadPositionFromFile(appData.loadPositionFile,
1196                                       index,
1197                                       appData.loadPositionFile)) {
1198                 DisplayFatalError(_("Bad position file"), 0, 1);
1199                 return;
1200             }
1201         }
1202         TwoMachinesEvent();
1203     } else if (*appData.cmailGameName != NULLCHAR) {
1204         /* Set up cmail mode */
1205         ReloadCmailMsgEvent(TRUE);
1206     } else {
1207         /* Set up other modes */
1208         if (initialMode == AnalyzeFile) {
1209           if (*appData.loadGameFile == NULLCHAR) {
1210             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1211             return;
1212           }
1213         }
1214         if (*appData.loadGameFile != NULLCHAR) {
1215             (void) LoadGameFromFile(appData.loadGameFile,
1216                                     appData.loadGameIndex,
1217                                     appData.loadGameFile, TRUE);
1218         } else if (*appData.loadPositionFile != NULLCHAR) {
1219             (void) LoadPositionFromFile(appData.loadPositionFile,
1220                                         appData.loadPositionIndex,
1221                                         appData.loadPositionFile);
1222             /* [HGM] try to make self-starting even after FEN load */
1223             /* to allow automatic setup of fairy variants with wtm */
1224             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1225                 gameMode = BeginningOfGame;
1226                 setboardSpoiledMachineBlack = 1;
1227             }
1228             /* [HGM] loadPos: make that every new game uses the setup */
1229             /* from file as long as we do not switch variant          */
1230             if(!blackPlaysFirst) {
1231                 startedFromPositionFile = TRUE;
1232                 CopyBoard(filePosition, boards[0]);
1233             }
1234         }
1235         if (initialMode == AnalyzeMode) {
1236           if (appData.noChessProgram) {
1237             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1238             return;
1239           }
1240           if (appData.icsActive) {
1241             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1242             return;
1243           }
1244           AnalyzeModeEvent();
1245         } else if (initialMode == AnalyzeFile) {
1246           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1247           ShowThinkingEvent();
1248           AnalyzeFileEvent();
1249           AnalysisPeriodicEvent(1);
1250         } else if (initialMode == MachinePlaysWhite) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1253                               0, 2);
1254             return;
1255           }
1256           if (appData.icsActive) {
1257             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1258                               0, 2);
1259             return;
1260           }
1261           MachineWhiteEvent();
1262         } else if (initialMode == MachinePlaysBlack) {
1263           if (appData.noChessProgram) {
1264             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1265                               0, 2);
1266             return;
1267           }
1268           if (appData.icsActive) {
1269             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1270                               0, 2);
1271             return;
1272           }
1273           MachineBlackEvent();
1274         } else if (initialMode == TwoMachinesPlay) {
1275           if (appData.noChessProgram) {
1276             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1277                               0, 2);
1278             return;
1279           }
1280           if (appData.icsActive) {
1281             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1282                               0, 2);
1283             return;
1284           }
1285           TwoMachinesEvent();
1286         } else if (initialMode == EditGame) {
1287           EditGameEvent();
1288         } else if (initialMode == EditPosition) {
1289           EditPositionEvent();
1290         } else if (initialMode == Training) {
1291           if (*appData.loadGameFile == NULLCHAR) {
1292             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1293             return;
1294           }
1295           TrainingEvent();
1296         }
1297     }
1298 }
1299
1300 /*
1301  * Establish will establish a contact to a remote host.port.
1302  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1303  *  used to talk to the host.
1304  * Returns 0 if okay, error code if not.
1305  */
1306 int
1307 establish()
1308 {
1309     char buf[MSG_SIZ];
1310
1311     if (*appData.icsCommPort != NULLCHAR) {
1312         /* Talk to the host through a serial comm port */
1313         return OpenCommPort(appData.icsCommPort, &icsPR);
1314
1315     } else if (*appData.gateway != NULLCHAR) {
1316         if (*appData.remoteShell == NULLCHAR) {
1317             /* Use the rcmd protocol to run telnet program on a gateway host */
1318             snprintf(buf, sizeof(buf), "%s %s %s",
1319                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1320             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1321
1322         } else {
1323             /* Use the rsh program to run telnet program on a gateway host */
1324             if (*appData.remoteUser == NULLCHAR) {
1325                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1326                         appData.gateway, appData.telnetProgram,
1327                         appData.icsHost, appData.icsPort);
1328             } else {
1329                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1330                         appData.remoteShell, appData.gateway, 
1331                         appData.remoteUser, appData.telnetProgram,
1332                         appData.icsHost, appData.icsPort);
1333             }
1334             return StartChildProcess(buf, "", &icsPR);
1335
1336         }
1337     } else if (appData.useTelnet) {
1338         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1339
1340     } else {
1341         /* TCP socket interface differs somewhat between
1342            Unix and NT; handle details in the front end.
1343            */
1344         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1345     }
1346 }
1347
1348 void
1349 show_bytes(fp, buf, count)
1350      FILE *fp;
1351      char *buf;
1352      int count;
1353 {
1354     while (count--) {
1355         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1356             fprintf(fp, "\\%03o", *buf & 0xff);
1357         } else {
1358             putc(*buf, fp);
1359         }
1360         buf++;
1361     }
1362     fflush(fp);
1363 }
1364
1365 /* Returns an errno value */
1366 int
1367 OutputMaybeTelnet(pr, message, count, outError)
1368      ProcRef pr;
1369      char *message;
1370      int count;
1371      int *outError;
1372 {
1373     char buf[8192], *p, *q, *buflim;
1374     int left, newcount, outcount;
1375
1376     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1377         *appData.gateway != NULLCHAR) {
1378         if (appData.debugMode) {
1379             fprintf(debugFP, ">ICS: ");
1380             show_bytes(debugFP, message, count);
1381             fprintf(debugFP, "\n");
1382         }
1383         return OutputToProcess(pr, message, count, outError);
1384     }
1385
1386     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387     p = message;
1388     q = buf;
1389     left = count;
1390     newcount = 0;
1391     while (left) {
1392         if (q >= buflim) {
1393             if (appData.debugMode) {
1394                 fprintf(debugFP, ">ICS: ");
1395                 show_bytes(debugFP, buf, newcount);
1396                 fprintf(debugFP, "\n");
1397             }
1398             outcount = OutputToProcess(pr, buf, newcount, outError);
1399             if (outcount < newcount) return -1; /* to be sure */
1400             q = buf;
1401             newcount = 0;
1402         }
1403         if (*p == '\n') {
1404             *q++ = '\r';
1405             newcount++;
1406         } else if (((unsigned char) *p) == TN_IAC) {
1407             *q++ = (char) TN_IAC;
1408             newcount ++;
1409         }
1410         *q++ = *p++;
1411         newcount++;
1412         left--;
1413     }
1414     if (appData.debugMode) {
1415         fprintf(debugFP, ">ICS: ");
1416         show_bytes(debugFP, buf, newcount);
1417         fprintf(debugFP, "\n");
1418     }
1419     outcount = OutputToProcess(pr, buf, newcount, outError);
1420     if (outcount < newcount) return -1; /* to be sure */
1421     return count;
1422 }
1423
1424 void
1425 read_from_player(isr, closure, message, count, error)
1426      InputSourceRef isr;
1427      VOIDSTAR closure;
1428      char *message;
1429      int count;
1430      int error;
1431 {
1432     int outError, outCount;
1433     static int gotEof = 0;
1434
1435     /* Pass data read from player on to ICS */
1436     if (count > 0) {
1437         gotEof = 0;
1438         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1439         if (outCount < count) {
1440             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1441         }
1442     } else if (count < 0) {
1443         RemoveInputSource(isr);
1444         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1445     } else if (gotEof++ > 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1448     }
1449 }
1450
1451 void
1452 KeepAlive()
1453 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1454     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1455     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1456     SendToICS("date\n");
1457     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1458 }
1459
1460 /* added routine for printf style output to ics */
1461 void ics_printf(char *format, ...)
1462 {
1463     char buffer[MSG_SIZ];
1464     va_list args;
1465
1466     va_start(args, format);
1467     vsnprintf(buffer, sizeof(buffer), format, args);
1468     buffer[sizeof(buffer)-1] = '\0';
1469     SendToICS(buffer);
1470     va_end(args);
1471 }
1472
1473 void
1474 SendToICS(s)
1475      char *s;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488 /* This is used for sending logon scripts to the ICS. Sending
1489    without a delay causes problems when using timestamp on ICC
1490    (at least on my machine). */
1491 void
1492 SendToICSDelayed(s,msdelay)
1493      char *s;
1494      long msdelay;
1495 {
1496     int count, outCount, outError;
1497
1498     if (icsPR == NULL) return;
1499
1500     count = strlen(s);
1501     if (appData.debugMode) {
1502         fprintf(debugFP, ">ICS: ");
1503         show_bytes(debugFP, s, count);
1504         fprintf(debugFP, "\n");
1505     }
1506     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1507                                       msdelay);
1508     if (outCount < count) {
1509         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1510     }
1511 }
1512
1513
1514 /* Remove all highlighting escape sequences in s
1515    Also deletes any suffix starting with '(' 
1516    */
1517 char *
1518 StripHighlightAndTitle(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             if (*s == '(' || *s == '[') {
1531                 *p = NULLCHAR;
1532                 return retbuf;
1533             }
1534             *p++ = *s++;
1535         }
1536     }
1537     *p = NULLCHAR;
1538     return retbuf;
1539 }
1540
1541 /* Remove all highlighting escape sequences in s */
1542 char *
1543 StripHighlight(s)
1544      char *s;
1545 {
1546     static char retbuf[MSG_SIZ];
1547     char *p = retbuf;
1548
1549     while (*s != NULLCHAR) {
1550         while (*s == '\033') {
1551             while (*s != NULLCHAR && !isalpha(*s)) s++;
1552             if (*s != NULLCHAR) s++;
1553         }
1554         while (*s != NULLCHAR && *s != '\033') {
1555             *p++ = *s++;
1556         }
1557     }
1558     *p = NULLCHAR;
1559     return retbuf;
1560 }
1561
1562 char *variantNames[] = VARIANT_NAMES;
1563 char *
1564 VariantName(v)
1565      VariantClass v;
1566 {
1567     return variantNames[v];
1568 }
1569
1570
1571 /* Identify a variant from the strings the chess servers use or the
1572    PGN Variant tag names we use. */
1573 VariantClass
1574 StringToVariant(e)
1575      char *e;
1576 {
1577     char *p;
1578     int wnum = -1;
1579     VariantClass v = VariantNormal;
1580     int i, found = FALSE;
1581     char buf[MSG_SIZ];
1582
1583     if (!e) return v;
1584
1585     /* [HGM] skip over optional board-size prefixes */
1586     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1587         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1588         while( *e++ != '_');
1589     }
1590
1591     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1592         v = VariantNormal;
1593         found = TRUE;
1594     } else
1595     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1596       if (StrCaseStr(e, variantNames[i])) {
1597         v = (VariantClass) i;
1598         found = TRUE;
1599         break;
1600       }
1601     }
1602
1603     if (!found) {
1604       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1605           || StrCaseStr(e, "wild/fr") 
1606           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1607         v = VariantFischeRandom;
1608       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1609                  (i = 1, p = StrCaseStr(e, "w"))) {
1610         p += i;
1611         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612         if (isdigit(*p)) {
1613           wnum = atoi(p);
1614         } else {
1615           wnum = -1;
1616         }
1617         switch (wnum) {
1618         case 0: /* FICS only, actually */
1619         case 1:
1620           /* Castling legal even if K starts on d-file */
1621           v = VariantWildCastle;
1622           break;
1623         case 2:
1624         case 3:
1625         case 4:
1626           /* Castling illegal even if K & R happen to start in
1627              normal positions. */
1628           v = VariantNoCastle;
1629           break;
1630         case 5:
1631         case 7:
1632         case 8:
1633         case 10:
1634         case 11:
1635         case 12:
1636         case 13:
1637         case 14:
1638         case 15:
1639         case 18:
1640         case 19:
1641           /* Castling legal iff K & R start in normal positions */
1642           v = VariantNormal;
1643           break;
1644         case 6:
1645         case 20:
1646         case 21:
1647           /* Special wilds for position setup; unclear what to do here */
1648           v = VariantLoadable;
1649           break;
1650         case 9:
1651           /* Bizarre ICC game */
1652           v = VariantTwoKings;
1653           break;
1654         case 16:
1655           v = VariantKriegspiel;
1656           break;
1657         case 17:
1658           v = VariantLosers;
1659           break;
1660         case 22:
1661           v = VariantFischeRandom;
1662           break;
1663         case 23:
1664           v = VariantCrazyhouse;
1665           break;
1666         case 24:
1667           v = VariantBughouse;
1668           break;
1669         case 25:
1670           v = Variant3Check;
1671           break;
1672         case 26:
1673           /* Not quite the same as FICS suicide! */
1674           v = VariantGiveaway;
1675           break;
1676         case 27:
1677           v = VariantAtomic;
1678           break;
1679         case 28:
1680           v = VariantShatranj;
1681           break;
1682
1683         /* Temporary names for future ICC types.  The name *will* change in 
1684            the next xboard/WinBoard release after ICC defines it. */
1685         case 29:
1686           v = Variant29;
1687           break;
1688         case 30:
1689           v = Variant30;
1690           break;
1691         case 31:
1692           v = Variant31;
1693           break;
1694         case 32:
1695           v = Variant32;
1696           break;
1697         case 33:
1698           v = Variant33;
1699           break;
1700         case 34:
1701           v = Variant34;
1702           break;
1703         case 35:
1704           v = Variant35;
1705           break;
1706         case 36:
1707           v = Variant36;
1708           break;
1709         case 37:
1710           v = VariantShogi;
1711           break;
1712         case 38:
1713           v = VariantXiangqi;
1714           break;
1715         case 39:
1716           v = VariantCourier;
1717           break;
1718         case 40:
1719           v = VariantGothic;
1720           break;
1721         case 41:
1722           v = VariantCapablanca;
1723           break;
1724         case 42:
1725           v = VariantKnightmate;
1726           break;
1727         case 43:
1728           v = VariantFairy;
1729           break;
1730         case 44:
1731           v = VariantCylinder;
1732           break;
1733         case 45:
1734           v = VariantFalcon;
1735           break;
1736         case 46:
1737           v = VariantCapaRandom;
1738           break;
1739         case 47:
1740           v = VariantBerolina;
1741           break;
1742         case 48:
1743           v = VariantJanus;
1744           break;
1745         case 49:
1746           v = VariantSuper;
1747           break;
1748         case 50:
1749           v = VariantGreat;
1750           break;
1751         case -1:
1752           /* Found "wild" or "w" in the string but no number;
1753              must assume it's normal chess. */
1754           v = VariantNormal;
1755           break;
1756         default:
1757           sprintf(buf, _("Unknown wild type %d"), wnum);
1758           DisplayError(buf, 0);
1759           v = VariantUnknown;
1760           break;
1761         }
1762       }
1763     }
1764     if (appData.debugMode) {
1765       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1766               e, wnum, VariantName(v));
1767     }
1768     return v;
1769 }
1770
1771 static int leftover_start = 0, leftover_len = 0;
1772 char star_match[STAR_MATCH_N][MSG_SIZ];
1773
1774 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1775    advance *index beyond it, and set leftover_start to the new value of
1776    *index; else return FALSE.  If pattern contains the character '*', it
1777    matches any sequence of characters not containing '\r', '\n', or the
1778    character following the '*' (if any), and the matched sequence(s) are
1779    copied into star_match.
1780    */
1781 int
1782 looking_at(buf, index, pattern)
1783      char *buf;
1784      int *index;
1785      char *pattern;
1786 {
1787     char *bufp = &buf[*index], *patternp = pattern;
1788     int star_count = 0;
1789     char *matchp = star_match[0];
1790     
1791     for (;;) {
1792         if (*patternp == NULLCHAR) {
1793             *index = leftover_start = bufp - buf;
1794             *matchp = NULLCHAR;
1795             return TRUE;
1796         }
1797         if (*bufp == NULLCHAR) return FALSE;
1798         if (*patternp == '*') {
1799             if (*bufp == *(patternp + 1)) {
1800                 *matchp = NULLCHAR;
1801                 matchp = star_match[++star_count];
1802                 patternp += 2;
1803                 bufp++;
1804                 continue;
1805             } else if (*bufp == '\n' || *bufp == '\r') {
1806                 patternp++;
1807                 if (*patternp == NULLCHAR)
1808                   continue;
1809                 else
1810                   return FALSE;
1811             } else {
1812                 *matchp++ = *bufp++;
1813                 continue;
1814             }
1815         }
1816         if (*patternp != *bufp) return FALSE;
1817         patternp++;
1818         bufp++;
1819     }
1820 }
1821
1822 void
1823 SendToPlayer(data, length)
1824      char *data;
1825      int length;
1826 {
1827     int error, outCount;
1828     outCount = OutputToProcess(NoProc, data, length, &error);
1829     if (outCount < length) {
1830         DisplayFatalError(_("Error writing to display"), error, 1);
1831     }
1832 }
1833
1834 void
1835 PackHolding(packed, holding)
1836      char packed[];
1837      char *holding;
1838 {
1839     char *p = holding;
1840     char *q = packed;
1841     int runlength = 0;
1842     int curr = 9999;
1843     do {
1844         if (*p == curr) {
1845             runlength++;
1846         } else {
1847             switch (runlength) {
1848               case 0:
1849                 break;
1850               case 1:
1851                 *q++ = curr;
1852                 break;
1853               case 2:
1854                 *q++ = curr;
1855                 *q++ = curr;
1856                 break;
1857               default:
1858                 sprintf(q, "%d", runlength);
1859                 while (*q) q++;
1860                 *q++ = curr;
1861                 break;
1862             }
1863             runlength = 1;
1864             curr = *p;
1865         }
1866     } while (*p++);
1867     *q = NULLCHAR;
1868 }
1869
1870 /* Telnet protocol requests from the front end */
1871 void
1872 TelnetRequest(ddww, option)
1873      unsigned char ddww, option;
1874 {
1875     unsigned char msg[3];
1876     int outCount, outError;
1877
1878     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1879
1880     if (appData.debugMode) {
1881         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1882         switch (ddww) {
1883           case TN_DO:
1884             ddwwStr = "DO";
1885             break;
1886           case TN_DONT:
1887             ddwwStr = "DONT";
1888             break;
1889           case TN_WILL:
1890             ddwwStr = "WILL";
1891             break;
1892           case TN_WONT:
1893             ddwwStr = "WONT";
1894             break;
1895           default:
1896             ddwwStr = buf1;
1897             sprintf(buf1, "%d", ddww);
1898             break;
1899         }
1900         switch (option) {
1901           case TN_ECHO:
1902             optionStr = "ECHO";
1903             break;
1904           default:
1905             optionStr = buf2;
1906             sprintf(buf2, "%d", option);
1907             break;
1908         }
1909         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1910     }
1911     msg[0] = TN_IAC;
1912     msg[1] = ddww;
1913     msg[2] = option;
1914     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1915     if (outCount < 3) {
1916         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917     }
1918 }
1919
1920 void
1921 DoEcho()
1922 {
1923     if (!appData.icsActive) return;
1924     TelnetRequest(TN_DO, TN_ECHO);
1925 }
1926
1927 void
1928 DontEcho()
1929 {
1930     if (!appData.icsActive) return;
1931     TelnetRequest(TN_DONT, TN_ECHO);
1932 }
1933
1934 void
1935 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1936 {
1937     /* put the holdings sent to us by the server on the board holdings area */
1938     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1939     char p;
1940     ChessSquare piece;
1941
1942     if(gameInfo.holdingsWidth < 2)  return;
1943     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1944         return; // prevent overwriting by pre-board holdings
1945
1946     if( (int)lowestPiece >= BlackPawn ) {
1947         holdingsColumn = 0;
1948         countsColumn = 1;
1949         holdingsStartRow = BOARD_HEIGHT-1;
1950         direction = -1;
1951     } else {
1952         holdingsColumn = BOARD_WIDTH-1;
1953         countsColumn = BOARD_WIDTH-2;
1954         holdingsStartRow = 0;
1955         direction = 1;
1956     }
1957
1958     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1959         board[i][holdingsColumn] = EmptySquare;
1960         board[i][countsColumn]   = (ChessSquare) 0;
1961     }
1962     while( (p=*holdings++) != NULLCHAR ) {
1963         piece = CharToPiece( ToUpper(p) );
1964         if(piece == EmptySquare) continue;
1965         /*j = (int) piece - (int) WhitePawn;*/
1966         j = PieceToNumber(piece);
1967         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1968         if(j < 0) continue;               /* should not happen */
1969         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1970         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1971         board[holdingsStartRow+j*direction][countsColumn]++;
1972     }
1973 }
1974
1975
1976 void
1977 VariantSwitch(Board board, VariantClass newVariant)
1978 {
1979    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1980    Board oldBoard;
1981
1982    startedFromPositionFile = FALSE;
1983    if(gameInfo.variant == newVariant) return;
1984
1985    /* [HGM] This routine is called each time an assignment is made to
1986     * gameInfo.variant during a game, to make sure the board sizes
1987     * are set to match the new variant. If that means adding or deleting
1988     * holdings, we shift the playing board accordingly
1989     * This kludge is needed because in ICS observe mode, we get boards
1990     * of an ongoing game without knowing the variant, and learn about the
1991     * latter only later. This can be because of the move list we requested,
1992     * in which case the game history is refilled from the beginning anyway,
1993     * but also when receiving holdings of a crazyhouse game. In the latter
1994     * case we want to add those holdings to the already received position.
1995     */
1996
1997    
1998    if (appData.debugMode) {
1999      fprintf(debugFP, "Switch board from %s to %s\n",
2000              VariantName(gameInfo.variant), VariantName(newVariant));
2001      setbuf(debugFP, NULL);
2002    }
2003    shuffleOpenings = 0;       /* [HGM] shuffle */
2004    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2005    switch(newVariant) 
2006      {
2007      case VariantShogi:
2008        newWidth = 9;  newHeight = 9;
2009        gameInfo.holdingsSize = 7;
2010      case VariantBughouse:
2011      case VariantCrazyhouse:
2012        newHoldingsWidth = 2; break;
2013      case VariantGreat:
2014        newWidth = 10;
2015      case VariantSuper:
2016        newHoldingsWidth = 2;
2017        gameInfo.holdingsSize = 8;
2018        break;
2019      case VariantGothic:
2020      case VariantCapablanca:
2021      case VariantCapaRandom:
2022        newWidth = 10;
2023      default:
2024        newHoldingsWidth = gameInfo.holdingsSize = 0;
2025      };
2026    
2027    if(newWidth  != gameInfo.boardWidth  ||
2028       newHeight != gameInfo.boardHeight ||
2029       newHoldingsWidth != gameInfo.holdingsWidth ) {
2030      
2031      /* shift position to new playing area, if needed */
2032      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2033        for(i=0; i<BOARD_HEIGHT; i++) 
2034          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2035            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2036              board[i][j];
2037        for(i=0; i<newHeight; i++) {
2038          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2039          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2040        }
2041      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2042        for(i=0; i<BOARD_HEIGHT; i++)
2043          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2044            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2045              board[i][j];
2046      }
2047      gameInfo.boardWidth  = newWidth;
2048      gameInfo.boardHeight = newHeight;
2049      gameInfo.holdingsWidth = newHoldingsWidth;
2050      gameInfo.variant = newVariant;
2051      InitDrawingSizes(-2, 0);
2052    } else gameInfo.variant = newVariant;
2053    CopyBoard(oldBoard, board);   // remember correctly formatted board
2054      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2055    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2056 }
2057
2058 static int loggedOn = FALSE;
2059
2060 /*-- Game start info cache: --*/
2061 int gs_gamenum;
2062 char gs_kind[MSG_SIZ];
2063 static char player1Name[128] = "";
2064 static char player2Name[128] = "";
2065 static char cont_seq[] = "\n\\   ";
2066 static int player1Rating = -1;
2067 static int player2Rating = -1;
2068 /*----------------------------*/
2069
2070 ColorClass curColor = ColorNormal;
2071 int suppressKibitz = 0;
2072
2073 // [HGM] seekgraph
2074 Boolean soughtPending = FALSE;
2075 Boolean seekGraphUp;
2076 #define MAX_SEEK_ADS 200
2077 #define SQUARE 0x80
2078 char *seekAdList[MAX_SEEK_ADS];
2079 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2080 float tcList[MAX_SEEK_ADS];
2081 char colorList[MAX_SEEK_ADS];
2082 int nrOfSeekAds = 0;
2083 int minRating = 1010, maxRating = 2800;
2084 int hMargin = 10, vMargin = 20, h, w;
2085 extern int squareSize, lineGap;
2086
2087 void
2088 PlotSeekAd(int i)
2089 {
2090         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2091         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2092         if(r < minRating+100 && r >=0 ) r = minRating+100;
2093         if(r > maxRating) r = maxRating;
2094         if(tc < 1.) tc = 1.;
2095         if(tc > 95.) tc = 95.;
2096         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2097         y = ((double)r - minRating)/(maxRating - minRating)
2098             * (h-vMargin-squareSize/8-1) + vMargin;
2099         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2100         if(strstr(seekAdList[i], " u ")) color = 1;
2101         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2102            !strstr(seekAdList[i], "bullet") &&
2103            !strstr(seekAdList[i], "blitz") &&
2104            !strstr(seekAdList[i], "standard") ) color = 2;
2105         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2106         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2107 }
2108
2109 void
2110 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2111 {
2112         char buf[MSG_SIZ], *ext = "";
2113         VariantClass v = StringToVariant(type);
2114         if(strstr(type, "wild")) {
2115             ext = type + 4; // append wild number
2116             if(v == VariantFischeRandom) type = "chess960"; else
2117             if(v == VariantLoadable) type = "setup"; else
2118             type = VariantName(v);
2119         }
2120         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2121         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2122             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2123             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2124             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2125             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2126             seekNrList[nrOfSeekAds] = nr;
2127             zList[nrOfSeekAds] = 0;
2128             seekAdList[nrOfSeekAds++] = StrSave(buf);
2129             if(plot) PlotSeekAd(nrOfSeekAds-1);
2130         }
2131 }
2132
2133 void
2134 EraseSeekDot(int i)
2135 {
2136     int x = xList[i], y = yList[i], d=squareSize/4, k;
2137     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2138     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2139     // now replot every dot that overlapped
2140     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2141         int xx = xList[k], yy = yList[k];
2142         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2143             DrawSeekDot(xx, yy, colorList[k]);
2144     }
2145 }
2146
2147 void
2148 RemoveSeekAd(int nr)
2149 {
2150         int i;
2151         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2152             EraseSeekDot(i);
2153             if(seekAdList[i]) free(seekAdList[i]);
2154             seekAdList[i] = seekAdList[--nrOfSeekAds];
2155             seekNrList[i] = seekNrList[nrOfSeekAds];
2156             ratingList[i] = ratingList[nrOfSeekAds];
2157             colorList[i]  = colorList[nrOfSeekAds];
2158             tcList[i] = tcList[nrOfSeekAds];
2159             xList[i]  = xList[nrOfSeekAds];
2160             yList[i]  = yList[nrOfSeekAds];
2161             zList[i]  = zList[nrOfSeekAds];
2162             seekAdList[nrOfSeekAds] = NULL;
2163             break;
2164         }
2165 }
2166
2167 Boolean
2168 MatchSoughtLine(char *line)
2169 {
2170     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2171     int nr, base, inc, u=0; char dummy;
2172
2173     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2174        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2175        (u=1) &&
2176        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2177         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2178         // match: compact and save the line
2179         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2180         return TRUE;
2181     }
2182     return FALSE;
2183 }
2184
2185 int
2186 DrawSeekGraph()
2187 {
2188     if(!seekGraphUp) return FALSE;
2189     int i;
2190     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2191     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2192
2193     DrawSeekBackground(0, 0, w, h);
2194     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2195     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2196     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2197         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2198         yy = h-1-yy;
2199         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2200         if(i%500 == 0) {
2201             char buf[MSG_SIZ];
2202             sprintf(buf, "%d", i);
2203             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2204         }
2205     }
2206     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2207     for(i=1; i<100; i+=(i<10?1:5)) {
2208         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2209         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2210         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2211             char buf[MSG_SIZ];
2212             sprintf(buf, "%d", i);
2213             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2214         }
2215     }
2216     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2217     return TRUE;
2218 }
2219
2220 int SeekGraphClick(ClickType click, int x, int y, int moving)
2221 {
2222     static int lastDown = 0, displayed = 0, lastSecond;
2223     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2224         if(click == Release || moving) return FALSE;
2225         nrOfSeekAds = 0;
2226         soughtPending = TRUE;
2227         SendToICS(ics_prefix);
2228         SendToICS("sought\n"); // should this be "sought all"?
2229     } else { // issue challenge based on clicked ad
2230         int dist = 10000; int i, closest = 0, second = 0;
2231         for(i=0; i<nrOfSeekAds; i++) {
2232             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2233             if(d < dist) { dist = d; closest = i; }
2234             second += (d - zList[i] < 120); // count in-range ads
2235             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2236         }
2237         if(dist < 120) {
2238             char buf[MSG_SIZ];
2239             second = (second > 1);
2240             if(displayed != closest || second != lastSecond) {
2241                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2242                 lastSecond = second; displayed = closest;
2243             }
2244             if(click == Press) {
2245                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2246                 lastDown = closest;
2247                 return TRUE;
2248             } // on press 'hit', only show info
2249             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2250             sprintf(buf, "play %d\n", seekNrList[closest]);
2251             SendToICS(ics_prefix);
2252             SendToICS(buf);
2253             return TRUE; // let incoming board of started game pop down the graph
2254         } else if(click == Release) { // release 'miss' is ignored
2255             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2256             if(moving == 2) { // right up-click
2257                 nrOfSeekAds = 0; // refresh graph
2258                 soughtPending = TRUE;
2259                 SendToICS(ics_prefix);
2260                 SendToICS("sought\n"); // should this be "sought all"?
2261             }
2262             return TRUE;
2263         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2264         // press miss or release hit 'pop down' seek graph
2265         seekGraphUp = FALSE;
2266         DrawPosition(TRUE, NULL);
2267     }
2268     return TRUE;
2269 }
2270
2271 void
2272 read_from_ics(isr, closure, data, count, error)
2273      InputSourceRef isr;
2274      VOIDSTAR closure;
2275      char *data;
2276      int count;
2277      int error;
2278 {
2279 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2280 #define STARTED_NONE 0
2281 #define STARTED_MOVES 1
2282 #define STARTED_BOARD 2
2283 #define STARTED_OBSERVE 3
2284 #define STARTED_HOLDINGS 4
2285 #define STARTED_CHATTER 5
2286 #define STARTED_COMMENT 6
2287 #define STARTED_MOVES_NOHIDE 7
2288     
2289     static int started = STARTED_NONE;
2290     static char parse[20000];
2291     static int parse_pos = 0;
2292     static char buf[BUF_SIZE + 1];
2293     static int firstTime = TRUE, intfSet = FALSE;
2294     static ColorClass prevColor = ColorNormal;
2295     static int savingComment = FALSE;
2296     static int cmatch = 0; // continuation sequence match
2297     char *bp;
2298     char str[500];
2299     int i, oldi;
2300     int buf_len;
2301     int next_out;
2302     int tkind;
2303     int backup;    /* [DM] For zippy color lines */
2304     char *p;
2305     char talker[MSG_SIZ]; // [HGM] chat
2306     int channel;
2307
2308     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2309
2310     if (appData.debugMode) {
2311       if (!error) {
2312         fprintf(debugFP, "<ICS: ");
2313         show_bytes(debugFP, data, count);
2314         fprintf(debugFP, "\n");
2315       }
2316     }
2317
2318     if (appData.debugMode) { int f = forwardMostMove;
2319         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2320                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2321                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2322     }
2323     if (count > 0) {
2324         /* If last read ended with a partial line that we couldn't parse,
2325            prepend it to the new read and try again. */
2326         if (leftover_len > 0) {
2327             for (i=0; i<leftover_len; i++)
2328               buf[i] = buf[leftover_start + i];
2329         }
2330
2331     /* copy new characters into the buffer */
2332     bp = buf + leftover_len;
2333     buf_len=leftover_len;
2334     for (i=0; i<count; i++)
2335     {
2336         // ignore these
2337         if (data[i] == '\r')
2338             continue;
2339
2340         // join lines split by ICS?
2341         if (!appData.noJoin)
2342         {
2343             /*
2344                 Joining just consists of finding matches against the
2345                 continuation sequence, and discarding that sequence
2346                 if found instead of copying it.  So, until a match
2347                 fails, there's nothing to do since it might be the
2348                 complete sequence, and thus, something we don't want
2349                 copied.
2350             */
2351             if (data[i] == cont_seq[cmatch])
2352             {
2353                 cmatch++;
2354                 if (cmatch == strlen(cont_seq))
2355                 {
2356                     cmatch = 0; // complete match.  just reset the counter
2357
2358                     /*
2359                         it's possible for the ICS to not include the space
2360                         at the end of the last word, making our [correct]
2361                         join operation fuse two separate words.  the server
2362                         does this when the space occurs at the width setting.
2363                     */
2364                     if (!buf_len || buf[buf_len-1] != ' ')
2365                     {
2366                         *bp++ = ' ';
2367                         buf_len++;
2368                     }
2369                 }
2370                 continue;
2371             }
2372             else if (cmatch)
2373             {
2374                 /*
2375                     match failed, so we have to copy what matched before
2376                     falling through and copying this character.  In reality,
2377                     this will only ever be just the newline character, but
2378                     it doesn't hurt to be precise.
2379                 */
2380                 strncpy(bp, cont_seq, cmatch);
2381                 bp += cmatch;
2382                 buf_len += cmatch;
2383                 cmatch = 0;
2384             }
2385         }
2386
2387         // copy this char
2388         *bp++ = data[i];
2389         buf_len++;
2390     }
2391
2392         buf[buf_len] = NULLCHAR;
2393 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2394         next_out = 0;
2395         leftover_start = 0;
2396         
2397         i = 0;
2398         while (i < buf_len) {
2399             /* Deal with part of the TELNET option negotiation
2400                protocol.  We refuse to do anything beyond the
2401                defaults, except that we allow the WILL ECHO option,
2402                which ICS uses to turn off password echoing when we are
2403                directly connected to it.  We reject this option
2404                if localLineEditing mode is on (always on in xboard)
2405                and we are talking to port 23, which might be a real
2406                telnet server that will try to keep WILL ECHO on permanently.
2407              */
2408             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2409                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2410                 unsigned char option;
2411                 oldi = i;
2412                 switch ((unsigned char) buf[++i]) {
2413                   case TN_WILL:
2414                     if (appData.debugMode)
2415                       fprintf(debugFP, "\n<WILL ");
2416                     switch (option = (unsigned char) buf[++i]) {
2417                       case TN_ECHO:
2418                         if (appData.debugMode)
2419                           fprintf(debugFP, "ECHO ");
2420                         /* Reply only if this is a change, according
2421                            to the protocol rules. */
2422                         if (remoteEchoOption) break;
2423                         if (appData.localLineEditing &&
2424                             atoi(appData.icsPort) == TN_PORT) {
2425                             TelnetRequest(TN_DONT, TN_ECHO);
2426                         } else {
2427                             EchoOff();
2428                             TelnetRequest(TN_DO, TN_ECHO);
2429                             remoteEchoOption = TRUE;
2430                         }
2431                         break;
2432                       default:
2433                         if (appData.debugMode)
2434                           fprintf(debugFP, "%d ", option);
2435                         /* Whatever this is, we don't want it. */
2436                         TelnetRequest(TN_DONT, option);
2437                         break;
2438                     }
2439                     break;
2440                   case TN_WONT:
2441                     if (appData.debugMode)
2442                       fprintf(debugFP, "\n<WONT ");
2443                     switch (option = (unsigned char) buf[++i]) {
2444                       case TN_ECHO:
2445                         if (appData.debugMode)
2446                           fprintf(debugFP, "ECHO ");
2447                         /* Reply only if this is a change, according
2448                            to the protocol rules. */
2449                         if (!remoteEchoOption) break;
2450                         EchoOn();
2451                         TelnetRequest(TN_DONT, TN_ECHO);
2452                         remoteEchoOption = FALSE;
2453                         break;
2454                       default:
2455                         if (appData.debugMode)
2456                           fprintf(debugFP, "%d ", (unsigned char) option);
2457                         /* Whatever this is, it must already be turned
2458                            off, because we never agree to turn on
2459                            anything non-default, so according to the
2460                            protocol rules, we don't reply. */
2461                         break;
2462                     }
2463                     break;
2464                   case TN_DO:
2465                     if (appData.debugMode)
2466                       fprintf(debugFP, "\n<DO ");
2467                     switch (option = (unsigned char) buf[++i]) {
2468                       default:
2469                         /* Whatever this is, we refuse to do it. */
2470                         if (appData.debugMode)
2471                           fprintf(debugFP, "%d ", option);
2472                         TelnetRequest(TN_WONT, option);
2473                         break;
2474                     }
2475                     break;
2476                   case TN_DONT:
2477                     if (appData.debugMode)
2478                       fprintf(debugFP, "\n<DONT ");
2479                     switch (option = (unsigned char) buf[++i]) {
2480                       default:
2481                         if (appData.debugMode)
2482                           fprintf(debugFP, "%d ", option);
2483                         /* Whatever this is, we are already not doing
2484                            it, because we never agree to do anything
2485                            non-default, so according to the protocol
2486                            rules, we don't reply. */
2487                         break;
2488                     }
2489                     break;
2490                   case TN_IAC:
2491                     if (appData.debugMode)
2492                       fprintf(debugFP, "\n<IAC ");
2493                     /* Doubled IAC; pass it through */
2494                     i--;
2495                     break;
2496                   default:
2497                     if (appData.debugMode)
2498                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2499                     /* Drop all other telnet commands on the floor */
2500                     break;
2501                 }
2502                 if (oldi > next_out)
2503                   SendToPlayer(&buf[next_out], oldi - next_out);
2504                 if (++i > next_out)
2505                   next_out = i;
2506                 continue;
2507             }
2508                 
2509             /* OK, this at least will *usually* work */
2510             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2511                 loggedOn = TRUE;
2512             }
2513             
2514             if (loggedOn && !intfSet) {
2515                 if (ics_type == ICS_ICC) {
2516                   sprintf(str,
2517                           "/set-quietly interface %s\n/set-quietly style 12\n",
2518                           programVersion);
2519                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2520                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2521                 } else if (ics_type == ICS_CHESSNET) {
2522                   sprintf(str, "/style 12\n");
2523                 } else {
2524                   strcpy(str, "alias $ @\n$set interface ");
2525                   strcat(str, programVersion);
2526                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2527                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2528                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2529 #ifdef WIN32
2530                   strcat(str, "$iset nohighlight 1\n");
2531 #endif
2532                   strcat(str, "$iset lock 1\n$style 12\n");
2533                 }
2534                 SendToICS(str);
2535                 NotifyFrontendLogin();
2536                 intfSet = TRUE;
2537             }
2538
2539             if (started == STARTED_COMMENT) {
2540                 /* Accumulate characters in comment */
2541                 parse[parse_pos++] = buf[i];
2542                 if (buf[i] == '\n') {
2543                     parse[parse_pos] = NULLCHAR;
2544                     if(chattingPartner>=0) {
2545                         char mess[MSG_SIZ];
2546                         sprintf(mess, "%s%s", talker, parse);
2547                         OutputChatMessage(chattingPartner, mess);
2548                         chattingPartner = -1;
2549                         next_out = i+1; // [HGM] suppress printing in ICS window
2550                     } else
2551                     if(!suppressKibitz) // [HGM] kibitz
2552                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2553                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2554                         int nrDigit = 0, nrAlph = 0, j;
2555                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2556                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2557                         parse[parse_pos] = NULLCHAR;
2558                         // try to be smart: if it does not look like search info, it should go to
2559                         // ICS interaction window after all, not to engine-output window.
2560                         for(j=0; j<parse_pos; j++) { // count letters and digits
2561                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2562                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2563                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2564                         }
2565                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2566                             int depth=0; float score;
2567                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2568                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2569                                 pvInfoList[forwardMostMove-1].depth = depth;
2570                                 pvInfoList[forwardMostMove-1].score = 100*score;
2571                             }
2572                             OutputKibitz(suppressKibitz, parse);
2573                         } else {
2574                             char tmp[MSG_SIZ];
2575                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2576                             SendToPlayer(tmp, strlen(tmp));
2577                         }
2578                         next_out = i+1; // [HGM] suppress printing in ICS window
2579                     }
2580                     started = STARTED_NONE;
2581                 } else {
2582                     /* Don't match patterns against characters in comment */
2583                     i++;
2584                     continue;
2585                 }
2586             }
2587             if (started == STARTED_CHATTER) {
2588                 if (buf[i] != '\n') {
2589                     /* Don't match patterns against characters in chatter */
2590                     i++;
2591                     continue;
2592                 }
2593                 started = STARTED_NONE;
2594                 if(suppressKibitz) next_out = i+1;
2595             }
2596
2597             /* Kludge to deal with rcmd protocol */
2598             if (firstTime && looking_at(buf, &i, "\001*")) {
2599                 DisplayFatalError(&buf[1], 0, 1);
2600                 continue;
2601             } else {
2602                 firstTime = FALSE;
2603             }
2604
2605             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2606                 ics_type = ICS_ICC;
2607                 ics_prefix = "/";
2608                 if (appData.debugMode)
2609                   fprintf(debugFP, "ics_type %d\n", ics_type);
2610                 continue;
2611             }
2612             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2613                 ics_type = ICS_FICS;
2614                 ics_prefix = "$";
2615                 if (appData.debugMode)
2616                   fprintf(debugFP, "ics_type %d\n", ics_type);
2617                 continue;
2618             }
2619             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2620                 ics_type = ICS_CHESSNET;
2621                 ics_prefix = "/";
2622                 if (appData.debugMode)
2623                   fprintf(debugFP, "ics_type %d\n", ics_type);
2624                 continue;
2625             }
2626
2627             if (!loggedOn &&
2628                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2629                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2630                  looking_at(buf, &i, "will be \"*\""))) {
2631               strcpy(ics_handle, star_match[0]);
2632               continue;
2633             }
2634
2635             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2636               char buf[MSG_SIZ];
2637               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2638               DisplayIcsInteractionTitle(buf);
2639               have_set_title = TRUE;
2640             }
2641
2642             /* skip finger notes */
2643             if (started == STARTED_NONE &&
2644                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2645                  (buf[i] == '1' && buf[i+1] == '0')) &&
2646                 buf[i+2] == ':' && buf[i+3] == ' ') {
2647               started = STARTED_CHATTER;
2648               i += 3;
2649               continue;
2650             }
2651
2652             oldi = i;
2653             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2654             if(appData.seekGraph) {
2655                 if(soughtPending && MatchSoughtLine(buf+i)) {
2656                     i = strstr(buf+i, "rated") - buf;
2657                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2658                     next_out = leftover_start = i;
2659                     started = STARTED_CHATTER;
2660                     suppressKibitz = TRUE;
2661                     continue;
2662                 }
2663                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2664                         && looking_at(buf, &i, "* ads displayed")) {
2665                     soughtPending = FALSE;
2666                     seekGraphUp = TRUE;
2667                     DrawSeekGraph();
2668                     continue;
2669                 }
2670                 if(appData.autoRefresh) {
2671                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2672                         int s = (ics_type == ICS_ICC); // ICC format differs
2673                         if(seekGraphUp)
2674                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2675                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2676                         looking_at(buf, &i, "*% "); // eat prompt
2677                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2678                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679                         next_out = i; // suppress
2680                         continue;
2681                     }
2682                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2683                         char *p = star_match[0];
2684                         while(*p) {
2685                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2686                             while(*p && *p++ != ' '); // next
2687                         }
2688                         looking_at(buf, &i, "*% "); // eat prompt
2689                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2690                         next_out = i;
2691                         continue;
2692                     }
2693                 }
2694             }
2695
2696             /* skip formula vars */
2697             if (started == STARTED_NONE &&
2698                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2699               started = STARTED_CHATTER;
2700               i += 3;
2701               continue;
2702             }
2703
2704             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2705             if (appData.autoKibitz && started == STARTED_NONE && 
2706                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2707                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2708                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2709                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2710                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2711                         suppressKibitz = TRUE;
2712                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2713                         next_out = i;
2714                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2715                                 && (gameMode == IcsPlayingWhite)) ||
2716                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2717                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2718                             started = STARTED_CHATTER; // own kibitz we simply discard
2719                         else {
2720                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2721                             parse_pos = 0; parse[0] = NULLCHAR;
2722                             savingComment = TRUE;
2723                             suppressKibitz = gameMode != IcsObserving ? 2 :
2724                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2725                         } 
2726                         continue;
2727                 } else
2728                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2729                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2730                          && atoi(star_match[0])) {
2731                     // suppress the acknowledgements of our own autoKibitz
2732                     char *p;
2733                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2734                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2735                     SendToPlayer(star_match[0], strlen(star_match[0]));
2736                     if(looking_at(buf, &i, "*% ")) // eat prompt
2737                         suppressKibitz = FALSE;
2738                     next_out = i;
2739                     continue;
2740                 }
2741             } // [HGM] kibitz: end of patch
2742
2743             // [HGM] chat: intercept tells by users for which we have an open chat window
2744             channel = -1;
2745             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2746                                            looking_at(buf, &i, "* whispers:") ||
2747                                            looking_at(buf, &i, "* kibitzes:") ||
2748                                            looking_at(buf, &i, "* shouts:") ||
2749                                            looking_at(buf, &i, "* c-shouts:") ||
2750                                            looking_at(buf, &i, "--> * ") ||
2751                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2752                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2753                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2754                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2755                 int p;
2756                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2757                 chattingPartner = -1;
2758
2759                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2760                 for(p=0; p<MAX_CHAT; p++) {
2761                     if(channel == atoi(chatPartner[p])) {
2762                     talker[0] = '['; strcat(talker, "] ");
2763                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2764                     chattingPartner = p; break;
2765                     }
2766                 } else
2767                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2768                 for(p=0; p<MAX_CHAT; p++) {
2769                     if(!strcmp("kibitzes", chatPartner[p])) {
2770                         talker[0] = '['; strcat(talker, "] ");
2771                         chattingPartner = p; break;
2772                     }
2773                 } else
2774                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2775                 for(p=0; p<MAX_CHAT; p++) {
2776                     if(!strcmp("whispers", chatPartner[p])) {
2777                         talker[0] = '['; strcat(talker, "] ");
2778                         chattingPartner = p; break;
2779                     }
2780                 } else
2781                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2782                   if(buf[i-8] == '-' && buf[i-3] == 't')
2783                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2784                     if(!strcmp("c-shouts", chatPartner[p])) {
2785                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2786                         chattingPartner = p; break;
2787                     }
2788                   }
2789                   if(chattingPartner < 0)
2790                   for(p=0; p<MAX_CHAT; p++) {
2791                     if(!strcmp("shouts", chatPartner[p])) {
2792                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2793                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2794                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2795                         chattingPartner = p; break;
2796                     }
2797                   }
2798                 }
2799                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2800                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2801                     talker[0] = 0; Colorize(ColorTell, FALSE);
2802                     chattingPartner = p; break;
2803                 }
2804                 if(chattingPartner<0) i = oldi; else {
2805                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2806                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2807                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2808                     started = STARTED_COMMENT;
2809                     parse_pos = 0; parse[0] = NULLCHAR;
2810                     savingComment = 3 + chattingPartner; // counts as TRUE
2811                     suppressKibitz = TRUE;
2812                     continue;
2813                 }
2814             } // [HGM] chat: end of patch
2815
2816             if (appData.zippyTalk || appData.zippyPlay) {
2817                 /* [DM] Backup address for color zippy lines */
2818                 backup = i;
2819 #if ZIPPY
2820        #ifdef WIN32
2821                if (loggedOn == TRUE)
2822                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2823                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2824        #else
2825                 if (ZippyControl(buf, &i) ||
2826                     ZippyConverse(buf, &i) ||
2827                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2828                       loggedOn = TRUE;
2829                       if (!appData.colorize) continue;
2830                 }
2831        #endif
2832 #endif
2833             } // [DM] 'else { ' deleted
2834                 if (
2835                     /* Regular tells and says */
2836                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2837                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2838                     looking_at(buf, &i, "* says: ") ||
2839                     /* Don't color "message" or "messages" output */
2840                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2841                     looking_at(buf, &i, "*. * at *:*: ") ||
2842                     looking_at(buf, &i, "--* (*:*): ") ||
2843                     /* Message notifications (same color as tells) */
2844                     looking_at(buf, &i, "* has left a message ") ||
2845                     looking_at(buf, &i, "* just sent you a message:\n") ||
2846                     /* Whispers and kibitzes */
2847                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2848                     looking_at(buf, &i, "* kibitzes: ") ||
2849                     /* Channel tells */
2850                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2851
2852                   if (tkind == 1 && strchr(star_match[0], ':')) {
2853                       /* Avoid "tells you:" spoofs in channels */
2854                      tkind = 3;
2855                   }
2856                   if (star_match[0][0] == NULLCHAR ||
2857                       strchr(star_match[0], ' ') ||
2858                       (tkind == 3 && strchr(star_match[1], ' '))) {
2859                     /* Reject bogus matches */
2860                     i = oldi;
2861                   } else {
2862                     if (appData.colorize) {
2863                       if (oldi > next_out) {
2864                         SendToPlayer(&buf[next_out], oldi - next_out);
2865                         next_out = oldi;
2866                       }
2867                       switch (tkind) {
2868                       case 1:
2869                         Colorize(ColorTell, FALSE);
2870                         curColor = ColorTell;
2871                         break;
2872                       case 2:
2873                         Colorize(ColorKibitz, FALSE);
2874                         curColor = ColorKibitz;
2875                         break;
2876                       case 3:
2877                         p = strrchr(star_match[1], '(');
2878                         if (p == NULL) {
2879                           p = star_match[1];
2880                         } else {
2881                           p++;
2882                         }
2883                         if (atoi(p) == 1) {
2884                           Colorize(ColorChannel1, FALSE);
2885                           curColor = ColorChannel1;
2886                         } else {
2887                           Colorize(ColorChannel, FALSE);
2888                           curColor = ColorChannel;
2889                         }
2890                         break;
2891                       case 5:
2892                         curColor = ColorNormal;
2893                         break;
2894                       }
2895                     }
2896                     if (started == STARTED_NONE && appData.autoComment &&
2897                         (gameMode == IcsObserving ||
2898                          gameMode == IcsPlayingWhite ||
2899                          gameMode == IcsPlayingBlack)) {
2900                       parse_pos = i - oldi;
2901                       memcpy(parse, &buf[oldi], parse_pos);
2902                       parse[parse_pos] = NULLCHAR;
2903                       started = STARTED_COMMENT;
2904                       savingComment = TRUE;
2905                     } else {
2906                       started = STARTED_CHATTER;
2907                       savingComment = FALSE;
2908                     }
2909                     loggedOn = TRUE;
2910                     continue;
2911                   }
2912                 }
2913
2914                 if (looking_at(buf, &i, "* s-shouts: ") ||
2915                     looking_at(buf, &i, "* c-shouts: ")) {
2916                     if (appData.colorize) {
2917                         if (oldi > next_out) {
2918                             SendToPlayer(&buf[next_out], oldi - next_out);
2919                             next_out = oldi;
2920                         }
2921                         Colorize(ColorSShout, FALSE);
2922                         curColor = ColorSShout;
2923                     }
2924                     loggedOn = TRUE;
2925                     started = STARTED_CHATTER;
2926                     continue;
2927                 }
2928
2929                 if (looking_at(buf, &i, "--->")) {
2930                     loggedOn = TRUE;
2931                     continue;
2932                 }
2933
2934                 if (looking_at(buf, &i, "* shouts: ") ||
2935                     looking_at(buf, &i, "--> ")) {
2936                     if (appData.colorize) {
2937                         if (oldi > next_out) {
2938                             SendToPlayer(&buf[next_out], oldi - next_out);
2939                             next_out = oldi;
2940                         }
2941                         Colorize(ColorShout, FALSE);
2942                         curColor = ColorShout;
2943                     }
2944                     loggedOn = TRUE;
2945                     started = STARTED_CHATTER;
2946                     continue;
2947                 }
2948
2949                 if (looking_at( buf, &i, "Challenge:")) {
2950                     if (appData.colorize) {
2951                         if (oldi > next_out) {
2952                             SendToPlayer(&buf[next_out], oldi - next_out);
2953                             next_out = oldi;
2954                         }
2955                         Colorize(ColorChallenge, FALSE);
2956                         curColor = ColorChallenge;
2957                     }
2958                     loggedOn = TRUE;
2959                     continue;
2960                 }
2961
2962                 if (looking_at(buf, &i, "* offers you") ||
2963                     looking_at(buf, &i, "* offers to be") ||
2964                     looking_at(buf, &i, "* would like to") ||
2965                     looking_at(buf, &i, "* requests to") ||
2966                     looking_at(buf, &i, "Your opponent offers") ||
2967                     looking_at(buf, &i, "Your opponent requests")) {
2968
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorRequest, FALSE);
2975                         curColor = ColorRequest;
2976                     }
2977                     continue;
2978                 }
2979
2980                 if (looking_at(buf, &i, "* (*) seeking")) {
2981                     if (appData.colorize) {
2982                         if (oldi > next_out) {
2983                             SendToPlayer(&buf[next_out], oldi - next_out);
2984                             next_out = oldi;
2985                         }
2986                         Colorize(ColorSeek, FALSE);
2987                         curColor = ColorSeek;
2988                     }
2989                     continue;
2990             }
2991
2992             if (looking_at(buf, &i, "\\   ")) {
2993                 if (prevColor != ColorNormal) {
2994                     if (oldi > next_out) {
2995                         SendToPlayer(&buf[next_out], oldi - next_out);
2996                         next_out = oldi;
2997                     }
2998                     Colorize(prevColor, TRUE);
2999                     curColor = prevColor;
3000                 }
3001                 if (savingComment) {
3002                     parse_pos = i - oldi;
3003                     memcpy(parse, &buf[oldi], parse_pos);
3004                     parse[parse_pos] = NULLCHAR;
3005                     started = STARTED_COMMENT;
3006                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3007                         chattingPartner = savingComment - 3; // kludge to remember the box
3008                 } else {
3009                     started = STARTED_CHATTER;
3010                 }
3011                 continue;
3012             }
3013
3014             if (looking_at(buf, &i, "Black Strength :") ||
3015                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3016                 looking_at(buf, &i, "<10>") ||
3017                 looking_at(buf, &i, "#@#")) {
3018                 /* Wrong board style */
3019                 loggedOn = TRUE;
3020                 SendToICS(ics_prefix);
3021                 SendToICS("set style 12\n");
3022                 SendToICS(ics_prefix);
3023                 SendToICS("refresh\n");
3024                 continue;
3025             }
3026             
3027             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3028                 ICSInitScript();
3029                 have_sent_ICS_logon = 1;
3030                 continue;
3031             }
3032               
3033             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3034                 (looking_at(buf, &i, "\n<12> ") ||
3035                  looking_at(buf, &i, "<12> "))) {
3036                 loggedOn = TRUE;
3037                 if (oldi > next_out) {
3038                     SendToPlayer(&buf[next_out], oldi - next_out);
3039                 }
3040                 next_out = i;
3041                 started = STARTED_BOARD;
3042                 parse_pos = 0;
3043                 continue;
3044             }
3045
3046             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3047                 looking_at(buf, &i, "<b1> ")) {
3048                 if (oldi > next_out) {
3049                     SendToPlayer(&buf[next_out], oldi - next_out);
3050                 }
3051                 next_out = i;
3052                 started = STARTED_HOLDINGS;
3053                 parse_pos = 0;
3054                 continue;
3055             }
3056
3057             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3058                 loggedOn = TRUE;
3059                 /* Header for a move list -- first line */
3060
3061                 switch (ics_getting_history) {
3062                   case H_FALSE:
3063                     switch (gameMode) {
3064                       case IcsIdle:
3065                       case BeginningOfGame:
3066                         /* User typed "moves" or "oldmoves" while we
3067                            were idle.  Pretend we asked for these
3068                            moves and soak them up so user can step
3069                            through them and/or save them.
3070                            */
3071                         Reset(FALSE, TRUE);
3072                         gameMode = IcsObserving;
3073                         ModeHighlight();
3074                         ics_gamenum = -1;
3075                         ics_getting_history = H_GOT_UNREQ_HEADER;
3076                         break;
3077                       case EditGame: /*?*/
3078                       case EditPosition: /*?*/
3079                         /* Should above feature work in these modes too? */
3080                         /* For now it doesn't */
3081                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3082                         break;
3083                       default:
3084                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3085                         break;
3086                     }
3087                     break;
3088                   case H_REQUESTED:
3089                     /* Is this the right one? */
3090                     if (gameInfo.white && gameInfo.black &&
3091                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3092                         strcmp(gameInfo.black, star_match[2]) == 0) {
3093                         /* All is well */
3094                         ics_getting_history = H_GOT_REQ_HEADER;
3095                     }
3096                     break;
3097                   case H_GOT_REQ_HEADER:
3098                   case H_GOT_UNREQ_HEADER:
3099                   case H_GOT_UNWANTED_HEADER:
3100                   case H_GETTING_MOVES:
3101                     /* Should not happen */
3102                     DisplayError(_("Error gathering move list: two headers"), 0);
3103                     ics_getting_history = H_FALSE;
3104                     break;
3105                 }
3106
3107                 /* Save player ratings into gameInfo if needed */
3108                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3109                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3110                     (gameInfo.whiteRating == -1 ||
3111                      gameInfo.blackRating == -1)) {
3112
3113                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3114                     gameInfo.blackRating = string_to_rating(star_match[3]);
3115                     if (appData.debugMode)
3116                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3117                               gameInfo.whiteRating, gameInfo.blackRating);
3118                 }
3119                 continue;
3120             }
3121
3122             if (looking_at(buf, &i,
3123               "* * match, initial time: * minute*, increment: * second")) {
3124                 /* Header for a move list -- second line */
3125                 /* Initial board will follow if this is a wild game */
3126                 if (gameInfo.event != NULL) free(gameInfo.event);
3127                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3128                 gameInfo.event = StrSave(str);
3129                 /* [HGM] we switched variant. Translate boards if needed. */
3130                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3131                 continue;
3132             }
3133
3134             if (looking_at(buf, &i, "Move  ")) {
3135                 /* Beginning of a move list */
3136                 switch (ics_getting_history) {
3137                   case H_FALSE:
3138                     /* Normally should not happen */
3139                     /* Maybe user hit reset while we were parsing */
3140                     break;
3141                   case H_REQUESTED:
3142                     /* Happens if we are ignoring a move list that is not
3143                      * the one we just requested.  Common if the user
3144                      * tries to observe two games without turning off
3145                      * getMoveList */
3146                     break;
3147                   case H_GETTING_MOVES:
3148                     /* Should not happen */
3149                     DisplayError(_("Error gathering move list: nested"), 0);
3150                     ics_getting_history = H_FALSE;
3151                     break;
3152                   case H_GOT_REQ_HEADER:
3153                     ics_getting_history = H_GETTING_MOVES;
3154                     started = STARTED_MOVES;
3155                     parse_pos = 0;
3156                     if (oldi > next_out) {
3157                         SendToPlayer(&buf[next_out], oldi - next_out);
3158                     }
3159                     break;
3160                   case H_GOT_UNREQ_HEADER:
3161                     ics_getting_history = H_GETTING_MOVES;
3162                     started = STARTED_MOVES_NOHIDE;
3163                     parse_pos = 0;
3164                     break;
3165                   case H_GOT_UNWANTED_HEADER:
3166                     ics_getting_history = H_FALSE;
3167                     break;
3168                 }
3169                 continue;
3170             }                           
3171             
3172             if (looking_at(buf, &i, "% ") ||
3173                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3174                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3175                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3176                     soughtPending = FALSE;
3177                     seekGraphUp = TRUE;
3178                     DrawSeekGraph();
3179                 }
3180                 if(suppressKibitz) next_out = i;
3181                 savingComment = FALSE;
3182                 suppressKibitz = 0;
3183                 switch (started) {
3184                   case STARTED_MOVES:
3185                   case STARTED_MOVES_NOHIDE:
3186                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3187                     parse[parse_pos + i - oldi] = NULLCHAR;
3188                     ParseGameHistory(parse);
3189 #if ZIPPY
3190                     if (appData.zippyPlay && first.initDone) {
3191                         FeedMovesToProgram(&first, forwardMostMove);
3192                         if (gameMode == IcsPlayingWhite) {
3193                             if (WhiteOnMove(forwardMostMove)) {
3194                                 if (first.sendTime) {
3195                                   if (first.useColors) {
3196                                     SendToProgram("black\n", &first); 
3197                                   }
3198                                   SendTimeRemaining(&first, TRUE);
3199                                 }
3200                                 if (first.useColors) {
3201                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3202                                 }
3203                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3204                                 first.maybeThinking = TRUE;
3205                             } else {
3206                                 if (first.usePlayother) {
3207                                   if (first.sendTime) {
3208                                     SendTimeRemaining(&first, TRUE);
3209                                   }
3210                                   SendToProgram("playother\n", &first);
3211                                   firstMove = FALSE;
3212                                 } else {
3213                                   firstMove = TRUE;
3214                                 }
3215                             }
3216                         } else if (gameMode == IcsPlayingBlack) {
3217                             if (!WhiteOnMove(forwardMostMove)) {
3218                                 if (first.sendTime) {
3219                                   if (first.useColors) {
3220                                     SendToProgram("white\n", &first);
3221                                   }
3222                                   SendTimeRemaining(&first, FALSE);
3223                                 }
3224                                 if (first.useColors) {
3225                                   SendToProgram("black\n", &first);
3226                                 }
3227                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3228                                 first.maybeThinking = TRUE;
3229                             } else {
3230                                 if (first.usePlayother) {
3231                                   if (first.sendTime) {
3232                                     SendTimeRemaining(&first, FALSE);
3233                                   }
3234                                   SendToProgram("playother\n", &first);
3235                                   firstMove = FALSE;
3236                                 } else {
3237                                   firstMove = TRUE;
3238                                 }
3239                             }
3240                         }                       
3241                     }
3242 #endif
3243                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3244                         /* Moves came from oldmoves or moves command
3245                            while we weren't doing anything else.
3246                            */
3247                         currentMove = forwardMostMove;
3248                         ClearHighlights();/*!!could figure this out*/
3249                         flipView = appData.flipView;
3250                         DrawPosition(TRUE, boards[currentMove]);
3251                         DisplayBothClocks();
3252                         sprintf(str, "%s vs. %s",
3253                                 gameInfo.white, gameInfo.black);
3254                         DisplayTitle(str);
3255                         gameMode = IcsIdle;
3256                     } else {
3257                         /* Moves were history of an active game */
3258                         if (gameInfo.resultDetails != NULL) {
3259                             free(gameInfo.resultDetails);
3260                             gameInfo.resultDetails = NULL;
3261                         }
3262                     }
3263                     HistorySet(parseList, backwardMostMove,
3264                                forwardMostMove, currentMove-1);
3265                     DisplayMove(currentMove - 1);
3266                     if (started == STARTED_MOVES) next_out = i;
3267                     started = STARTED_NONE;
3268                     ics_getting_history = H_FALSE;
3269                     break;
3270
3271                   case STARTED_OBSERVE:
3272                     started = STARTED_NONE;
3273                     SendToICS(ics_prefix);
3274                     SendToICS("refresh\n");
3275                     break;
3276
3277                   default:
3278                     break;
3279                 }
3280                 if(bookHit) { // [HGM] book: simulate book reply
3281                     static char bookMove[MSG_SIZ]; // a bit generous?
3282
3283                     programStats.nodes = programStats.depth = programStats.time = 
3284                     programStats.score = programStats.got_only_move = 0;
3285                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3286
3287                     strcpy(bookMove, "move ");
3288                     strcat(bookMove, bookHit);
3289                     HandleMachineMove(bookMove, &first);
3290                 }
3291                 continue;
3292             }
3293             
3294             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3295                  started == STARTED_HOLDINGS ||
3296                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3297                 /* Accumulate characters in move list or board */
3298                 parse[parse_pos++] = buf[i];
3299             }
3300             
3301             /* Start of game messages.  Mostly we detect start of game
3302                when the first board image arrives.  On some versions
3303                of the ICS, though, we need to do a "refresh" after starting
3304                to observe in order to get the current board right away. */
3305             if (looking_at(buf, &i, "Adding game * to observation list")) {
3306                 started = STARTED_OBSERVE;
3307                 continue;
3308             }
3309
3310             /* Handle auto-observe */
3311             if (appData.autoObserve &&
3312                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3313                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3314                 char *player;
3315                 /* Choose the player that was highlighted, if any. */
3316                 if (star_match[0][0] == '\033' ||
3317                     star_match[1][0] != '\033') {
3318                     player = star_match[0];
3319                 } else {
3320                     player = star_match[2];
3321                 }
3322                 sprintf(str, "%sobserve %s\n",
3323                         ics_prefix, StripHighlightAndTitle(player));
3324                 SendToICS(str);
3325
3326                 /* Save ratings from notify string */
3327                 strcpy(player1Name, star_match[0]);
3328                 player1Rating = string_to_rating(star_match[1]);
3329                 strcpy(player2Name, star_match[2]);
3330                 player2Rating = string_to_rating(star_match[3]);
3331
3332                 if (appData.debugMode)
3333                   fprintf(debugFP, 
3334                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3335                           player1Name, player1Rating,
3336                           player2Name, player2Rating);
3337
3338                 continue;
3339             }
3340
3341             /* Deal with automatic examine mode after a game,
3342                and with IcsObserving -> IcsExamining transition */
3343             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3344                 looking_at(buf, &i, "has made you an examiner of game *")) {
3345
3346                 int gamenum = atoi(star_match[0]);
3347                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3348                     gamenum == ics_gamenum) {
3349                     /* We were already playing or observing this game;
3350                        no need to refetch history */
3351                     gameMode = IcsExamining;
3352                     if (pausing) {
3353                         pauseExamForwardMostMove = forwardMostMove;
3354                     } else if (currentMove < forwardMostMove) {
3355                         ForwardInner(forwardMostMove);
3356                     }
3357                 } else {
3358                     /* I don't think this case really can happen */
3359                     SendToICS(ics_prefix);
3360                     SendToICS("refresh\n");
3361                 }
3362                 continue;
3363             }    
3364             
3365             /* Error messages */
3366 //          if (ics_user_moved) {
3367             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3368                 if (looking_at(buf, &i, "Illegal move") ||
3369                     looking_at(buf, &i, "Not a legal move") ||
3370                     looking_at(buf, &i, "Your king is in check") ||
3371                     looking_at(buf, &i, "It isn't your turn") ||
3372                     looking_at(buf, &i, "It is not your move")) {
3373                     /* Illegal move */
3374                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3375                         currentMove = forwardMostMove-1;
3376                         DisplayMove(currentMove - 1); /* before DMError */
3377                         DrawPosition(FALSE, boards[currentMove]);
3378                         SwitchClocks(forwardMostMove-1); // [HGM] race
3379                         DisplayBothClocks();
3380                     }
3381                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3382                     ics_user_moved = 0;
3383                     continue;
3384                 }
3385             }
3386
3387             if (looking_at(buf, &i, "still have time") ||
3388                 looking_at(buf, &i, "not out of time") ||
3389                 looking_at(buf, &i, "either player is out of time") ||
3390                 looking_at(buf, &i, "has timeseal; checking")) {
3391                 /* We must have called his flag a little too soon */
3392                 whiteFlag = blackFlag = FALSE;
3393                 continue;
3394             }
3395
3396             if (looking_at(buf, &i, "added * seconds to") ||
3397                 looking_at(buf, &i, "seconds were added to")) {
3398                 /* Update the clocks */
3399                 SendToICS(ics_prefix);
3400                 SendToICS("refresh\n");
3401                 continue;
3402             }
3403
3404             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3405                 ics_clock_paused = TRUE;
3406                 StopClocks();
3407                 continue;
3408             }
3409
3410             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3411                 ics_clock_paused = FALSE;
3412                 StartClocks();
3413                 continue;
3414             }
3415
3416             /* Grab player ratings from the Creating: message.
3417                Note we have to check for the special case when
3418                the ICS inserts things like [white] or [black]. */
3419             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3420                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3421                 /* star_matches:
3422                    0    player 1 name (not necessarily white)
3423                    1    player 1 rating
3424                    2    empty, white, or black (IGNORED)
3425                    3    player 2 name (not necessarily black)
3426                    4    player 2 rating
3427                    
3428                    The names/ratings are sorted out when the game
3429                    actually starts (below).
3430                 */
3431                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3434                 player2Rating = string_to_rating(star_match[4]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP, 
3438                           "Ratings from 'Creating:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444             
3445             /* Improved generic start/end-of-game messages */
3446             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3447                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3448                 /* If tkind == 0: */
3449                 /* star_match[0] is the game number */
3450                 /*           [1] is the white player's name */
3451                 /*           [2] is the black player's name */
3452                 /* For end-of-game: */
3453                 /*           [3] is the reason for the game end */
3454                 /*           [4] is a PGN end game-token, preceded by " " */
3455                 /* For start-of-game: */
3456                 /*           [3] begins with "Creating" or "Continuing" */
3457                 /*           [4] is " *" or empty (don't care). */
3458                 int gamenum = atoi(star_match[0]);
3459                 char *whitename, *blackname, *why, *endtoken;
3460                 ChessMove endtype = (ChessMove) 0;
3461
3462                 if (tkind == 0) {
3463                   whitename = star_match[1];
3464                   blackname = star_match[2];
3465                   why = star_match[3];
3466                   endtoken = star_match[4];
3467                 } else {
3468                   whitename = star_match[1];
3469                   blackname = star_match[3];
3470                   why = star_match[5];
3471                   endtoken = star_match[6];
3472                 }
3473
3474                 /* Game start messages */
3475                 if (strncmp(why, "Creating ", 9) == 0 ||
3476                     strncmp(why, "Continuing ", 11) == 0) {
3477                     gs_gamenum = gamenum;
3478                     strcpy(gs_kind, strchr(why, ' ') + 1);
3479                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3480 #if ZIPPY
3481                     if (appData.zippyPlay) {
3482                         ZippyGameStart(whitename, blackname);
3483                     }
3484 #endif /*ZIPPY*/
3485                     partnerBoardValid = FALSE; // [HGM] bughouse
3486                     continue;
3487                 }
3488
3489                 /* Game end messages */
3490                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3491                     ics_gamenum != gamenum) {
3492                     continue;
3493                 }
3494                 while (endtoken[0] == ' ') endtoken++;
3495                 switch (endtoken[0]) {
3496                   case '*':
3497                   default:
3498                     endtype = GameUnfinished;
3499                     break;
3500                   case '0':
3501                     endtype = BlackWins;
3502                     break;
3503                   case '1':
3504                     if (endtoken[1] == '/')
3505                       endtype = GameIsDrawn;
3506                     else
3507                       endtype = WhiteWins;
3508                     break;
3509                 }
3510                 GameEnds(endtype, why, GE_ICS);
3511 #if ZIPPY
3512                 if (appData.zippyPlay && first.initDone) {
3513                     ZippyGameEnd(endtype, why);
3514                     if (first.pr == NULL) {
3515                       /* Start the next process early so that we'll
3516                          be ready for the next challenge */
3517                       StartChessProgram(&first);
3518                     }
3519                     /* Send "new" early, in case this command takes
3520                        a long time to finish, so that we'll be ready
3521                        for the next challenge. */
3522                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3523                     Reset(TRUE, TRUE);
3524                 }
3525 #endif /*ZIPPY*/
3526                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3527                 continue;
3528             }
3529
3530             if (looking_at(buf, &i, "Removing game * from observation") ||
3531                 looking_at(buf, &i, "no longer observing game *") ||
3532                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3533                 if (gameMode == IcsObserving &&
3534                     atoi(star_match[0]) == ics_gamenum)
3535                   {
3536                       /* icsEngineAnalyze */
3537                       if (appData.icsEngineAnalyze) {
3538                             ExitAnalyzeMode();
3539                             ModeHighlight();
3540                       }
3541                       StopClocks();
3542                       gameMode = IcsIdle;
3543                       ics_gamenum = -1;
3544                       ics_user_moved = FALSE;
3545                   }
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "no longer examining game *")) {
3550                 if (gameMode == IcsExamining &&
3551                     atoi(star_match[0]) == ics_gamenum)
3552                   {
3553                       gameMode = IcsIdle;
3554                       ics_gamenum = -1;
3555                       ics_user_moved = FALSE;
3556                   }
3557                 continue;
3558             }
3559
3560             /* Advance leftover_start past any newlines we find,
3561                so only partial lines can get reparsed */
3562             if (looking_at(buf, &i, "\n")) {
3563                 prevColor = curColor;
3564                 if (curColor != ColorNormal) {
3565                     if (oldi > next_out) {
3566                         SendToPlayer(&buf[next_out], oldi - next_out);
3567                         next_out = oldi;
3568                     }
3569                     Colorize(ColorNormal, FALSE);
3570                     curColor = ColorNormal;
3571                 }
3572                 if (started == STARTED_BOARD) {
3573                     started = STARTED_NONE;
3574                     parse[parse_pos] = NULLCHAR;
3575                     ParseBoard12(parse);
3576                     ics_user_moved = 0;
3577
3578                     /* Send premove here */
3579                     if (appData.premove) {
3580                       char str[MSG_SIZ];
3581                       if (currentMove == 0 &&
3582                           gameMode == IcsPlayingWhite &&
3583                           appData.premoveWhite) {
3584                         sprintf(str, "%s\n", appData.premoveWhiteText);
3585                         if (appData.debugMode)
3586                           fprintf(debugFP, "Sending premove:\n");
3587                         SendToICS(str);
3588                       } else if (currentMove == 1 &&
3589                                  gameMode == IcsPlayingBlack &&
3590                                  appData.premoveBlack) {
3591                         sprintf(str, "%s\n", appData.premoveBlackText);
3592                         if (appData.debugMode)
3593                           fprintf(debugFP, "Sending premove:\n");
3594                         SendToICS(str);
3595                       } else if (gotPremove) {
3596                         gotPremove = 0;
3597                         ClearPremoveHighlights();
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                           UserMoveEvent(premoveFromX, premoveFromY, 
3601                                         premoveToX, premoveToY, 
3602                                         premovePromoChar);
3603                       }
3604                     }
3605
3606                     /* Usually suppress following prompt */
3607                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3608                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3609                         if (looking_at(buf, &i, "*% ")) {
3610                             savingComment = FALSE;
3611                             suppressKibitz = 0;
3612                         }
3613                     }
3614                     next_out = i;
3615                 } else if (started == STARTED_HOLDINGS) {
3616                     int gamenum;
3617                     char new_piece[MSG_SIZ];
3618                     started = STARTED_NONE;
3619                     parse[parse_pos] = NULLCHAR;
3620                     if (appData.debugMode)
3621                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3622                                                         parse, currentMove);
3623                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3624                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3625                         if (gameInfo.variant == VariantNormal) {
3626                           /* [HGM] We seem to switch variant during a game!
3627                            * Presumably no holdings were displayed, so we have
3628                            * to move the position two files to the right to
3629                            * create room for them!
3630                            */
3631                           VariantClass newVariant;
3632                           switch(gameInfo.boardWidth) { // base guess on board width
3633                                 case 9:  newVariant = VariantShogi; break;
3634                                 case 10: newVariant = VariantGreat; break;
3635                                 default: newVariant = VariantCrazyhouse; break;
3636                           }
3637                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3638                           /* Get a move list just to see the header, which
3639                              will tell us whether this is really bug or zh */
3640                           if (ics_getting_history == H_FALSE) {
3641                             ics_getting_history = H_REQUESTED;
3642                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3643                             SendToICS(str);
3644                           }
3645                         }
3646                         new_piece[0] = NULLCHAR;
3647                         sscanf(parse, "game %d white [%s black [%s <- %s",
3648                                &gamenum, white_holding, black_holding,
3649                                new_piece);
3650                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3651                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3652                         /* [HGM] copy holdings to board holdings area */
3653                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3654                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3655                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3656 #if ZIPPY
3657                         if (appData.zippyPlay && first.initDone) {
3658                             ZippyHoldings(white_holding, black_holding,
3659                                           new_piece);
3660                         }
3661 #endif /*ZIPPY*/
3662                         if (tinyLayout || smallLayout) {
3663                             char wh[16], bh[16];
3664                             PackHolding(wh, white_holding);
3665                             PackHolding(bh, black_holding);
3666                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3667                                     gameInfo.white, gameInfo.black);
3668                         } else {
3669                             sprintf(str, "%s [%s] vs. %s [%s]",
3670                                     gameInfo.white, white_holding,
3671                                     gameInfo.black, black_holding);
3672                         }
3673                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3674                         DrawPosition(FALSE, boards[currentMove]);
3675                         DisplayTitle(str);
3676                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3677                         sscanf(parse, "game %d white [%s black [%s <- %s",
3678                                &gamenum, white_holding, black_holding,
3679                                new_piece);
3680                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3681                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3682                         /* [HGM] copy holdings to partner-board holdings area */
3683                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3684                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3685                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3686                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3687                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3688                       }
3689                     }
3690                     /* Suppress following prompt */
3691                     if (looking_at(buf, &i, "*% ")) {
3692                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693                         savingComment = FALSE;
3694                         suppressKibitz = 0;
3695                     }
3696                     next_out = i;
3697                 }
3698                 continue;
3699             }
3700
3701             i++;                /* skip unparsed character and loop back */
3702         }
3703         
3704         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 //          SendToPlayer(&buf[next_out], i - next_out);
3707             started != STARTED_HOLDINGS && leftover_start > next_out) {
3708             SendToPlayer(&buf[next_out], leftover_start - next_out);
3709             next_out = i;
3710         }
3711         
3712         leftover_len = buf_len - leftover_start;
3713         /* if buffer ends with something we couldn't parse,
3714            reparse it after appending the next read */
3715         
3716     } else if (count == 0) {
3717         RemoveInputSource(isr);
3718         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3719     } else {
3720         DisplayFatalError(_("Error reading from ICS"), error, 1);
3721     }
3722 }
3723
3724
3725 /* Board style 12 looks like this:
3726    
3727    <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
3728    
3729  * The "<12> " is stripped before it gets to this routine.  The two
3730  * trailing 0's (flip state and clock ticking) are later addition, and
3731  * some chess servers may not have them, or may have only the first.
3732  * Additional trailing fields may be added in the future.  
3733  */
3734
3735 #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"
3736
3737 #define RELATION_OBSERVING_PLAYED    0
3738 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE      1
3740 #define RELATION_PLAYING_NOTMYMOVE  -1
3741 #define RELATION_EXAMINING           2
3742 #define RELATION_ISOLATED_BOARD     -3
3743 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3744
3745 void
3746 ParseBoard12(string)
3747      char *string;
3748
3749     GameMode newGameMode;
3750     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753     char to_play, board_chars[200];
3754     char move_str[500], str[500], elapsed_time[500];
3755     char black[32], white[32];
3756     Board board;
3757     int prevMove = currentMove;
3758     int ticking = 2;
3759     ChessMove moveType;
3760     int fromX, fromY, toX, toY;
3761     char promoChar;
3762     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763     char *bookHit = NULL; // [HGM] book
3764     Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3765
3766     fromX = fromY = toX = toY = -1;
3767     
3768     newGame = FALSE;
3769
3770     if (appData.debugMode)
3771       fprintf(debugFP, _("Parsing board: %s\n"), string);
3772
3773     move_str[0] = NULLCHAR;
3774     elapsed_time[0] = NULLCHAR;
3775     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3776         int  i = 0, j;
3777         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778             if(string[i] == ' ') { ranks++; files = 0; }
3779             else files++;
3780             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3781             i++;
3782         }
3783         for(j = 0; j <i; j++) board_chars[j] = string[j];
3784         board_chars[i] = '\0';
3785         string += i + 1;
3786     }
3787     n = sscanf(string, PATTERN, &to_play, &double_push,
3788                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789                &gamenum, white, black, &relation, &basetime, &increment,
3790                &white_stren, &black_stren, &white_time, &black_time,
3791                &moveNum, str, elapsed_time, move_str, &ics_flip,
3792                &ticking);
3793
3794     if (n < 21) {
3795         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796         DisplayError(str, 0);
3797         return;
3798     }
3799
3800     /* Convert the move number to internal form */
3801     moveNum = (moveNum - 1) * 2;
3802     if (to_play == 'B') moveNum++;
3803     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3805                         0, 1);
3806       return;
3807     }
3808     
3809     switch (relation) {
3810       case RELATION_OBSERVING_PLAYED:
3811       case RELATION_OBSERVING_STATIC:
3812         if (gamenum == -1) {
3813             /* Old ICC buglet */
3814             relation = RELATION_OBSERVING_STATIC;
3815         }
3816         newGameMode = IcsObserving;
3817         break;
3818       case RELATION_PLAYING_MYMOVE:
3819       case RELATION_PLAYING_NOTMYMOVE:
3820         newGameMode =
3821           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822             IcsPlayingWhite : IcsPlayingBlack;
3823         break;
3824       case RELATION_EXAMINING:
3825         newGameMode = IcsExamining;
3826         break;
3827       case RELATION_ISOLATED_BOARD:
3828       default:
3829         /* Just display this board.  If user was doing something else,
3830            we will forget about it until the next board comes. */ 
3831         newGameMode = IcsIdle;
3832         break;
3833       case RELATION_STARTING_POSITION:
3834         newGameMode = gameMode;
3835         break;
3836     }
3837     
3838     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3839          && newGameMode == IcsObserving && appData.bgObserve) {
3840       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3841       for (k = 0; k < ranks; k++) {
3842         for (j = 0; j < files; j++)
3843           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3844         if(gameInfo.holdingsWidth > 1) {
3845              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3846              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3847         }
3848       }
3849       CopyBoard(partnerBoard, board);
3850       if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3851       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3852       if(partnerUp) DrawPosition(repaint, partnerBoard);
3853       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3854       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3855                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3856       DisplayMessage(partnerStatus, "");
3857         partnerBoardValid = TRUE;
3858       return;
3859     }
3860
3861     /* Modify behavior for initial board display on move listing
3862        of wild games.
3863        */
3864     switch (ics_getting_history) {
3865       case H_FALSE:
3866       case H_REQUESTED:
3867         break;
3868       case H_GOT_REQ_HEADER:
3869       case H_GOT_UNREQ_HEADER:
3870         /* This is the initial position of the current game */
3871         gamenum = ics_gamenum;
3872         moveNum = 0;            /* old ICS bug workaround */
3873         if (to_play == 'B') {
3874           startedFromSetupPosition = TRUE;
3875           blackPlaysFirst = TRUE;
3876           moveNum = 1;
3877           if (forwardMostMove == 0) forwardMostMove = 1;
3878           if (backwardMostMove == 0) backwardMostMove = 1;
3879           if (currentMove == 0) currentMove = 1;
3880         }
3881         newGameMode = gameMode;
3882         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3883         break;
3884       case H_GOT_UNWANTED_HEADER:
3885         /* This is an initial board that we don't want */
3886         return;
3887       case H_GETTING_MOVES:
3888         /* Should not happen */
3889         DisplayError(_("Error gathering move list: extra board"), 0);
3890         ics_getting_history = H_FALSE;
3891         return;
3892     }
3893
3894    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3895                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3896      /* [HGM] We seem to have switched variant unexpectedly
3897       * Try to guess new variant from board size
3898       */
3899           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3900           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3901           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3902           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3903           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3904           if(!weird) newVariant = VariantNormal;
3905           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3906           /* Get a move list just to see the header, which
3907              will tell us whether this is really bug or zh */
3908           if (ics_getting_history == H_FALSE) {
3909             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3910             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3911             SendToICS(str);
3912           }
3913     }
3914     
3915     /* Take action if this is the first board of a new game, or of a
3916        different game than is currently being displayed.  */
3917     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3918         relation == RELATION_ISOLATED_BOARD) {
3919         
3920         /* Forget the old game and get the history (if any) of the new one */
3921         if (gameMode != BeginningOfGame) {
3922           Reset(TRUE, TRUE);
3923         }
3924         newGame = TRUE;
3925         if (appData.autoRaiseBoard) BoardToTop();
3926         prevMove = -3;
3927         if (gamenum == -1) {
3928             newGameMode = IcsIdle;
3929         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3930                    appData.getMoveList && !reqFlag) {
3931             /* Need to get game history */
3932             ics_getting_history = H_REQUESTED;
3933             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3934             SendToICS(str);
3935         }
3936         
3937         /* Initially flip the board to have black on the bottom if playing
3938            black or if the ICS flip flag is set, but let the user change
3939            it with the Flip View button. */
3940         flipView = appData.autoFlipView ? 
3941           (newGameMode == IcsPlayingBlack) || ics_flip :
3942           appData.flipView;
3943         
3944         /* Done with values from previous mode; copy in new ones */
3945         gameMode = newGameMode;
3946         ModeHighlight();
3947         ics_gamenum = gamenum;
3948         if (gamenum == gs_gamenum) {
3949             int klen = strlen(gs_kind);
3950             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3951             sprintf(str, "ICS %s", gs_kind);
3952             gameInfo.event = StrSave(str);
3953         } else {
3954             gameInfo.event = StrSave("ICS game");
3955         }
3956         gameInfo.site = StrSave(appData.icsHost);
3957         gameInfo.date = PGNDate();
3958         gameInfo.round = StrSave("-");
3959         gameInfo.white = StrSave(white);
3960         gameInfo.black = StrSave(black);
3961         timeControl = basetime * 60 * 1000;
3962         timeControl_2 = 0;
3963         timeIncrement = increment * 1000;
3964         movesPerSession = 0;
3965         gameInfo.timeControl = TimeControlTagValue();
3966         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3967   if (appData.debugMode) {
3968     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3969     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3970     setbuf(debugFP, NULL);
3971   }
3972
3973         gameInfo.outOfBook = NULL;
3974         
3975         /* Do we have the ratings? */
3976         if (strcmp(player1Name, white) == 0 &&
3977             strcmp(player2Name, black) == 0) {
3978             if (appData.debugMode)
3979               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3980                       player1Rating, player2Rating);
3981             gameInfo.whiteRating = player1Rating;
3982             gameInfo.blackRating = player2Rating;
3983         } else if (strcmp(player2Name, white) == 0 &&
3984                    strcmp(player1Name, black) == 0) {
3985             if (appData.debugMode)
3986               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3987                       player2Rating, player1Rating);
3988             gameInfo.whiteRating = player2Rating;
3989             gameInfo.blackRating = player1Rating;
3990         }
3991         player1Name[0] = player2Name[0] = NULLCHAR;
3992
3993         /* Silence shouts if requested */
3994         if (appData.quietPlay &&
3995             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3996             SendToICS(ics_prefix);
3997             SendToICS("set shout 0\n");
3998         }
3999     }
4000     
4001     /* Deal with midgame name changes */
4002     if (!newGame) {
4003         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4004             if (gameInfo.white) free(gameInfo.white);
4005             gameInfo.white = StrSave(white);
4006         }
4007         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4008             if (gameInfo.black) free(gameInfo.black);
4009             gameInfo.black = StrSave(black);
4010         }
4011     }
4012     
4013     /* Throw away game result if anything actually changes in examine mode */
4014     if (gameMode == IcsExamining && !newGame) {
4015         gameInfo.result = GameUnfinished;
4016         if (gameInfo.resultDetails != NULL) {
4017             free(gameInfo.resultDetails);
4018             gameInfo.resultDetails = NULL;
4019         }
4020     }
4021     
4022     /* In pausing && IcsExamining mode, we ignore boards coming
4023        in if they are in a different variation than we are. */
4024     if (pauseExamInvalid) return;
4025     if (pausing && gameMode == IcsExamining) {
4026         if (moveNum <= pauseExamForwardMostMove) {
4027             pauseExamInvalid = TRUE;
4028             forwardMostMove = pauseExamForwardMostMove;
4029             return;
4030         }
4031     }
4032     
4033   if (appData.debugMode) {
4034     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4035   }
4036     /* Parse the board */
4037     for (k = 0; k < ranks; k++) {
4038       for (j = 0; j < files; j++)
4039         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4040       if(gameInfo.holdingsWidth > 1) {
4041            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4042            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4043       }
4044     }
4045     CopyBoard(boards[moveNum], board);
4046     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4047     if (moveNum == 0) {
4048         startedFromSetupPosition =
4049           !CompareBoards(board, initialPosition);
4050         if(startedFromSetupPosition)
4051             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4052     }
4053
4054     /* [HGM] Set castling rights. Take the outermost Rooks,
4055        to make it also work for FRC opening positions. Note that board12
4056        is really defective for later FRC positions, as it has no way to
4057        indicate which Rook can castle if they are on the same side of King.
4058        For the initial position we grant rights to the outermost Rooks,
4059        and remember thos rights, and we then copy them on positions
4060        later in an FRC game. This means WB might not recognize castlings with
4061        Rooks that have moved back to their original position as illegal,
4062        but in ICS mode that is not its job anyway.
4063     */
4064     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4065     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4066
4067         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4068             if(board[0][i] == WhiteRook) j = i;
4069         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4070         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4071             if(board[0][i] == WhiteRook) j = i;
4072         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4073         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4074             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4075         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4076         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4077             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4078         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4079
4080         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4081         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4082             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4083         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4084             if(board[BOARD_HEIGHT-1][k] == bKing)
4085                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4086         if(gameInfo.variant == VariantTwoKings) {
4087             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4088             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4089             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4090         }
4091     } else { int r;
4092         r = boards[moveNum][CASTLING][0] = initialRights[0];
4093         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4094         r = boards[moveNum][CASTLING][1] = initialRights[1];
4095         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4096         r = boards[moveNum][CASTLING][3] = initialRights[3];
4097         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4098         r = boards[moveNum][CASTLING][4] = initialRights[4];
4099         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4100         /* wildcastle kludge: always assume King has rights */
4101         r = boards[moveNum][CASTLING][2] = initialRights[2];
4102         r = boards[moveNum][CASTLING][5] = initialRights[5];
4103     }
4104     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4105     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4106
4107     
4108     if (ics_getting_history == H_GOT_REQ_HEADER ||
4109         ics_getting_history == H_GOT_UNREQ_HEADER) {
4110         /* This was an initial position from a move list, not
4111            the current position */
4112         return;
4113     }
4114     
4115     /* Update currentMove and known move number limits */
4116     newMove = newGame || moveNum > forwardMostMove;
4117
4118     if (newGame) {
4119         forwardMostMove = backwardMostMove = currentMove = moveNum;
4120         if (gameMode == IcsExamining && moveNum == 0) {
4121           /* Workaround for ICS limitation: we are not told the wild
4122              type when starting to examine a game.  But if we ask for
4123              the move list, the move list header will tell us */
4124             ics_getting_history = H_REQUESTED;
4125             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4126             SendToICS(str);
4127         }
4128     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4129                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4130 #if ZIPPY
4131         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4132         /* [HGM] applied this also to an engine that is silently watching        */
4133         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4134             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4135             gameInfo.variant == currentlyInitializedVariant) {
4136           takeback = forwardMostMove - moveNum;
4137           for (i = 0; i < takeback; i++) {
4138             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4139             SendToProgram("undo\n", &first);
4140           }
4141         }
4142 #endif
4143
4144         forwardMostMove = moveNum;
4145         if (!pausing || currentMove > forwardMostMove)
4146           currentMove = forwardMostMove;
4147     } else {
4148         /* New part of history that is not contiguous with old part */ 
4149         if (pausing && gameMode == IcsExamining) {
4150             pauseExamInvalid = TRUE;
4151             forwardMostMove = pauseExamForwardMostMove;
4152             return;
4153         }
4154         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4155 #if ZIPPY
4156             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4157                 // [HGM] when we will receive the move list we now request, it will be
4158                 // fed to the engine from the first move on. So if the engine is not
4159                 // in the initial position now, bring it there.
4160                 InitChessProgram(&first, 0);
4161             }
4162 #endif
4163             ics_getting_history = H_REQUESTED;
4164             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4165             SendToICS(str);
4166         }
4167         forwardMostMove = backwardMostMove = currentMove = moveNum;
4168     }
4169     
4170     /* Update the clocks */
4171     if (strchr(elapsed_time, '.')) {
4172       /* Time is in ms */
4173       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4174       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4175     } else {
4176       /* Time is in seconds */
4177       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4178       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4179     }
4180       
4181
4182 #if ZIPPY
4183     if (appData.zippyPlay && newGame &&
4184         gameMode != IcsObserving && gameMode != IcsIdle &&
4185         gameMode != IcsExamining)
4186       ZippyFirstBoard(moveNum, basetime, increment);
4187 #endif
4188     
4189     /* Put the move on the move list, first converting
4190        to canonical algebraic form. */
4191     if (moveNum > 0) {
4192   if (appData.debugMode) {
4193     if (appData.debugMode) { int f = forwardMostMove;
4194         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4195                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4196                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4197     }
4198     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4199     fprintf(debugFP, "moveNum = %d\n", moveNum);
4200     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4201     setbuf(debugFP, NULL);
4202   }
4203         if (moveNum <= backwardMostMove) {
4204             /* We don't know what the board looked like before
4205                this move.  Punt. */
4206             strcpy(parseList[moveNum - 1], move_str);
4207             strcat(parseList[moveNum - 1], " ");
4208             strcat(parseList[moveNum - 1], elapsed_time);
4209             moveList[moveNum - 1][0] = NULLCHAR;
4210         } else if (strcmp(move_str, "none") == 0) {
4211             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4212             /* Again, we don't know what the board looked like;
4213                this is really the start of the game. */
4214             parseList[moveNum - 1][0] = NULLCHAR;
4215             moveList[moveNum - 1][0] = NULLCHAR;
4216             backwardMostMove = moveNum;
4217             startedFromSetupPosition = TRUE;
4218             fromX = fromY = toX = toY = -1;
4219         } else {
4220           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4221           //                 So we parse the long-algebraic move string in stead of the SAN move
4222           int valid; char buf[MSG_SIZ], *prom;
4223
4224           // str looks something like "Q/a1-a2"; kill the slash
4225           if(str[1] == '/') 
4226                 sprintf(buf, "%c%s", str[0], str+2);
4227           else  strcpy(buf, str); // might be castling
4228           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4229                 strcat(buf, prom); // long move lacks promo specification!
4230           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4231                 if(appData.debugMode) 
4232                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4233                 strcpy(move_str, buf);
4234           }
4235           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4236                                 &fromX, &fromY, &toX, &toY, &promoChar)
4237                || ParseOneMove(buf, moveNum - 1, &moveType,
4238                                 &fromX, &fromY, &toX, &toY, &promoChar);
4239           // end of long SAN patch
4240           if (valid) {
4241             (void) CoordsToAlgebraic(boards[moveNum - 1],
4242                                      PosFlags(moveNum - 1),
4243                                      fromY, fromX, toY, toX, promoChar,
4244                                      parseList[moveNum-1]);
4245             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4246               case MT_NONE:
4247               case MT_STALEMATE:
4248               default:
4249                 break;
4250               case MT_CHECK:
4251                 if(gameInfo.variant != VariantShogi)
4252                     strcat(parseList[moveNum - 1], "+");
4253                 break;
4254               case MT_CHECKMATE:
4255               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4256                 strcat(parseList[moveNum - 1], "#");
4257                 break;
4258             }
4259             strcat(parseList[moveNum - 1], " ");
4260             strcat(parseList[moveNum - 1], elapsed_time);
4261             /* currentMoveString is set as a side-effect of ParseOneMove */
4262             strcpy(moveList[moveNum - 1], currentMoveString);
4263             strcat(moveList[moveNum - 1], "\n");
4264           } else {
4265             /* Move from ICS was illegal!?  Punt. */
4266   if (appData.debugMode) {
4267     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4269   }
4270             strcpy(parseList[moveNum - 1], move_str);
4271             strcat(parseList[moveNum - 1], " ");
4272             strcat(parseList[moveNum - 1], elapsed_time);
4273             moveList[moveNum - 1][0] = NULLCHAR;
4274             fromX = fromY = toX = toY = -1;
4275           }
4276         }
4277   if (appData.debugMode) {
4278     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4279     setbuf(debugFP, NULL);
4280   }
4281
4282 #if ZIPPY
4283         /* Send move to chess program (BEFORE animating it). */
4284         if (appData.zippyPlay && !newGame && newMove && 
4285            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4286
4287             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4288                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4289                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4290                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4291                             move_str);
4292                     DisplayError(str, 0);
4293                 } else {
4294                     if (first.sendTime) {
4295                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4296                     }
4297                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4298                     if (firstMove && !bookHit) {
4299                         firstMove = FALSE;
4300                         if (first.useColors) {
4301                           SendToProgram(gameMode == IcsPlayingWhite ?
4302                                         "white\ngo\n" :
4303                                         "black\ngo\n", &first);
4304                         } else {
4305                           SendToProgram("go\n", &first);
4306                         }
4307                         first.maybeThinking = TRUE;
4308                     }
4309                 }
4310             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4311               if (moveList[moveNum - 1][0] == NULLCHAR) {
4312                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4313                 DisplayError(str, 0);
4314               } else {
4315                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4316                 SendMoveToProgram(moveNum - 1, &first);
4317               }
4318             }
4319         }
4320 #endif
4321     }
4322
4323     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4324         /* If move comes from a remote source, animate it.  If it
4325            isn't remote, it will have already been animated. */
4326         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4327             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4328         }
4329         if (!pausing && appData.highlightLastMove) {
4330             SetHighlights(fromX, fromY, toX, toY);
4331         }
4332     }
4333     
4334     /* Start the clocks */
4335     whiteFlag = blackFlag = FALSE;
4336     appData.clockMode = !(basetime == 0 && increment == 0);
4337     if (ticking == 0) {
4338       ics_clock_paused = TRUE;
4339       StopClocks();
4340     } else if (ticking == 1) {
4341       ics_clock_paused = FALSE;
4342     }
4343     if (gameMode == IcsIdle ||
4344         relation == RELATION_OBSERVING_STATIC ||
4345         relation == RELATION_EXAMINING ||
4346         ics_clock_paused)
4347       DisplayBothClocks();
4348     else
4349       StartClocks();
4350     
4351     /* Display opponents and material strengths */
4352     if (gameInfo.variant != VariantBughouse &&
4353         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4354         if (tinyLayout || smallLayout) {
4355             if(gameInfo.variant == VariantNormal)
4356                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4357                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4358                     basetime, increment);
4359             else
4360                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4361                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4362                     basetime, increment, (int) gameInfo.variant);
4363         } else {
4364             if(gameInfo.variant == VariantNormal)
4365                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4366                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4367                     basetime, increment);
4368             else
4369                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4370                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4371                     basetime, increment, VariantName(gameInfo.variant));
4372         }
4373         DisplayTitle(str);
4374   if (appData.debugMode) {
4375     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4376   }
4377     }
4378
4379
4380     /* Display the board */
4381     if (!pausing && !appData.noGUI) {
4382       
4383       if (appData.premove)
4384           if (!gotPremove || 
4385              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4386              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4387               ClearPremoveHighlights();
4388
4389       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4390         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4391       DrawPosition(j, boards[currentMove]);
4392
4393       DisplayMove(moveNum - 1);
4394       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4395             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4396               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4397         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4398       }
4399     }
4400
4401     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4402 #if ZIPPY
4403     if(bookHit) { // [HGM] book: simulate book reply
4404         static char bookMove[MSG_SIZ]; // a bit generous?
4405
4406         programStats.nodes = programStats.depth = programStats.time = 
4407         programStats.score = programStats.got_only_move = 0;
4408         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4409
4410         strcpy(bookMove, "move ");
4411         strcat(bookMove, bookHit);
4412         HandleMachineMove(bookMove, &first);
4413     }
4414 #endif
4415 }
4416
4417 void
4418 GetMoveListEvent()
4419 {
4420     char buf[MSG_SIZ];
4421     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4422         ics_getting_history = H_REQUESTED;
4423         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4424         SendToICS(buf);
4425     }
4426 }
4427
4428 void
4429 AnalysisPeriodicEvent(force)
4430      int force;
4431 {
4432     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4433          && !force) || !appData.periodicUpdates)
4434       return;
4435
4436     /* Send . command to Crafty to collect stats */
4437     SendToProgram(".\n", &first);
4438
4439     /* Don't send another until we get a response (this makes
4440        us stop sending to old Crafty's which don't understand
4441        the "." command (sending illegal cmds resets node count & time,
4442        which looks bad)) */
4443     programStats.ok_to_send = 0;
4444 }
4445
4446 void ics_update_width(new_width)
4447         int new_width;
4448 {
4449         ics_printf("set width %d\n", new_width);
4450 }
4451
4452 void
4453 SendMoveToProgram(moveNum, cps)
4454      int moveNum;
4455      ChessProgramState *cps;
4456 {
4457     char buf[MSG_SIZ];
4458
4459     if (cps->useUsermove) {
4460       SendToProgram("usermove ", cps);
4461     }
4462     if (cps->useSAN) {
4463       char *space;
4464       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4465         int len = space - parseList[moveNum];
4466         memcpy(buf, parseList[moveNum], len);
4467         buf[len++] = '\n';
4468         buf[len] = NULLCHAR;
4469       } else {
4470         sprintf(buf, "%s\n", parseList[moveNum]);
4471       }
4472       SendToProgram(buf, cps);
4473     } else {
4474       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4475         AlphaRank(moveList[moveNum], 4);
4476         SendToProgram(moveList[moveNum], cps);
4477         AlphaRank(moveList[moveNum], 4); // and back
4478       } else
4479       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4480        * the engine. It would be nice to have a better way to identify castle 
4481        * moves here. */
4482       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4483                                                                          && cps->useOOCastle) {
4484         int fromX = moveList[moveNum][0] - AAA; 
4485         int fromY = moveList[moveNum][1] - ONE;
4486         int toX = moveList[moveNum][2] - AAA; 
4487         int toY = moveList[moveNum][3] - ONE;
4488         if((boards[moveNum][fromY][fromX] == WhiteKing 
4489             && boards[moveNum][toY][toX] == WhiteRook)
4490            || (boards[moveNum][fromY][fromX] == BlackKing 
4491                && boards[moveNum][toY][toX] == BlackRook)) {
4492           if(toX > fromX) SendToProgram("O-O\n", cps);
4493           else SendToProgram("O-O-O\n", cps);
4494         }
4495         else SendToProgram(moveList[moveNum], cps);
4496       }
4497       else SendToProgram(moveList[moveNum], cps);
4498       /* End of additions by Tord */
4499     }
4500
4501     /* [HGM] setting up the opening has brought engine in force mode! */
4502     /*       Send 'go' if we are in a mode where machine should play. */
4503     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4504         (gameMode == TwoMachinesPlay   ||
4505 #ifdef ZIPPY
4506          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4507 #endif
4508          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4509         SendToProgram("go\n", cps);
4510   if (appData.debugMode) {
4511     fprintf(debugFP, "(extra)\n");
4512   }
4513     }
4514     setboardSpoiledMachineBlack = 0;
4515 }
4516
4517 void
4518 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4519      ChessMove moveType;
4520      int fromX, fromY, toX, toY;
4521 {
4522     char user_move[MSG_SIZ];
4523
4524     switch (moveType) {
4525       default:
4526         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4527                 (int)moveType, fromX, fromY, toX, toY);
4528         DisplayError(user_move + strlen("say "), 0);
4529         break;
4530       case WhiteKingSideCastle:
4531       case BlackKingSideCastle:
4532       case WhiteQueenSideCastleWild:
4533       case BlackQueenSideCastleWild:
4534       /* PUSH Fabien */
4535       case WhiteHSideCastleFR:
4536       case BlackHSideCastleFR:
4537       /* POP Fabien */
4538         sprintf(user_move, "o-o\n");
4539         break;
4540       case WhiteQueenSideCastle:
4541       case BlackQueenSideCastle:
4542       case WhiteKingSideCastleWild:
4543       case BlackKingSideCastleWild:
4544       /* PUSH Fabien */
4545       case WhiteASideCastleFR:
4546       case BlackASideCastleFR:
4547       /* POP Fabien */
4548         sprintf(user_move, "o-o-o\n");
4549         break;
4550       case WhitePromotionQueen:
4551       case BlackPromotionQueen:
4552       case WhitePromotionRook:
4553       case BlackPromotionRook:
4554       case WhitePromotionBishop:
4555       case BlackPromotionBishop:
4556       case WhitePromotionKnight:
4557       case BlackPromotionKnight:
4558       case WhitePromotionKing:
4559       case BlackPromotionKing:
4560       case WhitePromotionChancellor:
4561       case BlackPromotionChancellor:
4562       case WhitePromotionArchbishop:
4563       case BlackPromotionArchbishop:
4564         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4565             sprintf(user_move, "%c%c%c%c=%c\n",
4566                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4567                 PieceToChar(WhiteFerz));
4568         else if(gameInfo.variant == VariantGreat)
4569             sprintf(user_move, "%c%c%c%c=%c\n",
4570                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4571                 PieceToChar(WhiteMan));
4572         else
4573             sprintf(user_move, "%c%c%c%c=%c\n",
4574                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4575                 PieceToChar(PromoPiece(moveType)));
4576         break;
4577       case WhiteDrop:
4578       case BlackDrop:
4579         sprintf(user_move, "%c@%c%c\n",
4580                 ToUpper(PieceToChar((ChessSquare) fromX)),
4581                 AAA + toX, ONE + toY);
4582         break;
4583       case NormalMove:
4584       case WhiteCapturesEnPassant:
4585       case BlackCapturesEnPassant:
4586       case IllegalMove:  /* could be a variant we don't quite understand */
4587         sprintf(user_move, "%c%c%c%c\n",
4588                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4589         break;
4590     }
4591     SendToICS(user_move);
4592     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4593         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4594 }
4595
4596 void
4597 UploadGameEvent()
4598 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4599     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4600     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4601     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4602         DisplayError("You cannot do this while you are playing or observing", 0);
4603         return;
4604     }
4605     if(gameMode != IcsExamining) { // is this ever not the case?
4606         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4607
4608         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4609             sprintf(command, "match %s", ics_handle);
4610         } else { // on FICS we must first go to general examine mode
4611             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4612         }
4613         if(gameInfo.variant != VariantNormal) {
4614             // try figure out wild number, as xboard names are not always valid on ICS
4615             for(i=1; i<=36; i++) {
4616                 sprintf(buf, "wild/%d", i);
4617                 if(StringToVariant(buf) == gameInfo.variant) break;
4618             }
4619             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4620             else if(i == 22) sprintf(buf, "%s fr\n", command);
4621             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4622         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4623         SendToICS(ics_prefix);
4624         SendToICS(buf);
4625         if(startedFromSetupPosition || backwardMostMove != 0) {
4626           fen = PositionToFEN(backwardMostMove, NULL);
4627           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4628             sprintf(buf, "loadfen %s\n", fen);
4629             SendToICS(buf);
4630           } else { // FICS: everything has to set by separate bsetup commands
4631             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4632             sprintf(buf, "bsetup fen %s\n", fen);
4633             SendToICS(buf);
4634             if(!WhiteOnMove(backwardMostMove)) {
4635                 SendToICS("bsetup tomove black\n");
4636             }
4637             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4638             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4639             SendToICS(buf);
4640             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4641             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4642             SendToICS(buf);
4643             i = boards[backwardMostMove][EP_STATUS];
4644             if(i >= 0) { // set e.p.
4645                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4646                 SendToICS(buf);
4647             }
4648             bsetup++;
4649           }
4650         }
4651       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4652             SendToICS("bsetup done\n"); // switch to normal examining.
4653     }
4654     for(i = backwardMostMove; i<last; i++) {
4655         char buf[20];
4656         sprintf(buf, "%s\n", parseList[i]);
4657         SendToICS(buf);
4658     }
4659     SendToICS(ics_prefix);
4660     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4661 }
4662
4663 void
4664 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4665      int rf, ff, rt, ft;
4666      char promoChar;
4667      char move[7];
4668 {
4669     if (rf == DROP_RANK) {
4670         sprintf(move, "%c@%c%c\n",
4671                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4672     } else {
4673         if (promoChar == 'x' || promoChar == NULLCHAR) {
4674             sprintf(move, "%c%c%c%c\n",
4675                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4676         } else {
4677             sprintf(move, "%c%c%c%c%c\n",
4678                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4679         }
4680     }
4681 }
4682
4683 void
4684 ProcessICSInitScript(f)
4685      FILE *f;
4686 {
4687     char buf[MSG_SIZ];
4688
4689     while (fgets(buf, MSG_SIZ, f)) {
4690         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4691     }
4692
4693     fclose(f);
4694 }
4695
4696
4697 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4698 void
4699 AlphaRank(char *move, int n)
4700 {
4701 //    char *p = move, c; int x, y;
4702
4703     if (appData.debugMode) {
4704         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4705     }
4706
4707     if(move[1]=='*' && 
4708        move[2]>='0' && move[2]<='9' &&
4709        move[3]>='a' && move[3]<='x'    ) {
4710         move[1] = '@';
4711         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4712         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4713     } else
4714     if(move[0]>='0' && move[0]<='9' &&
4715        move[1]>='a' && move[1]<='x' &&
4716        move[2]>='0' && move[2]<='9' &&
4717        move[3]>='a' && move[3]<='x'    ) {
4718         /* input move, Shogi -> normal */
4719         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4720         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4721         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4722         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4723     } else
4724     if(move[1]=='@' &&
4725        move[3]>='0' && move[3]<='9' &&
4726        move[2]>='a' && move[2]<='x'    ) {
4727         move[1] = '*';
4728         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4729         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4730     } else
4731     if(
4732        move[0]>='a' && move[0]<='x' &&
4733        move[3]>='0' && move[3]<='9' &&
4734        move[2]>='a' && move[2]<='x'    ) {
4735          /* output move, normal -> Shogi */
4736         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4737         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4738         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4739         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4740         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4741     }
4742     if (appData.debugMode) {
4743         fprintf(debugFP, "   out = '%s'\n", move);
4744     }
4745 }
4746
4747 char yy_textstr[8000];
4748
4749 /* Parser for moves from gnuchess, ICS, or user typein box */
4750 Boolean
4751 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4752      char *move;
4753      int moveNum;
4754      ChessMove *moveType;
4755      int *fromX, *fromY, *toX, *toY;
4756      char *promoChar;
4757 {       
4758     if (appData.debugMode) {
4759         fprintf(debugFP, "move to parse: %s\n", move);
4760     }
4761     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4762
4763     switch (*moveType) {
4764       case WhitePromotionChancellor:
4765       case BlackPromotionChancellor:
4766       case WhitePromotionArchbishop:
4767       case BlackPromotionArchbishop:
4768       case WhitePromotionQueen:
4769       case BlackPromotionQueen:
4770       case WhitePromotionRook:
4771       case BlackPromotionRook:
4772       case WhitePromotionBishop:
4773       case BlackPromotionBishop:
4774       case WhitePromotionKnight:
4775       case BlackPromotionKnight:
4776       case WhitePromotionKing:
4777       case BlackPromotionKing:
4778       case NormalMove:
4779       case WhiteCapturesEnPassant:
4780       case BlackCapturesEnPassant:
4781       case WhiteKingSideCastle:
4782       case WhiteQueenSideCastle:
4783       case BlackKingSideCastle:
4784       case BlackQueenSideCastle:
4785       case WhiteKingSideCastleWild:
4786       case WhiteQueenSideCastleWild:
4787       case BlackKingSideCastleWild:
4788       case BlackQueenSideCastleWild:
4789       /* Code added by Tord: */
4790       case WhiteHSideCastleFR:
4791       case WhiteASideCastleFR:
4792       case BlackHSideCastleFR:
4793       case BlackASideCastleFR:
4794       /* End of code added by Tord */
4795       case IllegalMove:         /* bug or odd chess variant */
4796         *fromX = currentMoveString[0] - AAA;
4797         *fromY = currentMoveString[1] - ONE;
4798         *toX = currentMoveString[2] - AAA;
4799         *toY = currentMoveString[3] - ONE;
4800         *promoChar = currentMoveString[4];
4801         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4802             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4803     if (appData.debugMode) {
4804         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4805     }
4806             *fromX = *fromY = *toX = *toY = 0;
4807             return FALSE;
4808         }
4809         if (appData.testLegality) {
4810           return (*moveType != IllegalMove);
4811         } else {
4812           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4813                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4814         }
4815
4816       case WhiteDrop:
4817       case BlackDrop:
4818         *fromX = *moveType == WhiteDrop ?
4819           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4820           (int) CharToPiece(ToLower(currentMoveString[0]));
4821         *fromY = DROP_RANK;
4822         *toX = currentMoveString[2] - AAA;
4823         *toY = currentMoveString[3] - ONE;
4824         *promoChar = NULLCHAR;
4825         return TRUE;
4826
4827       case AmbiguousMove:
4828       case ImpossibleMove:
4829       case (ChessMove) 0:       /* end of file */
4830       case ElapsedTime:
4831       case Comment:
4832       case PGNTag:
4833       case NAG:
4834       case WhiteWins:
4835       case BlackWins:
4836       case GameIsDrawn:
4837       default:
4838     if (appData.debugMode) {
4839         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4840     }
4841         /* bug? */
4842         *fromX = *fromY = *toX = *toY = 0;
4843         *promoChar = NULLCHAR;
4844         return FALSE;
4845     }
4846 }
4847
4848
4849 void
4850 ParsePV(char *pv, Boolean storeComments)
4851 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4852   int fromX, fromY, toX, toY; char promoChar;
4853   ChessMove moveType;
4854   Boolean valid;
4855   int nr = 0;
4856
4857   endPV = forwardMostMove;
4858   do {
4859     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4860     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4861     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4862 if(appData.debugMode){
4863 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);
4864 }
4865     if(!valid && nr == 0 &&
4866        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4867         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4868         // Hande case where played move is different from leading PV move
4869         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4870         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4871         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4872         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4873           endPV += 2; // if position different, keep this
4874           moveList[endPV-1][0] = fromX + AAA;
4875           moveList[endPV-1][1] = fromY + ONE;
4876           moveList[endPV-1][2] = toX + AAA;
4877           moveList[endPV-1][3] = toY + ONE;
4878           parseList[endPV-1][0] = NULLCHAR;
4879           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4880         }
4881       }
4882     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4883     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4884     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4885     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4886         valid++; // allow comments in PV
4887         continue;
4888     }
4889     nr++;
4890     if(endPV+1 > framePtr) break; // no space, truncate
4891     if(!valid) break;
4892     endPV++;
4893     CopyBoard(boards[endPV], boards[endPV-1]);
4894     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4895     moveList[endPV-1][0] = fromX + AAA;
4896     moveList[endPV-1][1] = fromY + ONE;
4897     moveList[endPV-1][2] = toX + AAA;
4898     moveList[endPV-1][3] = toY + ONE;
4899     if(storeComments)
4900         CoordsToAlgebraic(boards[endPV - 1],
4901                              PosFlags(endPV - 1),
4902                              fromY, fromX, toY, toX, promoChar,
4903                              parseList[endPV - 1]);
4904     else
4905         parseList[endPV-1][0] = NULLCHAR;
4906   } while(valid);
4907   currentMove = endPV;
4908   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4909   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4910                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4911   DrawPosition(TRUE, boards[currentMove]);
4912 }
4913
4914 static int lastX, lastY;
4915
4916 Boolean
4917 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4918 {
4919         int startPV;
4920         char *p;
4921
4922         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4923         lastX = x; lastY = y;
4924         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4925         startPV = index;
4926         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4927         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4928         index = startPV;
4929         do{ while(buf[index] && buf[index] != '\n') index++;
4930         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4931         buf[index] = 0;
4932         ParsePV(buf+startPV, FALSE);
4933         *start = startPV; *end = index-1;
4934         return TRUE;
4935 }
4936
4937 Boolean
4938 LoadPV(int x, int y)
4939 { // called on right mouse click to load PV
4940   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4941   lastX = x; lastY = y;
4942   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4943   return TRUE;
4944 }
4945
4946 void
4947 UnLoadPV()
4948 {
4949   if(endPV < 0) return;
4950   endPV = -1;
4951   currentMove = forwardMostMove;
4952   ClearPremoveHighlights();
4953   DrawPosition(TRUE, boards[currentMove]);
4954 }
4955
4956 void
4957 MovePV(int x, int y, int h)
4958 { // step through PV based on mouse coordinates (called on mouse move)
4959   int margin = h>>3, step = 0;
4960
4961   if(endPV < 0) return;
4962   // we must somehow check if right button is still down (might be released off board!)
4963   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4964   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4965   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4966   if(!step) return;
4967   lastX = x; lastY = y;
4968   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4969   currentMove += step;
4970   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4971   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4972                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4973   DrawPosition(FALSE, boards[currentMove]);
4974 }
4975
4976
4977 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4978 // All positions will have equal probability, but the current method will not provide a unique
4979 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4980 #define DARK 1
4981 #define LITE 2
4982 #define ANY 3
4983
4984 int squaresLeft[4];
4985 int piecesLeft[(int)BlackPawn];
4986 int seed, nrOfShuffles;
4987
4988 void GetPositionNumber()
4989 {       // sets global variable seed
4990         int i;
4991
4992         seed = appData.defaultFrcPosition;
4993         if(seed < 0) { // randomize based on time for negative FRC position numbers
4994                 for(i=0; i<50; i++) seed += random();
4995                 seed = random() ^ random() >> 8 ^ random() << 8;
4996                 if(seed<0) seed = -seed;
4997         }
4998 }
4999
5000 int put(Board board, int pieceType, int rank, int n, int shade)
5001 // put the piece on the (n-1)-th empty squares of the given shade
5002 {
5003         int i;
5004
5005         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5006                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5007                         board[rank][i] = (ChessSquare) pieceType;
5008                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5009                         squaresLeft[ANY]--;
5010                         piecesLeft[pieceType]--; 
5011                         return i;
5012                 }
5013         }
5014         return -1;
5015 }
5016
5017
5018 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5019 // calculate where the next piece goes, (any empty square), and put it there
5020 {
5021         int i;
5022
5023         i = seed % squaresLeft[shade];
5024         nrOfShuffles *= squaresLeft[shade];
5025         seed /= squaresLeft[shade];
5026         put(board, pieceType, rank, i, shade);
5027 }
5028
5029 void AddTwoPieces(Board board, int pieceType, int rank)
5030 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5031 {
5032         int i, n=squaresLeft[ANY], j=n-1, k;
5033
5034         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5035         i = seed % k;  // pick one
5036         nrOfShuffles *= k;
5037         seed /= k;
5038         while(i >= j) i -= j--;
5039         j = n - 1 - j; i += j;
5040         put(board, pieceType, rank, j, ANY);
5041         put(board, pieceType, rank, i, ANY);
5042 }
5043
5044 void SetUpShuffle(Board board, int number)
5045 {
5046         int i, p, first=1;
5047
5048         GetPositionNumber(); nrOfShuffles = 1;
5049
5050         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5051         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5052         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5053
5054         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5055
5056         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5057             p = (int) board[0][i];
5058             if(p < (int) BlackPawn) piecesLeft[p] ++;
5059             board[0][i] = EmptySquare;
5060         }
5061
5062         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5063             // shuffles restricted to allow normal castling put KRR first
5064             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5065                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5066             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5067                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5068             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5069                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5070             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5071                 put(board, WhiteRook, 0, 0, ANY);
5072             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5073         }
5074
5075         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5076             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5077             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5078                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5079                 while(piecesLeft[p] >= 2) {
5080                     AddOnePiece(board, p, 0, LITE);
5081                     AddOnePiece(board, p, 0, DARK);
5082                 }
5083                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5084             }
5085
5086         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5087             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5088             // but we leave King and Rooks for last, to possibly obey FRC restriction
5089             if(p == (int)WhiteRook) continue;
5090             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5091             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5092         }
5093
5094         // now everything is placed, except perhaps King (Unicorn) and Rooks
5095
5096         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5097             // Last King gets castling rights
5098             while(piecesLeft[(int)WhiteUnicorn]) {
5099                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5100                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5101             }
5102
5103             while(piecesLeft[(int)WhiteKing]) {
5104                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5105                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5106             }
5107
5108
5109         } else {
5110             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5111             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5112         }
5113
5114         // Only Rooks can be left; simply place them all
5115         while(piecesLeft[(int)WhiteRook]) {
5116                 i = put(board, WhiteRook, 0, 0, ANY);
5117                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5118                         if(first) {
5119                                 first=0;
5120                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5121                         }
5122                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5123                 }
5124         }
5125         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5126             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5127         }
5128
5129         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5130 }
5131
5132 int SetCharTable( char *table, const char * map )
5133 /* [HGM] moved here from winboard.c because of its general usefulness */
5134 /*       Basically a safe strcpy that uses the last character as King */
5135 {
5136     int result = FALSE; int NrPieces;
5137
5138     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5139                     && NrPieces >= 12 && !(NrPieces&1)) {
5140         int i; /* [HGM] Accept even length from 12 to 34 */
5141
5142         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5143         for( i=0; i<NrPieces/2-1; i++ ) {
5144             table[i] = map[i];
5145             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5146         }
5147         table[(int) WhiteKing]  = map[NrPieces/2-1];
5148         table[(int) BlackKing]  = map[NrPieces-1];
5149
5150         result = TRUE;
5151     }
5152
5153     return result;
5154 }
5155
5156 void Prelude(Board board)
5157 {       // [HGM] superchess: random selection of exo-pieces
5158         int i, j, k; ChessSquare p; 
5159         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5160
5161         GetPositionNumber(); // use FRC position number
5162
5163         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5164             SetCharTable(pieceToChar, appData.pieceToCharTable);
5165             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5166                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5167         }
5168
5169         j = seed%4;                 seed /= 4; 
5170         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5171         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5172         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5173         j = seed%3 + (seed%3 >= j); seed /= 3; 
5174         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5175         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5176         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5177         j = seed%3;                 seed /= 3; 
5178         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5179         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5180         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5181         j = seed%2 + (seed%2 >= j); seed /= 2; 
5182         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5183         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5184         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5185         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5186         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5187         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5188         put(board, exoPieces[0],    0, 0, ANY);
5189         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5190 }
5191
5192 void
5193 InitPosition(redraw)
5194      int redraw;
5195 {
5196     ChessSquare (* pieces)[BOARD_FILES];
5197     int i, j, pawnRow, overrule,
5198     oldx = gameInfo.boardWidth,
5199     oldy = gameInfo.boardHeight,
5200     oldh = gameInfo.holdingsWidth,
5201     oldv = gameInfo.variant;
5202
5203     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5204
5205     /* [AS] Initialize pv info list [HGM] and game status */
5206     {
5207         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5208             pvInfoList[i].depth = 0;
5209             boards[i][EP_STATUS] = EP_NONE;
5210             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5211         }
5212
5213         initialRulePlies = 0; /* 50-move counter start */
5214
5215         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5216         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5217     }
5218
5219     
5220     /* [HGM] logic here is completely changed. In stead of full positions */
5221     /* the initialized data only consist of the two backranks. The switch */
5222     /* selects which one we will use, which is than copied to the Board   */
5223     /* initialPosition, which for the rest is initialized by Pawns and    */
5224     /* empty squares. This initial position is then copied to boards[0],  */
5225     /* possibly after shuffling, so that it remains available.            */
5226
5227     gameInfo.holdingsWidth = 0; /* default board sizes */
5228     gameInfo.boardWidth    = 8;
5229     gameInfo.boardHeight   = 8;
5230     gameInfo.holdingsSize  = 0;
5231     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5232     for(i=0; i<BOARD_FILES-2; i++)
5233       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5234     initialPosition[EP_STATUS] = EP_NONE;
5235     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5236
5237     switch (gameInfo.variant) {
5238     case VariantFischeRandom:
5239       shuffleOpenings = TRUE;
5240     default:
5241       pieces = FIDEArray;
5242       break;
5243     case VariantShatranj:
5244       pieces = ShatranjArray;
5245       nrCastlingRights = 0;
5246       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5247       break;
5248     case VariantMakruk:
5249       pieces = makrukArray;
5250       nrCastlingRights = 0;
5251       startedFromSetupPosition = TRUE;
5252       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5253       break;
5254     case VariantTwoKings:
5255       pieces = twoKingsArray;
5256       break;
5257     case VariantCapaRandom:
5258       shuffleOpenings = TRUE;
5259     case VariantCapablanca:
5260       pieces = CapablancaArray;
5261       gameInfo.boardWidth = 10;
5262       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5263       break;
5264     case VariantGothic:
5265       pieces = GothicArray;
5266       gameInfo.boardWidth = 10;
5267       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5268       break;
5269     case VariantJanus:
5270       pieces = JanusArray;
5271       gameInfo.boardWidth = 10;
5272       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5273       nrCastlingRights = 6;
5274         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5275         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5276         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5277         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5278         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5279         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5280       break;
5281     case VariantFalcon:
5282       pieces = FalconArray;
5283       gameInfo.boardWidth = 10;
5284       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5285       break;
5286     case VariantXiangqi:
5287       pieces = XiangqiArray;
5288       gameInfo.boardWidth  = 9;
5289       gameInfo.boardHeight = 10;
5290       nrCastlingRights = 0;
5291       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5292       break;
5293     case VariantShogi:
5294       pieces = ShogiArray;
5295       gameInfo.boardWidth  = 9;
5296       gameInfo.boardHeight = 9;
5297       gameInfo.holdingsSize = 7;
5298       nrCastlingRights = 0;
5299       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5300       break;
5301     case VariantCourier:
5302       pieces = CourierArray;
5303       gameInfo.boardWidth  = 12;
5304       nrCastlingRights = 0;
5305       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5306       break;
5307     case VariantKnightmate:
5308       pieces = KnightmateArray;
5309       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5310       break;
5311     case VariantFairy:
5312       pieces = fairyArray;
5313       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5314       break;
5315     case VariantGreat:
5316       pieces = GreatArray;
5317       gameInfo.boardWidth = 10;
5318       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5319       gameInfo.holdingsSize = 8;
5320       break;
5321     case VariantSuper:
5322       pieces = FIDEArray;
5323       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5324       gameInfo.holdingsSize = 8;
5325       startedFromSetupPosition = TRUE;
5326       break;
5327     case VariantCrazyhouse:
5328     case VariantBughouse:
5329       pieces = FIDEArray;
5330       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5331       gameInfo.holdingsSize = 5;
5332       break;
5333     case VariantWildCastle:
5334       pieces = FIDEArray;
5335       /* !!?shuffle with kings guaranteed to be on d or e file */
5336       shuffleOpenings = 1;
5337       break;
5338     case VariantNoCastle:
5339       pieces = FIDEArray;
5340       nrCastlingRights = 0;
5341       /* !!?unconstrained back-rank shuffle */
5342       shuffleOpenings = 1;
5343       break;
5344     }
5345
5346     overrule = 0;
5347     if(appData.NrFiles >= 0) {
5348         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5349         gameInfo.boardWidth = appData.NrFiles;
5350     }
5351     if(appData.NrRanks >= 0) {
5352         gameInfo.boardHeight = appData.NrRanks;
5353     }
5354     if(appData.holdingsSize >= 0) {
5355         i = appData.holdingsSize;
5356         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5357         gameInfo.holdingsSize = i;
5358     }
5359     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5360     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5361         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5362
5363     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5364     if(pawnRow < 1) pawnRow = 1;
5365     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5366
5367     /* User pieceToChar list overrules defaults */
5368     if(appData.pieceToCharTable != NULL)
5369         SetCharTable(pieceToChar, appData.pieceToCharTable);
5370
5371     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5372
5373         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5374             s = (ChessSquare) 0; /* account holding counts in guard band */
5375         for( i=0; i<BOARD_HEIGHT; i++ )
5376             initialPosition[i][j] = s;
5377
5378         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5379         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5380         initialPosition[pawnRow][j] = WhitePawn;
5381         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5382         if(gameInfo.variant == VariantXiangqi) {
5383             if(j&1) {
5384                 initialPosition[pawnRow][j] = 
5385                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5386                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5387                    initialPosition[2][j] = WhiteCannon;
5388                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5389                 }
5390             }
5391         }
5392         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5393     }
5394     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5395
5396             j=BOARD_LEFT+1;
5397             initialPosition[1][j] = WhiteBishop;
5398             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5399             j=BOARD_RGHT-2;
5400             initialPosition[1][j] = WhiteRook;
5401             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5402     }
5403
5404     if( nrCastlingRights == -1) {
5405         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5406         /*       This sets default castling rights from none to normal corners   */
5407         /* Variants with other castling rights must set them themselves above    */
5408         nrCastlingRights = 6;
5409        
5410         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5411         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5412         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5413         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5414         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5415         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5416      }
5417
5418      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5419      if(gameInfo.variant == VariantGreat) { // promotion commoners
5420         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5421         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5422         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5423         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5424      }
5425   if (appData.debugMode) {
5426     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5427   }
5428     if(shuffleOpenings) {
5429         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5430         startedFromSetupPosition = TRUE;
5431     }
5432     if(startedFromPositionFile) {
5433       /* [HGM] loadPos: use PositionFile for every new game */
5434       CopyBoard(initialPosition, filePosition);
5435       for(i=0; i<nrCastlingRights; i++)
5436           initialRights[i] = filePosition[CASTLING][i];
5437       startedFromSetupPosition = TRUE;
5438     }
5439
5440     CopyBoard(boards[0], initialPosition);
5441
5442     if(oldx != gameInfo.boardWidth ||
5443        oldy != gameInfo.boardHeight ||
5444        oldh != gameInfo.holdingsWidth
5445 #ifdef GOTHIC
5446        || oldv == VariantGothic ||        // For licensing popups
5447        gameInfo.variant == VariantGothic
5448 #endif
5449 #ifdef FALCON
5450        || oldv == VariantFalcon ||
5451        gameInfo.variant == VariantFalcon
5452 #endif
5453                                          )
5454             InitDrawingSizes(-2 ,0);
5455
5456     if (redraw)
5457       DrawPosition(TRUE, boards[currentMove]);
5458 }
5459
5460 void
5461 SendBoard(cps, moveNum)
5462      ChessProgramState *cps;
5463      int moveNum;
5464 {
5465     char message[MSG_SIZ];
5466     
5467     if (cps->useSetboard) {
5468       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5469       sprintf(message, "setboard %s\n", fen);
5470       SendToProgram(message, cps);
5471       free(fen);
5472
5473     } else {
5474       ChessSquare *bp;
5475       int i, j;
5476       /* Kludge to set black to move, avoiding the troublesome and now
5477        * deprecated "black" command.
5478        */
5479       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5480
5481       SendToProgram("edit\n", cps);
5482       SendToProgram("#\n", cps);
5483       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5484         bp = &boards[moveNum][i][BOARD_LEFT];
5485         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5486           if ((int) *bp < (int) BlackPawn) {
5487             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5488                     AAA + j, ONE + i);
5489             if(message[0] == '+' || message[0] == '~') {
5490                 sprintf(message, "%c%c%c+\n",
5491                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5492                         AAA + j, ONE + i);
5493             }
5494             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5495                 message[1] = BOARD_RGHT   - 1 - j + '1';
5496                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5497             }
5498             SendToProgram(message, cps);
5499           }
5500         }
5501       }
5502     
5503       SendToProgram("c\n", cps);
5504       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5505         bp = &boards[moveNum][i][BOARD_LEFT];
5506         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5507           if (((int) *bp != (int) EmptySquare)
5508               && ((int) *bp >= (int) BlackPawn)) {
5509             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5510                     AAA + j, ONE + i);
5511             if(message[0] == '+' || message[0] == '~') {
5512                 sprintf(message, "%c%c%c+\n",
5513                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5514                         AAA + j, ONE + i);
5515             }
5516             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5517                 message[1] = BOARD_RGHT   - 1 - j + '1';
5518                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5519             }
5520             SendToProgram(message, cps);
5521           }
5522         }
5523       }
5524     
5525       SendToProgram(".\n", cps);
5526     }
5527     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5528 }
5529
5530 static int autoQueen; // [HGM] oneclick
5531
5532 int
5533 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5534 {
5535     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5536     /* [HGM] add Shogi promotions */
5537     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5538     ChessSquare piece;
5539     ChessMove moveType;
5540     Boolean premove;
5541
5542     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5543     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5544
5545     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5546       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5547         return FALSE;
5548
5549     piece = boards[currentMove][fromY][fromX];
5550     if(gameInfo.variant == VariantShogi) {
5551         promotionZoneSize = 3;
5552         highestPromotingPiece = (int)WhiteFerz;
5553     } else if(gameInfo.variant == VariantMakruk) {
5554         promotionZoneSize = 3;
5555     }
5556
5557     // next weed out all moves that do not touch the promotion zone at all
5558     if((int)piece >= BlackPawn) {
5559         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5560              return FALSE;
5561         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5562     } else {
5563         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5564            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5565     }
5566
5567     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5568
5569     // weed out mandatory Shogi promotions
5570     if(gameInfo.variant == VariantShogi) {
5571         if(piece >= BlackPawn) {
5572             if(toY == 0 && piece == BlackPawn ||
5573                toY == 0 && piece == BlackQueen ||
5574                toY <= 1 && piece == BlackKnight) {
5575                 *promoChoice = '+';
5576                 return FALSE;
5577             }
5578         } else {
5579             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5580                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5581                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5582                 *promoChoice = '+';
5583                 return FALSE;
5584             }
5585         }
5586     }
5587
5588     // weed out obviously illegal Pawn moves
5589     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5590         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5591         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5592         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5593         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5594         // note we are not allowed to test for valid (non-)capture, due to premove
5595     }
5596
5597     // we either have a choice what to promote to, or (in Shogi) whether to promote
5598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5599         *promoChoice = PieceToChar(BlackFerz);  // no choice
5600         return FALSE;
5601     }
5602     if(autoQueen) { // predetermined
5603         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5604              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5605         else *promoChoice = PieceToChar(BlackQueen);
5606         return FALSE;
5607     }
5608
5609     // suppress promotion popup on illegal moves that are not premoves
5610     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5611               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5612     if(appData.testLegality && !premove) {
5613         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5614                         fromY, fromX, toY, toX, NULLCHAR);
5615         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5616            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5617             return FALSE;
5618     }
5619
5620     return TRUE;
5621 }
5622
5623 int
5624 InPalace(row, column)
5625      int row, column;
5626 {   /* [HGM] for Xiangqi */
5627     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5628          column < (BOARD_WIDTH + 4)/2 &&
5629          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5630     return FALSE;
5631 }
5632
5633 int
5634 PieceForSquare (x, y)
5635      int x;
5636      int y;
5637 {
5638   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5639      return -1;
5640   else
5641      return boards[currentMove][y][x];
5642 }
5643
5644 int
5645 OKToStartUserMove(x, y)
5646      int x, y;
5647 {
5648     ChessSquare from_piece;
5649     int white_piece;
5650
5651     if (matchMode) return FALSE;
5652     if (gameMode == EditPosition) return TRUE;
5653
5654     if (x >= 0 && y >= 0)
5655       from_piece = boards[currentMove][y][x];
5656     else
5657       from_piece = EmptySquare;
5658
5659     if (from_piece == EmptySquare) return FALSE;
5660
5661     white_piece = (int)from_piece >= (int)WhitePawn &&
5662       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5663
5664     switch (gameMode) {
5665       case PlayFromGameFile:
5666       case AnalyzeFile:
5667       case TwoMachinesPlay:
5668       case EndOfGame:
5669         return FALSE;
5670
5671       case IcsObserving:
5672       case IcsIdle:
5673         return FALSE;
5674
5675       case MachinePlaysWhite:
5676       case IcsPlayingBlack:
5677         if (appData.zippyPlay) return FALSE;
5678         if (white_piece) {
5679             DisplayMoveError(_("You are playing Black"));
5680             return FALSE;
5681         }
5682         break;
5683
5684       case MachinePlaysBlack:
5685       case IcsPlayingWhite:
5686         if (appData.zippyPlay) return FALSE;
5687         if (!white_piece) {
5688             DisplayMoveError(_("You are playing White"));
5689             return FALSE;
5690         }
5691         break;
5692
5693       case EditGame:
5694         if (!white_piece && WhiteOnMove(currentMove)) {
5695             DisplayMoveError(_("It is White's turn"));
5696             return FALSE;
5697         }           
5698         if (white_piece && !WhiteOnMove(currentMove)) {
5699             DisplayMoveError(_("It is Black's turn"));
5700             return FALSE;
5701         }           
5702         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5703             /* Editing correspondence game history */
5704             /* Could disallow this or prompt for confirmation */
5705             cmailOldMove = -1;
5706         }
5707         break;
5708
5709       case BeginningOfGame:
5710         if (appData.icsActive) return FALSE;
5711         if (!appData.noChessProgram) {
5712             if (!white_piece) {
5713                 DisplayMoveError(_("You are playing White"));
5714                 return FALSE;
5715             }
5716         }
5717         break;
5718         
5719       case Training:
5720         if (!white_piece && WhiteOnMove(currentMove)) {
5721             DisplayMoveError(_("It is White's turn"));
5722             return FALSE;
5723         }           
5724         if (white_piece && !WhiteOnMove(currentMove)) {
5725             DisplayMoveError(_("It is Black's turn"));
5726             return FALSE;
5727         }           
5728         break;
5729
5730       default:
5731       case IcsExamining:
5732         break;
5733     }
5734     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5735         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5736         && gameMode != AnalyzeFile && gameMode != Training) {
5737         DisplayMoveError(_("Displayed position is not current"));
5738         return FALSE;
5739     }
5740     return TRUE;
5741 }
5742
5743 Boolean
5744 OnlyMove(int *x, int *y, Boolean captures) {
5745     DisambiguateClosure cl;
5746     if (appData.zippyPlay) return FALSE;
5747     switch(gameMode) {
5748       case MachinePlaysBlack:
5749       case IcsPlayingWhite:
5750       case BeginningOfGame:
5751         if(!WhiteOnMove(currentMove)) return FALSE;
5752         break;
5753       case MachinePlaysWhite:
5754       case IcsPlayingBlack:
5755         if(WhiteOnMove(currentMove)) return FALSE;
5756         break;
5757       default:
5758         return FALSE;
5759     }
5760     cl.pieceIn = EmptySquare; 
5761     cl.rfIn = *y;
5762     cl.ffIn = *x;
5763     cl.rtIn = -1;
5764     cl.ftIn = -1;
5765     cl.promoCharIn = NULLCHAR;
5766     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5767     if( cl.kind == NormalMove ||
5768         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5769         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5770         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5771         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5772       fromX = cl.ff;
5773       fromY = cl.rf;
5774       *x = cl.ft;
5775       *y = cl.rt;
5776       return TRUE;
5777     }
5778     if(cl.kind != ImpossibleMove) return FALSE;
5779     cl.pieceIn = EmptySquare;
5780     cl.rfIn = -1;
5781     cl.ffIn = -1;
5782     cl.rtIn = *y;
5783     cl.ftIn = *x;
5784     cl.promoCharIn = NULLCHAR;
5785     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5786     if( cl.kind == NormalMove ||
5787         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5788         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5789         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5790         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5791       fromX = cl.ff;
5792       fromY = cl.rf;
5793       *x = cl.ft;
5794       *y = cl.rt;
5795       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5796       return TRUE;
5797     }
5798     return FALSE;
5799 }
5800
5801 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5802 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5803 int lastLoadGameUseList = FALSE;
5804 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5805 ChessMove lastLoadGameStart = (ChessMove) 0;
5806
5807 ChessMove
5808 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5809      int fromX, fromY, toX, toY;
5810      int promoChar;
5811      Boolean captureOwn;
5812 {
5813     ChessMove moveType;
5814     ChessSquare pdown, pup;
5815
5816     /* Check if the user is playing in turn.  This is complicated because we
5817        let the user "pick up" a piece before it is his turn.  So the piece he
5818        tried to pick up may have been captured by the time he puts it down!
5819        Therefore we use the color the user is supposed to be playing in this
5820        test, not the color of the piece that is currently on the starting
5821        square---except in EditGame mode, where the user is playing both
5822        sides; fortunately there the capture race can't happen.  (It can
5823        now happen in IcsExamining mode, but that's just too bad.  The user
5824        will get a somewhat confusing message in that case.)
5825        */
5826
5827     switch (gameMode) {
5828       case PlayFromGameFile:
5829       case AnalyzeFile:
5830       case TwoMachinesPlay:
5831       case EndOfGame:
5832       case IcsObserving:
5833       case IcsIdle:
5834         /* We switched into a game mode where moves are not accepted,
5835            perhaps while the mouse button was down. */
5836         return ImpossibleMove;
5837
5838       case MachinePlaysWhite:
5839         /* User is moving for Black */
5840         if (WhiteOnMove(currentMove)) {
5841             DisplayMoveError(_("It is White's turn"));
5842             return ImpossibleMove;
5843         }
5844         break;
5845
5846       case MachinePlaysBlack:
5847         /* User is moving for White */
5848         if (!WhiteOnMove(currentMove)) {
5849             DisplayMoveError(_("It is Black's turn"));
5850             return ImpossibleMove;
5851         }
5852         break;
5853
5854       case EditGame:
5855       case IcsExamining:
5856       case BeginningOfGame:
5857       case AnalyzeMode:
5858       case Training:
5859         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5860             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5861             /* User is moving for Black */
5862             if (WhiteOnMove(currentMove)) {
5863                 DisplayMoveError(_("It is White's turn"));
5864                 return ImpossibleMove;
5865             }
5866         } else {
5867             /* User is moving for White */
5868             if (!WhiteOnMove(currentMove)) {
5869                 DisplayMoveError(_("It is Black's turn"));
5870                 return ImpossibleMove;
5871             }
5872         }
5873         break;
5874
5875       case IcsPlayingBlack:
5876         /* User is moving for Black */
5877         if (WhiteOnMove(currentMove)) {
5878             if (!appData.premove) {
5879                 DisplayMoveError(_("It is White's turn"));
5880             } else if (toX >= 0 && toY >= 0) {
5881                 premoveToX = toX;
5882                 premoveToY = toY;
5883                 premoveFromX = fromX;
5884                 premoveFromY = fromY;
5885                 premovePromoChar = promoChar;
5886                 gotPremove = 1;
5887                 if (appData.debugMode) 
5888                     fprintf(debugFP, "Got premove: fromX %d,"
5889                             "fromY %d, toX %d, toY %d\n",
5890                             fromX, fromY, toX, toY);
5891             }
5892             return ImpossibleMove;
5893         }
5894         break;
5895
5896       case IcsPlayingWhite:
5897         /* User is moving for White */
5898         if (!WhiteOnMove(currentMove)) {
5899             if (!appData.premove) {
5900                 DisplayMoveError(_("It is Black's turn"));
5901             } else if (toX >= 0 && toY >= 0) {
5902                 premoveToX = toX;
5903                 premoveToY = toY;
5904                 premoveFromX = fromX;
5905                 premoveFromY = fromY;
5906                 premovePromoChar = promoChar;
5907                 gotPremove = 1;
5908                 if (appData.debugMode) 
5909                     fprintf(debugFP, "Got premove: fromX %d,"
5910                             "fromY %d, toX %d, toY %d\n",
5911                             fromX, fromY, toX, toY);
5912             }
5913             return ImpossibleMove;
5914         }
5915         break;
5916
5917       default:
5918         break;
5919
5920       case EditPosition:
5921         /* EditPosition, empty square, or different color piece;
5922            click-click move is possible */
5923         if (toX == -2 || toY == -2) {
5924             boards[0][fromY][fromX] = EmptySquare;
5925             return AmbiguousMove;
5926         } else if (toX >= 0 && toY >= 0) {
5927             boards[0][toY][toX] = boards[0][fromY][fromX];
5928             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5929                 if(boards[0][fromY][0] != EmptySquare) {
5930                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5931                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5932                 }
5933             } else
5934             if(fromX == BOARD_RGHT+1) {
5935                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5936                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5937                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5938                 }
5939             } else
5940             boards[0][fromY][fromX] = EmptySquare;
5941             return AmbiguousMove;
5942         }
5943         return ImpossibleMove;
5944     }
5945
5946     if(toX < 0 || toY < 0) return ImpossibleMove;
5947     pdown = boards[currentMove][fromY][fromX];
5948     pup = boards[currentMove][toY][toX];
5949
5950     /* [HGM] If move started in holdings, it means a drop */
5951     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5952          if( pup != EmptySquare ) return ImpossibleMove;
5953          if(appData.testLegality) {
5954              /* it would be more logical if LegalityTest() also figured out
5955               * which drops are legal. For now we forbid pawns on back rank.
5956               * Shogi is on its own here...
5957               */
5958              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5959                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5960                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5961          }
5962          return WhiteDrop; /* Not needed to specify white or black yet */
5963     }
5964
5965     /* [HGM] always test for legality, to get promotion info */
5966     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5967                                          fromY, fromX, toY, toX, promoChar);
5968     /* [HGM] but possibly ignore an IllegalMove result */
5969     if (appData.testLegality) {
5970         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5971             DisplayMoveError(_("Illegal move"));
5972             return ImpossibleMove;
5973         }
5974     }
5975
5976     return moveType;
5977     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5978        function is made into one that returns an OK move type if FinishMove
5979        should be called. This to give the calling driver routine the
5980        opportunity to finish the userMove input with a promotion popup,
5981        without bothering the user with this for invalid or illegal moves */
5982
5983 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5984 }
5985
5986 /* Common tail of UserMoveEvent and DropMenuEvent */
5987 int
5988 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5989      ChessMove moveType;
5990      int fromX, fromY, toX, toY;
5991      /*char*/int promoChar;
5992 {
5993     char *bookHit = 0;
5994
5995     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5996         // [HGM] superchess: suppress promotions to non-available piece
5997         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5998         if(WhiteOnMove(currentMove)) {
5999             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6000         } else {
6001             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6002         }
6003     }
6004
6005     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6006        move type in caller when we know the move is a legal promotion */
6007     if(moveType == NormalMove && promoChar)
6008         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6009
6010     /* [HGM] convert drag-and-drop piece drops to standard form */
6011     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6012          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6013            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6014                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6015            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6016            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6017            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6018            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6019          fromY = DROP_RANK;
6020     }
6021
6022     /* [HGM] <popupFix> The following if has been moved here from
6023        UserMoveEvent(). Because it seemed to belong here (why not allow
6024        piece drops in training games?), and because it can only be
6025        performed after it is known to what we promote. */
6026     if (gameMode == Training) {
6027       /* compare the move played on the board to the next move in the
6028        * game. If they match, display the move and the opponent's response. 
6029        * If they don't match, display an error message.
6030        */
6031       int saveAnimate;
6032       Board testBoard;
6033       CopyBoard(testBoard, boards[currentMove]);
6034       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6035
6036       if (CompareBoards(testBoard, boards[currentMove+1])) {
6037         ForwardInner(currentMove+1);
6038
6039         /* Autoplay the opponent's response.
6040          * if appData.animate was TRUE when Training mode was entered,
6041          * the response will be animated.
6042          */
6043         saveAnimate = appData.animate;
6044         appData.animate = animateTraining;
6045         ForwardInner(currentMove+1);
6046         appData.animate = saveAnimate;
6047
6048         /* check for the end of the game */
6049         if (currentMove >= forwardMostMove) {
6050           gameMode = PlayFromGameFile;
6051           ModeHighlight();
6052           SetTrainingModeOff();
6053           DisplayInformation(_("End of game"));
6054         }
6055       } else {
6056         DisplayError(_("Incorrect move"), 0);
6057       }
6058       return 1;
6059     }
6060
6061   /* Ok, now we know that the move is good, so we can kill
6062      the previous line in Analysis Mode */
6063   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6064                                 && currentMove < forwardMostMove) {
6065     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6066   }
6067
6068   /* If we need the chess program but it's dead, restart it */
6069   ResurrectChessProgram();
6070
6071   /* A user move restarts a paused game*/
6072   if (pausing)
6073     PauseEvent();
6074
6075   thinkOutput[0] = NULLCHAR;
6076
6077   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6078
6079   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6080
6081   if (gameMode == BeginningOfGame) {
6082     if (appData.noChessProgram) {
6083       gameMode = EditGame;
6084       SetGameInfo();
6085     } else {
6086       char buf[MSG_SIZ];
6087       gameMode = MachinePlaysBlack;
6088       StartClocks();
6089       SetGameInfo();
6090       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6091       DisplayTitle(buf);
6092       if (first.sendName) {
6093         sprintf(buf, "name %s\n", gameInfo.white);
6094         SendToProgram(buf, &first);
6095       }
6096       StartClocks();
6097     }
6098     ModeHighlight();
6099   }
6100
6101   /* Relay move to ICS or chess engine */
6102   if (appData.icsActive) {
6103     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6104         gameMode == IcsExamining) {
6105       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6106         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6107         SendToICS("draw ");
6108         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6109       }
6110       // also send plain move, in case ICS does not understand atomic claims
6111       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6112       ics_user_moved = 1;
6113     }
6114   } else {
6115     if (first.sendTime && (gameMode == BeginningOfGame ||
6116                            gameMode == MachinePlaysWhite ||
6117                            gameMode == MachinePlaysBlack)) {
6118       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6119     }
6120     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6121          // [HGM] book: if program might be playing, let it use book
6122         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6123         first.maybeThinking = TRUE;
6124     } else SendMoveToProgram(forwardMostMove-1, &first);
6125     if (currentMove == cmailOldMove + 1) {
6126       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6127     }
6128   }
6129
6130   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131
6132   switch (gameMode) {
6133   case EditGame:
6134     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6135     case MT_NONE:
6136     case MT_CHECK:
6137       break;
6138     case MT_CHECKMATE:
6139     case MT_STAINMATE:
6140       if (WhiteOnMove(currentMove)) {
6141         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6142       } else {
6143         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6144       }
6145       break;
6146     case MT_STALEMATE:
6147       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6148       break;
6149     }
6150     break;
6151     
6152   case MachinePlaysBlack:
6153   case MachinePlaysWhite:
6154     /* disable certain menu options while machine is thinking */
6155     SetMachineThinkingEnables();
6156     break;
6157
6158   default:
6159     break;
6160   }
6161
6162   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6163         
6164   if(bookHit) { // [HGM] book: simulate book reply
6165         static char bookMove[MSG_SIZ]; // a bit generous?
6166
6167         programStats.nodes = programStats.depth = programStats.time = 
6168         programStats.score = programStats.got_only_move = 0;
6169         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6170
6171         strcpy(bookMove, "move ");
6172         strcat(bookMove, bookHit);
6173         HandleMachineMove(bookMove, &first);
6174   }
6175   return 1;
6176 }
6177
6178 void
6179 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6180      int fromX, fromY, toX, toY;
6181      int promoChar;
6182 {
6183     /* [HGM] This routine was added to allow calling of its two logical
6184        parts from other modules in the old way. Before, UserMoveEvent()
6185        automatically called FinishMove() if the move was OK, and returned
6186        otherwise. I separated the two, in order to make it possible to
6187        slip a promotion popup in between. But that it always needs two
6188        calls, to the first part, (now called UserMoveTest() ), and to
6189        FinishMove if the first part succeeded. Calls that do not need
6190        to do anything in between, can call this routine the old way. 
6191     */
6192     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6193 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6194     if(moveType == AmbiguousMove)
6195         DrawPosition(FALSE, boards[currentMove]);
6196     else if(moveType != ImpossibleMove && moveType != Comment)
6197         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6198 }
6199
6200 void
6201 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6202      Board board;
6203      int flags;
6204      ChessMove kind;
6205      int rf, ff, rt, ft;
6206      VOIDSTAR closure;
6207 {
6208     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6209     Markers *m = (Markers *) closure;
6210     if(rf == fromY && ff == fromX)
6211         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6212                          || kind == WhiteCapturesEnPassant
6213                          || kind == BlackCapturesEnPassant);
6214     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6215 }
6216
6217 void
6218 MarkTargetSquares(int clear)
6219 {
6220   int x, y;
6221   if(!appData.markers || !appData.highlightDragging || 
6222      !appData.testLegality || gameMode == EditPosition) return;
6223   if(clear) {
6224     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6225   } else {
6226     int capt = 0;
6227     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6228     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6229       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6230       if(capt)
6231       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6232     }
6233   }
6234   DrawPosition(TRUE, NULL);
6235 }
6236
6237 void LeftClick(ClickType clickType, int xPix, int yPix)
6238 {
6239     int x, y;
6240     Boolean saveAnimate;
6241     static int second = 0, promotionChoice = 0;
6242     char promoChoice = NULLCHAR;
6243
6244     if(appData.seekGraph && appData.icsActive && loggedOn &&
6245         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6246         SeekGraphClick(clickType, xPix, yPix, 0);
6247         return;
6248     }
6249
6250     if (clickType == Press) ErrorPopDown();
6251     MarkTargetSquares(1);
6252
6253     x = EventToSquare(xPix, BOARD_WIDTH);
6254     y = EventToSquare(yPix, BOARD_HEIGHT);
6255     if (!flipView && y >= 0) {
6256         y = BOARD_HEIGHT - 1 - y;
6257     }
6258     if (flipView && x >= 0) {
6259         x = BOARD_WIDTH - 1 - x;
6260     }
6261
6262     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6263         if(clickType == Release) return; // ignore upclick of click-click destination
6264         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6265         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6266         if(gameInfo.holdingsWidth && 
6267                 (WhiteOnMove(currentMove) 
6268                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6269                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6270             // click in right holdings, for determining promotion piece
6271             ChessSquare p = boards[currentMove][y][x];
6272             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6273             if(p != EmptySquare) {
6274                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6275                 fromX = fromY = -1;
6276                 return;
6277             }
6278         }
6279         DrawPosition(FALSE, boards[currentMove]);
6280         return;
6281     }
6282
6283     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6284     if(clickType == Press
6285             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6286               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6287               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6288         return;
6289
6290     autoQueen = appData.alwaysPromoteToQueen;
6291
6292     if (fromX == -1) {
6293       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6294         if (clickType == Press) {
6295             /* First square */
6296             if (OKToStartUserMove(x, y)) {
6297                 fromX = x;
6298                 fromY = y;
6299                 second = 0;
6300                 MarkTargetSquares(0);
6301                 DragPieceBegin(xPix, yPix);
6302                 if (appData.highlightDragging) {
6303                     SetHighlights(x, y, -1, -1);
6304                 }
6305             }
6306         }
6307         return;
6308       }
6309     }
6310
6311     /* fromX != -1 */
6312     if (clickType == Press && gameMode != EditPosition) {
6313         ChessSquare fromP;
6314         ChessSquare toP;
6315         int frc;
6316
6317         // ignore off-board to clicks
6318         if(y < 0 || x < 0) return;
6319
6320         /* Check if clicking again on the same color piece */
6321         fromP = boards[currentMove][fromY][fromX];
6322         toP = boards[currentMove][y][x];
6323         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6324         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6325              WhitePawn <= toP && toP <= WhiteKing &&
6326              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6327              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6328             (BlackPawn <= fromP && fromP <= BlackKing && 
6329              BlackPawn <= toP && toP <= BlackKing &&
6330              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6331              !(fromP == BlackKing && toP == BlackRook && frc))) {
6332             /* Clicked again on same color piece -- changed his mind */
6333             second = (x == fromX && y == fromY);
6334            if(!second || !OnlyMove(&x, &y, TRUE)) {
6335             if (appData.highlightDragging) {
6336                 SetHighlights(x, y, -1, -1);
6337             } else {
6338                 ClearHighlights();
6339             }
6340             if (OKToStartUserMove(x, y)) {
6341                 fromX = x;
6342                 fromY = y;
6343                 MarkTargetSquares(0);
6344                 DragPieceBegin(xPix, yPix);
6345             }
6346             return;
6347            }
6348         }
6349         // ignore clicks on holdings
6350         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6351     }
6352
6353     if (clickType == Release && x == fromX && y == fromY) {
6354         DragPieceEnd(xPix, yPix);
6355         if (appData.animateDragging) {
6356             /* Undo animation damage if any */
6357             DrawPosition(FALSE, NULL);
6358         }
6359         if (second) {
6360             /* Second up/down in same square; just abort move */
6361             second = 0;
6362             fromX = fromY = -1;
6363             ClearHighlights();
6364             gotPremove = 0;
6365             ClearPremoveHighlights();
6366         } else {
6367             /* First upclick in same square; start click-click mode */
6368             SetHighlights(x, y, -1, -1);
6369         }
6370         return;
6371     }
6372
6373     /* we now have a different from- and (possibly off-board) to-square */
6374     /* Completed move */
6375     toX = x;
6376     toY = y;
6377     saveAnimate = appData.animate;
6378     if (clickType == Press) {
6379         /* Finish clickclick move */
6380         if (appData.animate || appData.highlightLastMove) {
6381             SetHighlights(fromX, fromY, toX, toY);
6382         } else {
6383             ClearHighlights();
6384         }
6385     } else {
6386         /* Finish drag move */
6387         if (appData.highlightLastMove) {
6388             SetHighlights(fromX, fromY, toX, toY);
6389         } else {
6390             ClearHighlights();
6391         }
6392         DragPieceEnd(xPix, yPix);
6393         /* Don't animate move and drag both */
6394         appData.animate = FALSE;
6395     }
6396
6397     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6398     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6399         ChessSquare piece = boards[currentMove][fromY][fromX];
6400         if(gameMode == EditPosition && piece != EmptySquare &&
6401            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6402             int n;
6403              
6404             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6405                 n = PieceToNumber(piece - (int)BlackPawn);
6406                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6407                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6408                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6409             } else
6410             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6411                 n = PieceToNumber(piece);
6412                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6413                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6414                 boards[currentMove][n][BOARD_WIDTH-2]++;
6415             }
6416             boards[currentMove][fromY][fromX] = EmptySquare;
6417         }
6418         ClearHighlights();
6419         fromX = fromY = -1;
6420         DrawPosition(TRUE, boards[currentMove]);
6421         return;
6422     }
6423
6424     // off-board moves should not be highlighted
6425     if(x < 0 || x < 0) ClearHighlights();
6426
6427     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6428         SetHighlights(fromX, fromY, toX, toY);
6429         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6430             // [HGM] super: promotion to captured piece selected from holdings
6431             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6432             promotionChoice = TRUE;
6433             // kludge follows to temporarily execute move on display, without promoting yet
6434             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6435             boards[currentMove][toY][toX] = p;
6436             DrawPosition(FALSE, boards[currentMove]);
6437             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6438             boards[currentMove][toY][toX] = q;
6439             DisplayMessage("Click in holdings to choose piece", "");
6440             return;
6441         }
6442         PromotionPopUp();
6443     } else {
6444         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6445         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6446         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6447         fromX = fromY = -1;
6448     }
6449     appData.animate = saveAnimate;
6450     if (appData.animate || appData.animateDragging) {
6451         /* Undo animation damage if needed */
6452         DrawPosition(FALSE, NULL);
6453     }
6454 }
6455
6456 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6457 {   // front-end-free part taken out of PieceMenuPopup
6458     int whichMenu; int xSqr, ySqr;
6459
6460     if(seekGraphUp) { // [HGM] seekgraph
6461         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6462         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6463         return -2;
6464     }
6465
6466     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6467          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6468         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6469         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6470         if(action == Press)   {
6471             originalFlip = flipView;
6472             flipView = !flipView; // temporarily flip board to see game from partners perspective
6473             DrawPosition(TRUE, partnerBoard);
6474             DisplayMessage(partnerStatus, "");
6475             partnerUp = TRUE;
6476         } else if(action == Release) {
6477             flipView = originalFlip;
6478             DrawPosition(TRUE, boards[currentMove]);
6479             partnerUp = FALSE;
6480         }
6481         return -2;
6482     }
6483
6484     xSqr = EventToSquare(x, BOARD_WIDTH);
6485     ySqr = EventToSquare(y, BOARD_HEIGHT);
6486     if (action == Release) UnLoadPV(); // [HGM] pv
6487     if (action != Press) return -2; // return code to be ignored
6488     switch (gameMode) {
6489       case IcsExamining:
6490         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6491       case EditPosition:
6492         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6493         if (xSqr < 0 || ySqr < 0) return -1;\r
6494         whichMenu = 0; // edit-position menu
6495         break;
6496       case IcsObserving:
6497         if(!appData.icsEngineAnalyze) return -1;
6498       case IcsPlayingWhite:
6499       case IcsPlayingBlack:
6500         if(!appData.zippyPlay) goto noZip;
6501       case AnalyzeMode:
6502       case AnalyzeFile:
6503       case MachinePlaysWhite:
6504       case MachinePlaysBlack:
6505       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6506         if (!appData.dropMenu) {
6507           LoadPV(x, y);
6508           return 2; // flag front-end to grab mouse events
6509         }
6510         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6511            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6512       case EditGame:
6513       noZip:
6514         if (xSqr < 0 || ySqr < 0) return -1;
6515         if (!appData.dropMenu || appData.testLegality &&
6516             gameInfo.variant != VariantBughouse &&
6517             gameInfo.variant != VariantCrazyhouse) return -1;
6518         whichMenu = 1; // drop menu
6519         break;
6520       default:
6521         return -1;
6522     }
6523
6524     if (((*fromX = xSqr) < 0) ||
6525         ((*fromY = ySqr) < 0)) {
6526         *fromX = *fromY = -1;
6527         return -1;
6528     }
6529     if (flipView)
6530       *fromX = BOARD_WIDTH - 1 - *fromX;
6531     else
6532       *fromY = BOARD_HEIGHT - 1 - *fromY;
6533
6534     return whichMenu;
6535 }
6536
6537 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6538 {
6539 //    char * hint = lastHint;
6540     FrontEndProgramStats stats;
6541
6542     stats.which = cps == &first ? 0 : 1;
6543     stats.depth = cpstats->depth;
6544     stats.nodes = cpstats->nodes;
6545     stats.score = cpstats->score;
6546     stats.time = cpstats->time;
6547     stats.pv = cpstats->movelist;
6548     stats.hint = lastHint;
6549     stats.an_move_index = 0;
6550     stats.an_move_count = 0;
6551
6552     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6553         stats.hint = cpstats->move_name;
6554         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6555         stats.an_move_count = cpstats->nr_moves;
6556     }
6557
6558     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6559
6560     SetProgramStats( &stats );
6561 }
6562
6563 int
6564 Adjudicate(ChessProgramState *cps)
6565 {       // [HGM] some adjudications useful with buggy engines
6566         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6567         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6568         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6569         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6570         int k, count = 0; static int bare = 1;
6571         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6572         Boolean canAdjudicate = !appData.icsActive;
6573
6574         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6575         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6576             if( appData.testLegality )
6577             {   /* [HGM] Some more adjudications for obstinate engines */
6578                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6579                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6580                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6581                 static int moveCount = 6;
6582                 ChessMove result;
6583                 char *reason = NULL;
6584
6585                 /* Count what is on board. */
6586                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6587                 {   ChessSquare p = boards[forwardMostMove][i][j];
6588                     int m=i;
6589
6590                     switch((int) p)
6591                     {   /* count B,N,R and other of each side */
6592                         case WhiteKing:
6593                         case BlackKing:
6594                              NrK++; break; // [HGM] atomic: count Kings
6595                         case WhiteKnight:
6596                              NrWN++; break;
6597                         case WhiteBishop:
6598                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6599                              bishopsColor |= 1 << ((i^j)&1);
6600                              NrWB++; break;
6601                         case BlackKnight:
6602                              NrBN++; break;
6603                         case BlackBishop:
6604                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6605                              bishopsColor |= 1 << ((i^j)&1);
6606                              NrBB++; break;
6607                         case WhiteRook:
6608                              NrWR++; break;
6609                         case BlackRook:
6610                              NrBR++; break;
6611                         case WhiteQueen:
6612                              NrWQ++; break;
6613                         case BlackQueen:
6614                              NrBQ++; break;
6615                         case EmptySquare: 
6616                              break;
6617                         case BlackPawn:
6618                              m = 7-i;
6619                         case WhitePawn:
6620                              PawnAdvance += m; NrPawns++;
6621                     }
6622                     NrPieces += (p != EmptySquare);
6623                     NrW += ((int)p < (int)BlackPawn);
6624                     if(gameInfo.variant == VariantXiangqi && 
6625                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6626                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6627                         NrW -= ((int)p < (int)BlackPawn);
6628                     }
6629                 }
6630
6631                 /* Some material-based adjudications that have to be made before stalemate test */
6632                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6633                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6634                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6635                      if(canAdjudicate && appData.checkMates) {
6636                          if(engineOpponent)
6637                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6638                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6640                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6641                          return 1;
6642                      }
6643                 }
6644
6645                 /* Bare King in Shatranj (loses) or Losers (wins) */
6646                 if( NrW == 1 || NrPieces - NrW == 1) {
6647                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6648                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6649                      if(canAdjudicate && appData.checkMates) {
6650                          if(engineOpponent)
6651                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6652                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6653                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6654                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6655                          return 1;
6656                      }
6657                   } else
6658                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6659                   {    /* bare King */
6660                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6661                         if(canAdjudicate && appData.checkMates) {
6662                             /* but only adjudicate if adjudication enabled */
6663                             if(engineOpponent)
6664                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6665                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6666                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6667                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6668                             return 1;
6669                         }
6670                   }
6671                 } else bare = 1;
6672
6673
6674             // don't wait for engine to announce game end if we can judge ourselves
6675             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6676               case MT_CHECK:
6677                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6678                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6679                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6680                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6681                             checkCnt++;
6682                         if(checkCnt >= 2) {
6683                             reason = "Xboard adjudication: 3rd check";
6684                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6685                             break;
6686                         }
6687                     }
6688                 }
6689               case MT_NONE:
6690               default:
6691                 break;
6692               case MT_STALEMATE:
6693               case MT_STAINMATE:
6694                 reason = "Xboard adjudication: Stalemate";
6695                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6696                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6697                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6698                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6699                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6700                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6701                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6702                                                                         EP_CHECKMATE : EP_WINS);
6703                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6704                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6705                 }
6706                 break;
6707               case MT_CHECKMATE:
6708                 reason = "Xboard adjudication: Checkmate";
6709                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6710                 break;
6711             }
6712
6713                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6714                     case EP_STALEMATE:
6715                         result = GameIsDrawn; break;
6716                     case EP_CHECKMATE:
6717                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6718                     case EP_WINS:
6719                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6720                     default:
6721                         result = (ChessMove) 0;
6722                 }
6723                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6724                     if(engineOpponent)
6725                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6726                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727                     GameEnds( result, reason, GE_XBOARD );
6728                     return 1;
6729                 }
6730
6731                 /* Next absolutely insufficient mating material. */
6732                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6733                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6734                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6735                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6736                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6737
6738                      /* always flag draws, for judging claims */
6739                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6740
6741                      if(canAdjudicate && appData.materialDraws) {
6742                          /* but only adjudicate them if adjudication enabled */
6743                          if(engineOpponent) {
6744                            SendToProgram("force\n", engineOpponent); // suppress reply
6745                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6746                          }
6747                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6748                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6749                          return 1;
6750                      }
6751                 }
6752
6753                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6754                 if(NrPieces == 4 && 
6755                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6756                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6757                    || NrWN==2 || NrBN==2     /* KNNK */
6758                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6759                   ) ) {
6760                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6761                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6762                           if(engineOpponent) {
6763                             SendToProgram("force\n", engineOpponent); // suppress reply
6764                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6765                           }
6766                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6767                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6768                           return 1;
6769                      }
6770                 } else moveCount = 6;
6771             }
6772         }
6773           
6774         if (appData.debugMode) { int i;
6775             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6776                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6777                     appData.drawRepeats);
6778             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6779               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6780             
6781         }
6782
6783         // Repetition draws and 50-move rule can be applied independently of legality testing
6784
6785                 /* Check for rep-draws */
6786                 count = 0;
6787                 for(k = forwardMostMove-2;
6788                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6789                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6790                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6791                     k-=2)
6792                 {   int rights=0;
6793                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6794                         /* compare castling rights */
6795                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6796                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6797                                 rights++; /* King lost rights, while rook still had them */
6798                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6799                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6800                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6801                                    rights++; /* but at least one rook lost them */
6802                         }
6803                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6804                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6805                                 rights++; 
6806                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6807                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6808                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6809                                    rights++;
6810                         }
6811                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6812                             && appData.drawRepeats > 1) {
6813                              /* adjudicate after user-specified nr of repeats */
6814                              if(engineOpponent) {
6815                                SendToProgram("force\n", engineOpponent); // suppress reply
6816                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6817                              }
6818                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6819                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6820                                 // [HGM] xiangqi: check for forbidden perpetuals
6821                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6822                                 for(m=forwardMostMove; m>k; m-=2) {
6823                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6824                                         ourPerpetual = 0; // the current mover did not always check
6825                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6826                                         hisPerpetual = 0; // the opponent did not always check
6827                                 }
6828                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6829                                                                         ourPerpetual, hisPerpetual);
6830                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6831                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6832                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6833                                     return 1;
6834                                 }
6835                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6836                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6837                                 // Now check for perpetual chases
6838                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6839                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6840                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6841                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6842                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6843                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6844                                         return 1;
6845                                     }
6846                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6847                                         break; // Abort repetition-checking loop.
6848                                 }
6849                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6850                              }
6851                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6852                              return 1;
6853                         }
6854                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6855                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6856                     }
6857                 }
6858
6859                 /* Now we test for 50-move draws. Determine ply count */
6860                 count = forwardMostMove;
6861                 /* look for last irreversble move */
6862                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6863                     count--;
6864                 /* if we hit starting position, add initial plies */
6865                 if( count == backwardMostMove )
6866                     count -= initialRulePlies;
6867                 count = forwardMostMove - count; 
6868                 if( count >= 100)
6869                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6870                          /* this is used to judge if draw claims are legal */
6871                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6872                          if(engineOpponent) {
6873                            SendToProgram("force\n", engineOpponent); // suppress reply
6874                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6875                          }
6876                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6877                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6878                          return 1;
6879                 }
6880
6881                 /* if draw offer is pending, treat it as a draw claim
6882                  * when draw condition present, to allow engines a way to
6883                  * claim draws before making their move to avoid a race
6884                  * condition occurring after their move
6885                  */
6886                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6887                          char *p = NULL;
6888                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6889                              p = "Draw claim: 50-move rule";
6890                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6891                              p = "Draw claim: 3-fold repetition";
6892                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6893                              p = "Draw claim: insufficient mating material";
6894                          if( p != NULL && canAdjudicate) {
6895                              if(engineOpponent) {
6896                                SendToProgram("force\n", engineOpponent); // suppress reply
6897                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6898                              }
6899                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6900                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6901                              return 1;
6902                          }
6903                 }
6904
6905                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6906                     if(engineOpponent) {
6907                       SendToProgram("force\n", engineOpponent); // suppress reply
6908                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6909                     }
6910                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6911                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6912                     return 1;
6913                 }
6914         return 0;
6915 }
6916
6917 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6918 {   // [HGM] book: this routine intercepts moves to simulate book replies
6919     char *bookHit = NULL;
6920
6921     //first determine if the incoming move brings opponent into his book
6922     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6923         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6924     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6925     if(bookHit != NULL && !cps->bookSuspend) {
6926         // make sure opponent is not going to reply after receiving move to book position
6927         SendToProgram("force\n", cps);
6928         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6929     }
6930     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6931     // now arrange restart after book miss
6932     if(bookHit) {
6933         // after a book hit we never send 'go', and the code after the call to this routine
6934         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6935         char buf[MSG_SIZ];
6936         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6937         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6938         SendToProgram(buf, cps);
6939         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6940     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6941         SendToProgram("go\n", cps);
6942         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6943     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6944         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6945             SendToProgram("go\n", cps); 
6946         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6947     }
6948     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6949 }
6950
6951 char *savedMessage;
6952 ChessProgramState *savedState;
6953 void DeferredBookMove(void)
6954 {
6955         if(savedState->lastPing != savedState->lastPong)
6956                     ScheduleDelayedEvent(DeferredBookMove, 10);
6957         else
6958         HandleMachineMove(savedMessage, savedState);
6959 }
6960
6961 void
6962 HandleMachineMove(message, cps)
6963      char *message;
6964      ChessProgramState *cps;
6965 {
6966     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6967     char realname[MSG_SIZ];
6968     int fromX, fromY, toX, toY;
6969     ChessMove moveType;
6970     char promoChar;
6971     char *p;
6972     int machineWhite;
6973     char *bookHit;
6974
6975     cps->userError = 0;
6976
6977 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6978     /*
6979      * Kludge to ignore BEL characters
6980      */
6981     while (*message == '\007') message++;
6982
6983     /*
6984      * [HGM] engine debug message: ignore lines starting with '#' character
6985      */
6986     if(cps->debug && *message == '#') return;
6987
6988     /*
6989      * Look for book output
6990      */
6991     if (cps == &first && bookRequested) {
6992         if (message[0] == '\t' || message[0] == ' ') {
6993             /* Part of the book output is here; append it */
6994             strcat(bookOutput, message);
6995             strcat(bookOutput, "  \n");
6996             return;
6997         } else if (bookOutput[0] != NULLCHAR) {
6998             /* All of book output has arrived; display it */
6999             char *p = bookOutput;
7000             while (*p != NULLCHAR) {
7001                 if (*p == '\t') *p = ' ';
7002                 p++;
7003             }
7004             DisplayInformation(bookOutput);
7005             bookRequested = FALSE;
7006             /* Fall through to parse the current output */
7007         }
7008     }
7009
7010     /*
7011      * Look for machine move.
7012      */
7013     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7014         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7015     {
7016         /* This method is only useful on engines that support ping */
7017         if (cps->lastPing != cps->lastPong) {
7018           if (gameMode == BeginningOfGame) {
7019             /* Extra move from before last new; ignore */
7020             if (appData.debugMode) {
7021                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7022             }
7023           } else {
7024             if (appData.debugMode) {
7025                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7026                         cps->which, gameMode);
7027             }
7028
7029             SendToProgram("undo\n", cps);
7030           }
7031           return;
7032         }
7033
7034         switch (gameMode) {
7035           case BeginningOfGame:
7036             /* Extra move from before last reset; ignore */
7037             if (appData.debugMode) {
7038                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7039             }
7040             return;
7041
7042           case EndOfGame:
7043           case IcsIdle:
7044           default:
7045             /* Extra move after we tried to stop.  The mode test is
7046                not a reliable way of detecting this problem, but it's
7047                the best we can do on engines that don't support ping.
7048             */
7049             if (appData.debugMode) {
7050                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7051                         cps->which, gameMode);
7052             }
7053             SendToProgram("undo\n", cps);
7054             return;
7055
7056           case MachinePlaysWhite:
7057           case IcsPlayingWhite:
7058             machineWhite = TRUE;
7059             break;
7060
7061           case MachinePlaysBlack:
7062           case IcsPlayingBlack:
7063             machineWhite = FALSE;
7064             break;
7065
7066           case TwoMachinesPlay:
7067             machineWhite = (cps->twoMachinesColor[0] == 'w');
7068             break;
7069         }
7070         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7071             if (appData.debugMode) {
7072                 fprintf(debugFP,
7073                         "Ignoring move out of turn by %s, gameMode %d"
7074                         ", forwardMost %d\n",
7075                         cps->which, gameMode, forwardMostMove);
7076             }
7077             return;
7078         }
7079
7080     if (appData.debugMode) { int f = forwardMostMove;
7081         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7082                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7083                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7084     }
7085         if(cps->alphaRank) AlphaRank(machineMove, 4);
7086         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7087                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7088             /* Machine move could not be parsed; ignore it. */
7089             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7090                     machineMove, cps->which);
7091             DisplayError(buf1, 0);
7092             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7093                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7094             if (gameMode == TwoMachinesPlay) {
7095               GameEnds(machineWhite ? BlackWins : WhiteWins,
7096                        buf1, GE_XBOARD);
7097             }
7098             return;
7099         }
7100
7101         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7102         /* So we have to redo legality test with true e.p. status here,  */
7103         /* to make sure an illegal e.p. capture does not slip through,   */
7104         /* to cause a forfeit on a justified illegal-move complaint      */
7105         /* of the opponent.                                              */
7106         if( gameMode==TwoMachinesPlay && appData.testLegality
7107             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7108                                                               ) {
7109            ChessMove moveType;
7110            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7111                              fromY, fromX, toY, toX, promoChar);
7112             if (appData.debugMode) {
7113                 int i;
7114                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7115                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7116                 fprintf(debugFP, "castling rights\n");
7117             }
7118             if(moveType == IllegalMove) {
7119                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7120                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7121                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7122                            buf1, GE_XBOARD);
7123                 return;
7124            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7125            /* [HGM] Kludge to handle engines that send FRC-style castling
7126               when they shouldn't (like TSCP-Gothic) */
7127            switch(moveType) {
7128              case WhiteASideCastleFR:
7129              case BlackASideCastleFR:
7130                toX+=2;
7131                currentMoveString[2]++;
7132                break;
7133              case WhiteHSideCastleFR:
7134              case BlackHSideCastleFR:
7135                toX--;
7136                currentMoveString[2]--;
7137                break;
7138              default: ; // nothing to do, but suppresses warning of pedantic compilers
7139            }
7140         }
7141         hintRequested = FALSE;
7142         lastHint[0] = NULLCHAR;
7143         bookRequested = FALSE;
7144         /* Program may be pondering now */
7145         cps->maybeThinking = TRUE;
7146         if (cps->sendTime == 2) cps->sendTime = 1;
7147         if (cps->offeredDraw) cps->offeredDraw--;
7148
7149         /* currentMoveString is set as a side-effect of ParseOneMove */
7150         strcpy(machineMove, currentMoveString);
7151         strcat(machineMove, "\n");
7152         strcpy(moveList[forwardMostMove], machineMove);
7153
7154         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7155
7156         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7157         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7158             int count = 0;
7159
7160             while( count < adjudicateLossPlies ) {
7161                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7162
7163                 if( count & 1 ) {
7164                     score = -score; /* Flip score for winning side */
7165                 }
7166
7167                 if( score > adjudicateLossThreshold ) {
7168                     break;
7169                 }
7170
7171                 count++;
7172             }
7173
7174             if( count >= adjudicateLossPlies ) {
7175                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7176
7177                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7178                     "Xboard adjudication", 
7179                     GE_XBOARD );
7180
7181                 return;
7182             }
7183         }
7184
7185         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7186
7187 #if ZIPPY
7188         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7189             first.initDone) {
7190           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7191                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7192                 SendToICS("draw ");
7193                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7194           }
7195           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7196           ics_user_moved = 1;
7197           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7198                 char buf[3*MSG_SIZ];
7199
7200                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7201                         programStats.score / 100.,
7202                         programStats.depth,
7203                         programStats.time / 100.,
7204                         (unsigned int)programStats.nodes,
7205                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7206                         programStats.movelist);
7207                 SendToICS(buf);
7208 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7209           }
7210         }
7211 #endif
7212
7213         /* [AS] Save move info and clear stats for next move */
7214         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7215         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7216         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7217         ClearProgramStats();
7218         thinkOutput[0] = NULLCHAR;
7219         hiddenThinkOutputState = 0;
7220
7221         bookHit = NULL;
7222         if (gameMode == TwoMachinesPlay) {
7223             /* [HGM] relaying draw offers moved to after reception of move */
7224             /* and interpreting offer as claim if it brings draw condition */
7225             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7226                 SendToProgram("draw\n", cps->other);
7227             }
7228             if (cps->other->sendTime) {
7229                 SendTimeRemaining(cps->other,
7230                                   cps->other->twoMachinesColor[0] == 'w');
7231             }
7232             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7233             if (firstMove && !bookHit) {
7234                 firstMove = FALSE;
7235                 if (cps->other->useColors) {
7236                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7237                 }
7238                 SendToProgram("go\n", cps->other);
7239             }
7240             cps->other->maybeThinking = TRUE;
7241         }
7242
7243         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7244         
7245         if (!pausing && appData.ringBellAfterMoves) {
7246             RingBell();
7247         }
7248
7249         /* 
7250          * Reenable menu items that were disabled while
7251          * machine was thinking
7252          */
7253         if (gameMode != TwoMachinesPlay)
7254             SetUserThinkingEnables();
7255
7256         // [HGM] book: after book hit opponent has received move and is now in force mode
7257         // force the book reply into it, and then fake that it outputted this move by jumping
7258         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7259         if(bookHit) {
7260                 static char bookMove[MSG_SIZ]; // a bit generous?
7261
7262                 strcpy(bookMove, "move ");
7263                 strcat(bookMove, bookHit);
7264                 message = bookMove;
7265                 cps = cps->other;
7266                 programStats.nodes = programStats.depth = programStats.time = 
7267                 programStats.score = programStats.got_only_move = 0;
7268                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7269
7270                 if(cps->lastPing != cps->lastPong) {
7271                     savedMessage = message; // args for deferred call
7272                     savedState = cps;
7273                     ScheduleDelayedEvent(DeferredBookMove, 10);
7274                     return;
7275                 }
7276                 goto FakeBookMove;
7277         }
7278
7279         return;
7280     }
7281
7282     /* Set special modes for chess engines.  Later something general
7283      *  could be added here; for now there is just one kludge feature,
7284      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7285      *  when "xboard" is given as an interactive command.
7286      */
7287     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7288         cps->useSigint = FALSE;
7289         cps->useSigterm = FALSE;
7290     }
7291     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7292       ParseFeatures(message+8, cps);
7293       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7294     }
7295
7296     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7297      * want this, I was asked to put it in, and obliged.
7298      */
7299     if (!strncmp(message, "setboard ", 9)) {
7300         Board initial_position;
7301
7302         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7303
7304         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7305             DisplayError(_("Bad FEN received from engine"), 0);
7306             return ;
7307         } else {
7308            Reset(TRUE, FALSE);
7309            CopyBoard(boards[0], initial_position);
7310            initialRulePlies = FENrulePlies;
7311            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7312            else gameMode = MachinePlaysBlack;                 
7313            DrawPosition(FALSE, boards[currentMove]);
7314         }
7315         return;
7316     }
7317
7318     /*
7319      * Look for communication commands
7320      */
7321     if (!strncmp(message, "telluser ", 9)) {
7322         DisplayNote(message + 9);
7323         return;
7324     }
7325     if (!strncmp(message, "tellusererror ", 14)) {
7326         cps->userError = 1;
7327         DisplayError(message + 14, 0);
7328         return;
7329     }
7330     if (!strncmp(message, "tellopponent ", 13)) {
7331       if (appData.icsActive) {
7332         if (loggedOn) {
7333           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7334           SendToICS(buf1);
7335         }
7336       } else {
7337         DisplayNote(message + 13);
7338       }
7339       return;
7340     }
7341     if (!strncmp(message, "tellothers ", 11)) {
7342       if (appData.icsActive) {
7343         if (loggedOn) {
7344           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7345           SendToICS(buf1);
7346         }
7347       }
7348       return;
7349     }
7350     if (!strncmp(message, "tellall ", 8)) {
7351       if (appData.icsActive) {
7352         if (loggedOn) {
7353           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7354           SendToICS(buf1);
7355         }
7356       } else {
7357         DisplayNote(message + 8);
7358       }
7359       return;
7360     }
7361     if (strncmp(message, "warning", 7) == 0) {
7362         /* Undocumented feature, use tellusererror in new code */
7363         DisplayError(message, 0);
7364         return;
7365     }
7366     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7367         strcpy(realname, cps->tidy);
7368         strcat(realname, " query");
7369         AskQuestion(realname, buf2, buf1, cps->pr);
7370         return;
7371     }
7372     /* Commands from the engine directly to ICS.  We don't allow these to be 
7373      *  sent until we are logged on. Crafty kibitzes have been known to 
7374      *  interfere with the login process.
7375      */
7376     if (loggedOn) {
7377         if (!strncmp(message, "tellics ", 8)) {
7378             SendToICS(message + 8);
7379             SendToICS("\n");
7380             return;
7381         }
7382         if (!strncmp(message, "tellicsnoalias ", 15)) {
7383             SendToICS(ics_prefix);
7384             SendToICS(message + 15);
7385             SendToICS("\n");
7386             return;
7387         }
7388         /* The following are for backward compatibility only */
7389         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7390             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7391             SendToICS(ics_prefix);
7392             SendToICS(message);
7393             SendToICS("\n");
7394             return;
7395         }
7396     }
7397     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7398         return;
7399     }
7400     /*
7401      * If the move is illegal, cancel it and redraw the board.
7402      * Also deal with other error cases.  Matching is rather loose
7403      * here to accommodate engines written before the spec.
7404      */
7405     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7406         strncmp(message, "Error", 5) == 0) {
7407         if (StrStr(message, "name") || 
7408             StrStr(message, "rating") || StrStr(message, "?") ||
7409             StrStr(message, "result") || StrStr(message, "board") ||
7410             StrStr(message, "bk") || StrStr(message, "computer") ||
7411             StrStr(message, "variant") || StrStr(message, "hint") ||
7412             StrStr(message, "random") || StrStr(message, "depth") ||
7413             StrStr(message, "accepted")) {
7414             return;
7415         }
7416         if (StrStr(message, "protover")) {
7417           /* Program is responding to input, so it's apparently done
7418              initializing, and this error message indicates it is
7419              protocol version 1.  So we don't need to wait any longer
7420              for it to initialize and send feature commands. */
7421           FeatureDone(cps, 1);
7422           cps->protocolVersion = 1;
7423           return;
7424         }
7425         cps->maybeThinking = FALSE;
7426
7427         if (StrStr(message, "draw")) {
7428             /* Program doesn't have "draw" command */
7429             cps->sendDrawOffers = 0;
7430             return;
7431         }
7432         if (cps->sendTime != 1 &&
7433             (StrStr(message, "time") || StrStr(message, "otim"))) {
7434           /* Program apparently doesn't have "time" or "otim" command */
7435           cps->sendTime = 0;
7436           return;
7437         }
7438         if (StrStr(message, "analyze")) {
7439             cps->analysisSupport = FALSE;
7440             cps->analyzing = FALSE;
7441             Reset(FALSE, TRUE);
7442             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7443             DisplayError(buf2, 0);
7444             return;
7445         }
7446         if (StrStr(message, "(no matching move)st")) {
7447           /* Special kludge for GNU Chess 4 only */
7448           cps->stKludge = TRUE;
7449           SendTimeControl(cps, movesPerSession, timeControl,
7450                           timeIncrement, appData.searchDepth,
7451                           searchTime);
7452           return;
7453         }
7454         if (StrStr(message, "(no matching move)sd")) {
7455           /* Special kludge for GNU Chess 4 only */
7456           cps->sdKludge = TRUE;
7457           SendTimeControl(cps, movesPerSession, timeControl,
7458                           timeIncrement, appData.searchDepth,
7459                           searchTime);
7460           return;
7461         }
7462         if (!StrStr(message, "llegal")) {
7463             return;
7464         }
7465         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7466             gameMode == IcsIdle) return;
7467         if (forwardMostMove <= backwardMostMove) return;
7468         if (pausing) PauseEvent();
7469       if(appData.forceIllegal) {
7470             // [HGM] illegal: machine refused move; force position after move into it
7471           SendToProgram("force\n", cps);
7472           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7473                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7474                 // when black is to move, while there might be nothing on a2 or black
7475                 // might already have the move. So send the board as if white has the move.
7476                 // But first we must change the stm of the engine, as it refused the last move
7477                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7478                 if(WhiteOnMove(forwardMostMove)) {
7479                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7480                     SendBoard(cps, forwardMostMove); // kludgeless board
7481                 } else {
7482                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7483                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7484                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7485                 }
7486           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7487             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7488                  gameMode == TwoMachinesPlay)
7489               SendToProgram("go\n", cps);
7490             return;
7491       } else
7492         if (gameMode == PlayFromGameFile) {
7493             /* Stop reading this game file */
7494             gameMode = EditGame;
7495             ModeHighlight();
7496         }
7497         currentMove = forwardMostMove-1;
7498         DisplayMove(currentMove-1); /* before DisplayMoveError */
7499         SwitchClocks(forwardMostMove-1); // [HGM] race
7500         DisplayBothClocks();
7501         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7502                 parseList[currentMove], cps->which);
7503         DisplayMoveError(buf1);
7504         DrawPosition(FALSE, boards[currentMove]);
7505
7506         /* [HGM] illegal-move claim should forfeit game when Xboard */
7507         /* only passes fully legal moves                            */
7508         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7509             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7510                                 "False illegal-move claim", GE_XBOARD );
7511         }
7512         return;
7513     }
7514     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7515         /* Program has a broken "time" command that
7516            outputs a string not ending in newline.
7517            Don't use it. */
7518         cps->sendTime = 0;
7519     }
7520     
7521     /*
7522      * If chess program startup fails, exit with an error message.
7523      * Attempts to recover here are futile.
7524      */
7525     if ((StrStr(message, "unknown host") != NULL)
7526         || (StrStr(message, "No remote directory") != NULL)
7527         || (StrStr(message, "not found") != NULL)
7528         || (StrStr(message, "No such file") != NULL)
7529         || (StrStr(message, "can't alloc") != NULL)
7530         || (StrStr(message, "Permission denied") != NULL)) {
7531
7532         cps->maybeThinking = FALSE;
7533         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7534                 cps->which, cps->program, cps->host, message);
7535         RemoveInputSource(cps->isr);
7536         DisplayFatalError(buf1, 0, 1);
7537         return;
7538     }
7539     
7540     /* 
7541      * Look for hint output
7542      */
7543     if (sscanf(message, "Hint: %s", buf1) == 1) {
7544         if (cps == &first && hintRequested) {
7545             hintRequested = FALSE;
7546             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7547                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7548                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7549                                     PosFlags(forwardMostMove),
7550                                     fromY, fromX, toY, toX, promoChar, buf1);
7551                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7552                 DisplayInformation(buf2);
7553             } else {
7554                 /* Hint move could not be parsed!? */
7555               snprintf(buf2, sizeof(buf2),
7556                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7557                         buf1, cps->which);
7558                 DisplayError(buf2, 0);
7559             }
7560         } else {
7561             strcpy(lastHint, buf1);
7562         }
7563         return;
7564     }
7565
7566     /*
7567      * Ignore other messages if game is not in progress
7568      */
7569     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7570         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7571
7572     /*
7573      * look for win, lose, draw, or draw offer
7574      */
7575     if (strncmp(message, "1-0", 3) == 0) {
7576         char *p, *q, *r = "";
7577         p = strchr(message, '{');
7578         if (p) {
7579             q = strchr(p, '}');
7580             if (q) {
7581                 *q = NULLCHAR;
7582                 r = p + 1;
7583             }
7584         }
7585         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7586         return;
7587     } else if (strncmp(message, "0-1", 3) == 0) {
7588         char *p, *q, *r = "";
7589         p = strchr(message, '{');
7590         if (p) {
7591             q = strchr(p, '}');
7592             if (q) {
7593                 *q = NULLCHAR;
7594                 r = p + 1;
7595             }
7596         }
7597         /* Kludge for Arasan 4.1 bug */
7598         if (strcmp(r, "Black resigns") == 0) {
7599             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7600             return;
7601         }
7602         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7603         return;
7604     } else if (strncmp(message, "1/2", 3) == 0) {
7605         char *p, *q, *r = "";
7606         p = strchr(message, '{');
7607         if (p) {
7608             q = strchr(p, '}');
7609             if (q) {
7610                 *q = NULLCHAR;
7611                 r = p + 1;
7612             }
7613         }
7614             
7615         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7616         return;
7617
7618     } else if (strncmp(message, "White resign", 12) == 0) {
7619         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7620         return;
7621     } else if (strncmp(message, "Black resign", 12) == 0) {
7622         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7623         return;
7624     } else if (strncmp(message, "White matches", 13) == 0 ||
7625                strncmp(message, "Black matches", 13) == 0   ) {
7626         /* [HGM] ignore GNUShogi noises */
7627         return;
7628     } else if (strncmp(message, "White", 5) == 0 &&
7629                message[5] != '(' &&
7630                StrStr(message, "Black") == NULL) {
7631         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7632         return;
7633     } else if (strncmp(message, "Black", 5) == 0 &&
7634                message[5] != '(') {
7635         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7636         return;
7637     } else if (strcmp(message, "resign") == 0 ||
7638                strcmp(message, "computer resigns") == 0) {
7639         switch (gameMode) {
7640           case MachinePlaysBlack:
7641           case IcsPlayingBlack:
7642             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7643             break;
7644           case MachinePlaysWhite:
7645           case IcsPlayingWhite:
7646             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7647             break;
7648           case TwoMachinesPlay:
7649             if (cps->twoMachinesColor[0] == 'w')
7650               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7651             else
7652               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7653             break;
7654           default:
7655             /* can't happen */
7656             break;
7657         }
7658         return;
7659     } else if (strncmp(message, "opponent mates", 14) == 0) {
7660         switch (gameMode) {
7661           case MachinePlaysBlack:
7662           case IcsPlayingBlack:
7663             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7664             break;
7665           case MachinePlaysWhite:
7666           case IcsPlayingWhite:
7667             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7668             break;
7669           case TwoMachinesPlay:
7670             if (cps->twoMachinesColor[0] == 'w')
7671               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7672             else
7673               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7674             break;
7675           default:
7676             /* can't happen */
7677             break;
7678         }
7679         return;
7680     } else if (strncmp(message, "computer mates", 14) == 0) {
7681         switch (gameMode) {
7682           case MachinePlaysBlack:
7683           case IcsPlayingBlack:
7684             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7685             break;
7686           case MachinePlaysWhite:
7687           case IcsPlayingWhite:
7688             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7689             break;
7690           case TwoMachinesPlay:
7691             if (cps->twoMachinesColor[0] == 'w')
7692               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7693             else
7694               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7695             break;
7696           default:
7697             /* can't happen */
7698             break;
7699         }
7700         return;
7701     } else if (strncmp(message, "checkmate", 9) == 0) {
7702         if (WhiteOnMove(forwardMostMove)) {
7703             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7704         } else {
7705             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7706         }
7707         return;
7708     } else if (strstr(message, "Draw") != NULL ||
7709                strstr(message, "game is a draw") != NULL) {
7710         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7711         return;
7712     } else if (strstr(message, "offer") != NULL &&
7713                strstr(message, "draw") != NULL) {
7714 #if ZIPPY
7715         if (appData.zippyPlay && first.initDone) {
7716             /* Relay offer to ICS */
7717             SendToICS(ics_prefix);
7718             SendToICS("draw\n");
7719         }
7720 #endif
7721         cps->offeredDraw = 2; /* valid until this engine moves twice */
7722         if (gameMode == TwoMachinesPlay) {
7723             if (cps->other->offeredDraw) {
7724                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7725             /* [HGM] in two-machine mode we delay relaying draw offer      */
7726             /* until after we also have move, to see if it is really claim */
7727             }
7728         } else if (gameMode == MachinePlaysWhite ||
7729                    gameMode == MachinePlaysBlack) {
7730           if (userOfferedDraw) {
7731             DisplayInformation(_("Machine accepts your draw offer"));
7732             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7733           } else {
7734             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7735           }
7736         }
7737     }
7738
7739     
7740     /*
7741      * Look for thinking output
7742      */
7743     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7744           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7745                                 ) {
7746         int plylev, mvleft, mvtot, curscore, time;
7747         char mvname[MOVE_LEN];
7748         u64 nodes; // [DM]
7749         char plyext;
7750         int ignore = FALSE;
7751         int prefixHint = FALSE;
7752         mvname[0] = NULLCHAR;
7753
7754         switch (gameMode) {
7755           case MachinePlaysBlack:
7756           case IcsPlayingBlack:
7757             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7758             break;
7759           case MachinePlaysWhite:
7760           case IcsPlayingWhite:
7761             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7762             break;
7763           case AnalyzeMode:
7764           case AnalyzeFile:
7765             break;
7766           case IcsObserving: /* [DM] icsEngineAnalyze */
7767             if (!appData.icsEngineAnalyze) ignore = TRUE;
7768             break;
7769           case TwoMachinesPlay:
7770             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7771                 ignore = TRUE;
7772             }
7773             break;
7774           default:
7775             ignore = TRUE;
7776             break;
7777         }
7778
7779         if (!ignore) {
7780             buf1[0] = NULLCHAR;
7781             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7782                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7783
7784                 if (plyext != ' ' && plyext != '\t') {
7785                     time *= 100;
7786                 }
7787
7788                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7789                 if( cps->scoreIsAbsolute && 
7790                     ( gameMode == MachinePlaysBlack ||
7791                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7792                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7793                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7794                      !WhiteOnMove(currentMove)
7795                     ) )
7796                 {
7797                     curscore = -curscore;
7798                 }
7799
7800
7801                 programStats.depth = plylev;
7802                 programStats.nodes = nodes;
7803                 programStats.time = time;
7804                 programStats.score = curscore;
7805                 programStats.got_only_move = 0;
7806
7807                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7808                         int ticklen;
7809
7810                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7811                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7812                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7813                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7814                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7815                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7816                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7817                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7818                 }
7819
7820                 /* Buffer overflow protection */
7821                 if (buf1[0] != NULLCHAR) {
7822                     if (strlen(buf1) >= sizeof(programStats.movelist)
7823                         && appData.debugMode) {
7824                         fprintf(debugFP,
7825                                 "PV is too long; using the first %u bytes.\n",
7826                                 (unsigned) sizeof(programStats.movelist) - 1);
7827                     }
7828
7829                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7830                 } else {
7831                     sprintf(programStats.movelist, " no PV\n");
7832                 }
7833
7834                 if (programStats.seen_stat) {
7835                     programStats.ok_to_send = 1;
7836                 }
7837
7838                 if (strchr(programStats.movelist, '(') != NULL) {
7839                     programStats.line_is_book = 1;
7840                     programStats.nr_moves = 0;
7841                     programStats.moves_left = 0;
7842                 } else {
7843                     programStats.line_is_book = 0;
7844                 }
7845
7846                 SendProgramStatsToFrontend( cps, &programStats );
7847
7848                 /* 
7849                     [AS] Protect the thinkOutput buffer from overflow... this
7850                     is only useful if buf1 hasn't overflowed first!
7851                 */
7852                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7853                         plylev, 
7854                         (gameMode == TwoMachinesPlay ?
7855                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7856                         ((double) curscore) / 100.0,
7857                         prefixHint ? lastHint : "",
7858                         prefixHint ? " " : "" );
7859
7860                 if( buf1[0] != NULLCHAR ) {
7861                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7862
7863                     if( strlen(buf1) > max_len ) {
7864                         if( appData.debugMode) {
7865                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7866                         }
7867                         buf1[max_len+1] = '\0';
7868                     }
7869
7870                     strcat( thinkOutput, buf1 );
7871                 }
7872
7873                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7874                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7875                     DisplayMove(currentMove - 1);
7876                 }
7877                 return;
7878
7879             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7880                 /* crafty (9.25+) says "(only move) <move>"
7881                  * if there is only 1 legal move
7882                  */
7883                 sscanf(p, "(only move) %s", buf1);
7884                 sprintf(thinkOutput, "%s (only move)", buf1);
7885                 sprintf(programStats.movelist, "%s (only move)", buf1);
7886                 programStats.depth = 1;
7887                 programStats.nr_moves = 1;
7888                 programStats.moves_left = 1;
7889                 programStats.nodes = 1;
7890                 programStats.time = 1;
7891                 programStats.got_only_move = 1;
7892
7893                 /* Not really, but we also use this member to
7894                    mean "line isn't going to change" (Crafty
7895                    isn't searching, so stats won't change) */
7896                 programStats.line_is_book = 1;
7897
7898                 SendProgramStatsToFrontend( cps, &programStats );
7899                 
7900                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7901                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7902                     DisplayMove(currentMove - 1);
7903                 }
7904                 return;
7905             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7906                               &time, &nodes, &plylev, &mvleft,
7907                               &mvtot, mvname) >= 5) {
7908                 /* The stat01: line is from Crafty (9.29+) in response
7909                    to the "." command */
7910                 programStats.seen_stat = 1;
7911                 cps->maybeThinking = TRUE;
7912
7913                 if (programStats.got_only_move || !appData.periodicUpdates)
7914                   return;
7915
7916                 programStats.depth = plylev;
7917                 programStats.time = time;
7918                 programStats.nodes = nodes;
7919                 programStats.moves_left = mvleft;
7920                 programStats.nr_moves = mvtot;
7921                 strcpy(programStats.move_name, mvname);
7922                 programStats.ok_to_send = 1;
7923                 programStats.movelist[0] = '\0';
7924
7925                 SendProgramStatsToFrontend( cps, &programStats );
7926
7927                 return;
7928
7929             } else if (strncmp(message,"++",2) == 0) {
7930                 /* Crafty 9.29+ outputs this */
7931                 programStats.got_fail = 2;
7932                 return;
7933
7934             } else if (strncmp(message,"--",2) == 0) {
7935                 /* Crafty 9.29+ outputs this */
7936                 programStats.got_fail = 1;
7937                 return;
7938
7939             } else if (thinkOutput[0] != NULLCHAR &&
7940                        strncmp(message, "    ", 4) == 0) {
7941                 unsigned message_len;
7942
7943                 p = message;
7944                 while (*p && *p == ' ') p++;
7945
7946                 message_len = strlen( p );
7947
7948                 /* [AS] Avoid buffer overflow */
7949                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7950                     strcat(thinkOutput, " ");
7951                     strcat(thinkOutput, p);
7952                 }
7953
7954                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7955                     strcat(programStats.movelist, " ");
7956                     strcat(programStats.movelist, p);
7957                 }
7958
7959                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7960                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7961                     DisplayMove(currentMove - 1);
7962                 }
7963                 return;
7964             }
7965         }
7966         else {
7967             buf1[0] = NULLCHAR;
7968
7969             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7970                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7971             {
7972                 ChessProgramStats cpstats;
7973
7974                 if (plyext != ' ' && plyext != '\t') {
7975                     time *= 100;
7976                 }
7977
7978                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7979                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7980                     curscore = -curscore;
7981                 }
7982
7983                 cpstats.depth = plylev;
7984                 cpstats.nodes = nodes;
7985                 cpstats.time = time;
7986                 cpstats.score = curscore;
7987                 cpstats.got_only_move = 0;
7988                 cpstats.movelist[0] = '\0';
7989
7990                 if (buf1[0] != NULLCHAR) {
7991                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7992                 }
7993
7994                 cpstats.ok_to_send = 0;
7995                 cpstats.line_is_book = 0;
7996                 cpstats.nr_moves = 0;
7997                 cpstats.moves_left = 0;
7998
7999                 SendProgramStatsToFrontend( cps, &cpstats );
8000             }
8001         }
8002     }
8003 }
8004
8005
8006 /* Parse a game score from the character string "game", and
8007    record it as the history of the current game.  The game
8008    score is NOT assumed to start from the standard position. 
8009    The display is not updated in any way.
8010    */
8011 void
8012 ParseGameHistory(game)
8013      char *game;
8014 {
8015     ChessMove moveType;
8016     int fromX, fromY, toX, toY, boardIndex;
8017     char promoChar;
8018     char *p, *q;
8019     char buf[MSG_SIZ];
8020
8021     if (appData.debugMode)
8022       fprintf(debugFP, "Parsing game history: %s\n", game);
8023
8024     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8025     gameInfo.site = StrSave(appData.icsHost);
8026     gameInfo.date = PGNDate();
8027     gameInfo.round = StrSave("-");
8028
8029     /* Parse out names of players */
8030     while (*game == ' ') game++;
8031     p = buf;
8032     while (*game != ' ') *p++ = *game++;
8033     *p = NULLCHAR;
8034     gameInfo.white = StrSave(buf);
8035     while (*game == ' ') game++;
8036     p = buf;
8037     while (*game != ' ' && *game != '\n') *p++ = *game++;
8038     *p = NULLCHAR;
8039     gameInfo.black = StrSave(buf);
8040
8041     /* Parse moves */
8042     boardIndex = blackPlaysFirst ? 1 : 0;
8043     yynewstr(game);
8044     for (;;) {
8045         yyboardindex = boardIndex;
8046         moveType = (ChessMove) yylex();
8047         switch (moveType) {
8048           case IllegalMove:             /* maybe suicide chess, etc. */
8049   if (appData.debugMode) {
8050     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8051     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8052     setbuf(debugFP, NULL);
8053   }
8054           case WhitePromotionChancellor:
8055           case BlackPromotionChancellor:
8056           case WhitePromotionArchbishop:
8057           case BlackPromotionArchbishop:
8058           case WhitePromotionQueen:
8059           case BlackPromotionQueen:
8060           case WhitePromotionRook:
8061           case BlackPromotionRook:
8062           case WhitePromotionBishop:
8063           case BlackPromotionBishop:
8064           case WhitePromotionKnight:
8065           case BlackPromotionKnight:
8066           case WhitePromotionKing:
8067           case BlackPromotionKing:
8068           case NormalMove:
8069           case WhiteCapturesEnPassant:
8070           case BlackCapturesEnPassant:
8071           case WhiteKingSideCastle:
8072           case WhiteQueenSideCastle:
8073           case BlackKingSideCastle:
8074           case BlackQueenSideCastle:
8075           case WhiteKingSideCastleWild:
8076           case WhiteQueenSideCastleWild:
8077           case BlackKingSideCastleWild:
8078           case BlackQueenSideCastleWild:
8079           /* PUSH Fabien */
8080           case WhiteHSideCastleFR:
8081           case WhiteASideCastleFR:
8082           case BlackHSideCastleFR:
8083           case BlackASideCastleFR:
8084           /* POP Fabien */
8085             fromX = currentMoveString[0] - AAA;
8086             fromY = currentMoveString[1] - ONE;
8087             toX = currentMoveString[2] - AAA;
8088             toY = currentMoveString[3] - ONE;
8089             promoChar = currentMoveString[4];
8090             break;
8091           case WhiteDrop:
8092           case BlackDrop:
8093             fromX = moveType == WhiteDrop ?
8094               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8095             (int) CharToPiece(ToLower(currentMoveString[0]));
8096             fromY = DROP_RANK;
8097             toX = currentMoveString[2] - AAA;
8098             toY = currentMoveString[3] - ONE;
8099             promoChar = NULLCHAR;
8100             break;
8101           case AmbiguousMove:
8102             /* bug? */
8103             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8104   if (appData.debugMode) {
8105     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8106     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8107     setbuf(debugFP, NULL);
8108   }
8109             DisplayError(buf, 0);
8110             return;
8111           case ImpossibleMove:
8112             /* bug? */
8113             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8114   if (appData.debugMode) {
8115     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8117     setbuf(debugFP, NULL);
8118   }
8119             DisplayError(buf, 0);
8120             return;
8121           case (ChessMove) 0:   /* end of file */
8122             if (boardIndex < backwardMostMove) {
8123                 /* Oops, gap.  How did that happen? */
8124                 DisplayError(_("Gap in move list"), 0);
8125                 return;
8126             }
8127             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8128             if (boardIndex > forwardMostMove) {
8129                 forwardMostMove = boardIndex;
8130             }
8131             return;
8132           case ElapsedTime:
8133             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8134                 strcat(parseList[boardIndex-1], " ");
8135                 strcat(parseList[boardIndex-1], yy_text);
8136             }
8137             continue;
8138           case Comment:
8139           case PGNTag:
8140           case NAG:
8141           default:
8142             /* ignore */
8143             continue;
8144           case WhiteWins:
8145           case BlackWins:
8146           case GameIsDrawn:
8147           case GameUnfinished:
8148             if (gameMode == IcsExamining) {
8149                 if (boardIndex < backwardMostMove) {
8150                     /* Oops, gap.  How did that happen? */
8151                     return;
8152                 }
8153                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8154                 return;
8155             }
8156             gameInfo.result = moveType;
8157             p = strchr(yy_text, '{');
8158             if (p == NULL) p = strchr(yy_text, '(');
8159             if (p == NULL) {
8160                 p = yy_text;
8161                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8162             } else {
8163                 q = strchr(p, *p == '{' ? '}' : ')');
8164                 if (q != NULL) *q = NULLCHAR;
8165                 p++;
8166             }
8167             gameInfo.resultDetails = StrSave(p);
8168             continue;
8169         }
8170         if (boardIndex >= forwardMostMove &&
8171             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8172             backwardMostMove = blackPlaysFirst ? 1 : 0;
8173             return;
8174         }
8175         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8176                                  fromY, fromX, toY, toX, promoChar,
8177                                  parseList[boardIndex]);
8178         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8179         /* currentMoveString is set as a side-effect of yylex */
8180         strcpy(moveList[boardIndex], currentMoveString);
8181         strcat(moveList[boardIndex], "\n");
8182         boardIndex++;
8183         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8184         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8185           case MT_NONE:
8186           case MT_STALEMATE:
8187           default:
8188             break;
8189           case MT_CHECK:
8190             if(gameInfo.variant != VariantShogi)
8191                 strcat(parseList[boardIndex - 1], "+");
8192             break;
8193           case MT_CHECKMATE:
8194           case MT_STAINMATE:
8195             strcat(parseList[boardIndex - 1], "#");
8196             break;
8197         }
8198     }
8199 }
8200
8201
8202 /* Apply a move to the given board  */
8203 void
8204 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8205      int fromX, fromY, toX, toY;
8206      int promoChar;
8207      Board board;
8208 {
8209   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8210   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8211
8212     /* [HGM] compute & store e.p. status and castling rights for new position */
8213     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8214     { int i;
8215
8216       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8217       oldEP = (signed char)board[EP_STATUS];
8218       board[EP_STATUS] = EP_NONE;
8219
8220       if( board[toY][toX] != EmptySquare ) 
8221            board[EP_STATUS] = EP_CAPTURE;  
8222
8223       if( board[fromY][fromX] == WhitePawn ) {
8224            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8225                board[EP_STATUS] = EP_PAWN_MOVE;
8226            if( toY-fromY==2) {
8227                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8228                         gameInfo.variant != VariantBerolina || toX < fromX)
8229                       board[EP_STATUS] = toX | berolina;
8230                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8231                         gameInfo.variant != VariantBerolina || toX > fromX) 
8232                       board[EP_STATUS] = toX;
8233            }
8234       } else 
8235       if( board[fromY][fromX] == BlackPawn ) {
8236            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8237                board[EP_STATUS] = EP_PAWN_MOVE; 
8238            if( toY-fromY== -2) {
8239                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8240                         gameInfo.variant != VariantBerolina || toX < fromX)
8241                       board[EP_STATUS] = toX | berolina;
8242                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8243                         gameInfo.variant != VariantBerolina || toX > fromX) 
8244                       board[EP_STATUS] = toX;
8245            }
8246        }
8247
8248        for(i=0; i<nrCastlingRights; i++) {
8249            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8250               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8251              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8252        }
8253
8254     }
8255
8256   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8257   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8258        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8259          
8260   if (fromX == toX && fromY == toY) return;
8261
8262   if (fromY == DROP_RANK) {
8263         /* must be first */
8264         piece = board[toY][toX] = (ChessSquare) fromX;
8265   } else {
8266      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8267      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8268      if(gameInfo.variant == VariantKnightmate)
8269          king += (int) WhiteUnicorn - (int) WhiteKing;
8270
8271     /* Code added by Tord: */
8272     /* FRC castling assumed when king captures friendly rook. */
8273     if (board[fromY][fromX] == WhiteKing &&
8274              board[toY][toX] == WhiteRook) {
8275       board[fromY][fromX] = EmptySquare;
8276       board[toY][toX] = EmptySquare;
8277       if(toX > fromX) {
8278         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8279       } else {
8280         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8281       }
8282     } else if (board[fromY][fromX] == BlackKing &&
8283                board[toY][toX] == BlackRook) {
8284       board[fromY][fromX] = EmptySquare;
8285       board[toY][toX] = EmptySquare;
8286       if(toX > fromX) {
8287         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8288       } else {
8289         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8290       }
8291     /* End of code added by Tord */
8292
8293     } else if (board[fromY][fromX] == king
8294         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8295         && toY == fromY && toX > fromX+1) {
8296         board[fromY][fromX] = EmptySquare;
8297         board[toY][toX] = king;
8298         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8299         board[fromY][BOARD_RGHT-1] = EmptySquare;
8300     } else if (board[fromY][fromX] == king
8301         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8302                && toY == fromY && toX < fromX-1) {
8303         board[fromY][fromX] = EmptySquare;
8304         board[toY][toX] = king;
8305         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8306         board[fromY][BOARD_LEFT] = EmptySquare;
8307     } else if (board[fromY][fromX] == WhitePawn
8308                && toY >= BOARD_HEIGHT-promoRank
8309                && gameInfo.variant != VariantXiangqi
8310                ) {
8311         /* white pawn promotion */
8312         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8313         if (board[toY][toX] == EmptySquare) {
8314             board[toY][toX] = WhiteQueen;
8315         }
8316         if(gameInfo.variant==VariantBughouse ||
8317            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8318             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8319         board[fromY][fromX] = EmptySquare;
8320     } else if ((fromY == BOARD_HEIGHT-4)
8321                && (toX != fromX)
8322                && gameInfo.variant != VariantXiangqi
8323                && gameInfo.variant != VariantBerolina
8324                && (board[fromY][fromX] == WhitePawn)
8325                && (board[toY][toX] == EmptySquare)) {
8326         board[fromY][fromX] = EmptySquare;
8327         board[toY][toX] = WhitePawn;
8328         captured = board[toY - 1][toX];
8329         board[toY - 1][toX] = EmptySquare;
8330     } else if ((fromY == BOARD_HEIGHT-4)
8331                && (toX == fromX)
8332                && gameInfo.variant == VariantBerolina
8333                && (board[fromY][fromX] == WhitePawn)
8334                && (board[toY][toX] == EmptySquare)) {
8335         board[fromY][fromX] = EmptySquare;
8336         board[toY][toX] = WhitePawn;
8337         if(oldEP & EP_BEROLIN_A) {
8338                 captured = board[fromY][fromX-1];
8339                 board[fromY][fromX-1] = EmptySquare;
8340         }else{  captured = board[fromY][fromX+1];
8341                 board[fromY][fromX+1] = EmptySquare;
8342         }
8343     } else if (board[fromY][fromX] == king
8344         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8345                && toY == fromY && toX > fromX+1) {
8346         board[fromY][fromX] = EmptySquare;
8347         board[toY][toX] = king;
8348         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8349         board[fromY][BOARD_RGHT-1] = EmptySquare;
8350     } else if (board[fromY][fromX] == king
8351         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8352                && toY == fromY && toX < fromX-1) {
8353         board[fromY][fromX] = EmptySquare;
8354         board[toY][toX] = king;
8355         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8356         board[fromY][BOARD_LEFT] = EmptySquare;
8357     } else if (fromY == 7 && fromX == 3
8358                && board[fromY][fromX] == BlackKing
8359                && toY == 7 && toX == 5) {
8360         board[fromY][fromX] = EmptySquare;
8361         board[toY][toX] = BlackKing;
8362         board[fromY][7] = EmptySquare;
8363         board[toY][4] = BlackRook;
8364     } else if (fromY == 7 && fromX == 3
8365                && board[fromY][fromX] == BlackKing
8366                && toY == 7 && toX == 1) {
8367         board[fromY][fromX] = EmptySquare;
8368         board[toY][toX] = BlackKing;
8369         board[fromY][0] = EmptySquare;
8370         board[toY][2] = BlackRook;
8371     } else if (board[fromY][fromX] == BlackPawn
8372                && toY < promoRank
8373                && gameInfo.variant != VariantXiangqi
8374                ) {
8375         /* black pawn promotion */
8376         board[toY][toX] = CharToPiece(ToLower(promoChar));
8377         if (board[toY][toX] == EmptySquare) {
8378             board[toY][toX] = BlackQueen;
8379         }
8380         if(gameInfo.variant==VariantBughouse ||
8381            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8382             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8383         board[fromY][fromX] = EmptySquare;
8384     } else if ((fromY == 3)
8385                && (toX != fromX)
8386                && gameInfo.variant != VariantXiangqi
8387                && gameInfo.variant != VariantBerolina
8388                && (board[fromY][fromX] == BlackPawn)
8389                && (board[toY][toX] == EmptySquare)) {
8390         board[fromY][fromX] = EmptySquare;
8391         board[toY][toX] = BlackPawn;
8392         captured = board[toY + 1][toX];
8393         board[toY + 1][toX] = EmptySquare;
8394     } else if ((fromY == 3)
8395                && (toX == fromX)
8396                && gameInfo.variant == VariantBerolina
8397                && (board[fromY][fromX] == BlackPawn)
8398                && (board[toY][toX] == EmptySquare)) {
8399         board[fromY][fromX] = EmptySquare;
8400         board[toY][toX] = BlackPawn;
8401         if(oldEP & EP_BEROLIN_A) {
8402                 captured = board[fromY][fromX-1];
8403                 board[fromY][fromX-1] = EmptySquare;
8404         }else{  captured = board[fromY][fromX+1];
8405                 board[fromY][fromX+1] = EmptySquare;
8406         }
8407     } else {
8408         board[toY][toX] = board[fromY][fromX];
8409         board[fromY][fromX] = EmptySquare;
8410     }
8411
8412     /* [HGM] now we promote for Shogi, if needed */
8413     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8414         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8415   }
8416
8417     if (gameInfo.holdingsWidth != 0) {
8418
8419       /* !!A lot more code needs to be written to support holdings  */
8420       /* [HGM] OK, so I have written it. Holdings are stored in the */
8421       /* penultimate board files, so they are automaticlly stored   */
8422       /* in the game history.                                       */
8423       if (fromY == DROP_RANK) {
8424         /* Delete from holdings, by decreasing count */
8425         /* and erasing image if necessary            */
8426         p = (int) fromX;
8427         if(p < (int) BlackPawn) { /* white drop */
8428              p -= (int)WhitePawn;
8429                  p = PieceToNumber((ChessSquare)p);
8430              if(p >= gameInfo.holdingsSize) p = 0;
8431              if(--board[p][BOARD_WIDTH-2] <= 0)
8432                   board[p][BOARD_WIDTH-1] = EmptySquare;
8433              if((int)board[p][BOARD_WIDTH-2] < 0)
8434                         board[p][BOARD_WIDTH-2] = 0;
8435         } else {                  /* black drop */
8436              p -= (int)BlackPawn;
8437                  p = PieceToNumber((ChessSquare)p);
8438              if(p >= gameInfo.holdingsSize) p = 0;
8439              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8440                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8441              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8442                         board[BOARD_HEIGHT-1-p][1] = 0;
8443         }
8444       }
8445       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8446           && gameInfo.variant != VariantBughouse        ) {
8447         /* [HGM] holdings: Add to holdings, if holdings exist */
8448         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8449                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8450                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8451         }
8452         p = (int) captured;
8453         if (p >= (int) BlackPawn) {
8454           p -= (int)BlackPawn;
8455           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8456                   /* in Shogi restore piece to its original  first */
8457                   captured = (ChessSquare) (DEMOTED captured);
8458                   p = DEMOTED p;
8459           }
8460           p = PieceToNumber((ChessSquare)p);
8461           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8462           board[p][BOARD_WIDTH-2]++;
8463           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8464         } else {
8465           p -= (int)WhitePawn;
8466           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8467                   captured = (ChessSquare) (DEMOTED captured);
8468                   p = DEMOTED p;
8469           }
8470           p = PieceToNumber((ChessSquare)p);
8471           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8472           board[BOARD_HEIGHT-1-p][1]++;
8473           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8474         }
8475       }
8476     } else if (gameInfo.variant == VariantAtomic) {
8477       if (captured != EmptySquare) {
8478         int y, x;
8479         for (y = toY-1; y <= toY+1; y++) {
8480           for (x = toX-1; x <= toX+1; x++) {
8481             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8482                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8483               board[y][x] = EmptySquare;
8484             }
8485           }
8486         }
8487         board[toY][toX] = EmptySquare;
8488       }
8489     }
8490     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8491         /* [HGM] Shogi promotions */
8492         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8493     }
8494
8495     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8496                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8497         // [HGM] superchess: take promotion piece out of holdings
8498         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8499         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8500             if(!--board[k][BOARD_WIDTH-2])
8501                 board[k][BOARD_WIDTH-1] = EmptySquare;
8502         } else {
8503             if(!--board[BOARD_HEIGHT-1-k][1])
8504                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8505         }
8506     }
8507
8508 }
8509
8510 /* Updates forwardMostMove */
8511 void
8512 MakeMove(fromX, fromY, toX, toY, promoChar)
8513      int fromX, fromY, toX, toY;
8514      int promoChar;
8515 {
8516 //    forwardMostMove++; // [HGM] bare: moved downstream
8517
8518     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8519         int timeLeft; static int lastLoadFlag=0; int king, piece;
8520         piece = boards[forwardMostMove][fromY][fromX];
8521         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8522         if(gameInfo.variant == VariantKnightmate)
8523             king += (int) WhiteUnicorn - (int) WhiteKing;
8524         if(forwardMostMove == 0) {
8525             if(blackPlaysFirst) 
8526                 fprintf(serverMoves, "%s;", second.tidy);
8527             fprintf(serverMoves, "%s;", first.tidy);
8528             if(!blackPlaysFirst) 
8529                 fprintf(serverMoves, "%s;", second.tidy);
8530         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8531         lastLoadFlag = loadFlag;
8532         // print base move
8533         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8534         // print castling suffix
8535         if( toY == fromY && piece == king ) {
8536             if(toX-fromX > 1)
8537                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8538             if(fromX-toX >1)
8539                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8540         }
8541         // e.p. suffix
8542         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8543              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8544              boards[forwardMostMove][toY][toX] == EmptySquare
8545              && fromX != toX )
8546                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8547         // promotion suffix
8548         if(promoChar != NULLCHAR)
8549                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8550         if(!loadFlag) {
8551             fprintf(serverMoves, "/%d/%d",
8552                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8553             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8554             else                      timeLeft = blackTimeRemaining/1000;
8555             fprintf(serverMoves, "/%d", timeLeft);
8556         }
8557         fflush(serverMoves);
8558     }
8559
8560     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8561       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8562                         0, 1);
8563       return;
8564     }
8565     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8566     if (commentList[forwardMostMove+1] != NULL) {
8567         free(commentList[forwardMostMove+1]);
8568         commentList[forwardMostMove+1] = NULL;
8569     }
8570     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8571     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8572     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8573     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8574     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8575     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8576     gameInfo.result = GameUnfinished;
8577     if (gameInfo.resultDetails != NULL) {
8578         free(gameInfo.resultDetails);
8579         gameInfo.resultDetails = NULL;
8580     }
8581     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8582                               moveList[forwardMostMove - 1]);
8583     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8584                              PosFlags(forwardMostMove - 1),
8585                              fromY, fromX, toY, toX, promoChar,
8586                              parseList[forwardMostMove - 1]);
8587     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8588       case MT_NONE:
8589       case MT_STALEMATE:
8590       default:
8591         break;
8592       case MT_CHECK:
8593         if(gameInfo.variant != VariantShogi)
8594             strcat(parseList[forwardMostMove - 1], "+");
8595         break;
8596       case MT_CHECKMATE:
8597       case MT_STAINMATE:
8598         strcat(parseList[forwardMostMove - 1], "#");
8599         break;
8600     }
8601     if (appData.debugMode) {
8602         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8603     }
8604
8605 }
8606
8607 /* Updates currentMove if not pausing */
8608 void
8609 ShowMove(fromX, fromY, toX, toY)
8610 {
8611     int instant = (gameMode == PlayFromGameFile) ?
8612         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8613     if(appData.noGUI) return;
8614     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8615         if (!instant) {
8616             if (forwardMostMove == currentMove + 1) {
8617                 AnimateMove(boards[forwardMostMove - 1],
8618                             fromX, fromY, toX, toY);
8619             }
8620             if (appData.highlightLastMove) {
8621                 SetHighlights(fromX, fromY, toX, toY);
8622             }
8623         }
8624         currentMove = forwardMostMove;
8625     }
8626
8627     if (instant) return;
8628
8629     DisplayMove(currentMove - 1);
8630     DrawPosition(FALSE, boards[currentMove]);
8631     DisplayBothClocks();
8632     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8633 }
8634
8635 void SendEgtPath(ChessProgramState *cps)
8636 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8637         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8638
8639         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8640
8641         while(*p) {
8642             char c, *q = name+1, *r, *s;
8643
8644             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8645             while(*p && *p != ',') *q++ = *p++;
8646             *q++ = ':'; *q = 0;
8647             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8648                 strcmp(name, ",nalimov:") == 0 ) {
8649                 // take nalimov path from the menu-changeable option first, if it is defined
8650                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8651                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8652             } else
8653             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8654                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8655                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8656                 s = r = StrStr(s, ":") + 1; // beginning of path info
8657                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8658                 c = *r; *r = 0;             // temporarily null-terminate path info
8659                     *--q = 0;               // strip of trailig ':' from name
8660                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8661                 *r = c;
8662                 SendToProgram(buf,cps);     // send egtbpath command for this format
8663             }
8664             if(*p == ',') p++; // read away comma to position for next format name
8665         }
8666 }
8667
8668 void
8669 InitChessProgram(cps, setup)
8670      ChessProgramState *cps;
8671      int setup; /* [HGM] needed to setup FRC opening position */
8672 {
8673     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8674     if (appData.noChessProgram) return;
8675     hintRequested = FALSE;
8676     bookRequested = FALSE;
8677
8678     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8679     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8680     if(cps->memSize) { /* [HGM] memory */
8681         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8682         SendToProgram(buf, cps);
8683     }
8684     SendEgtPath(cps); /* [HGM] EGT */
8685     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8686         sprintf(buf, "cores %d\n", appData.smpCores);
8687         SendToProgram(buf, cps);
8688     }
8689
8690     SendToProgram(cps->initString, cps);
8691     if (gameInfo.variant != VariantNormal &&
8692         gameInfo.variant != VariantLoadable
8693         /* [HGM] also send variant if board size non-standard */
8694         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8695                                             ) {
8696       char *v = VariantName(gameInfo.variant);
8697       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8698         /* [HGM] in protocol 1 we have to assume all variants valid */
8699         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8700         DisplayFatalError(buf, 0, 1);
8701         return;
8702       }
8703
8704       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8705       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8706       if( gameInfo.variant == VariantXiangqi )
8707            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8708       if( gameInfo.variant == VariantShogi )
8709            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8710       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8711            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8712       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8713                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8714            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8715       if( gameInfo.variant == VariantCourier )
8716            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8717       if( gameInfo.variant == VariantSuper )
8718            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8719       if( gameInfo.variant == VariantGreat )
8720            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8721
8722       if(overruled) {
8723            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8724                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8725            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8726            if(StrStr(cps->variants, b) == NULL) { 
8727                // specific sized variant not known, check if general sizing allowed
8728                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8729                    if(StrStr(cps->variants, "boardsize") == NULL) {
8730                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8731                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8732                        DisplayFatalError(buf, 0, 1);
8733                        return;
8734                    }
8735                    /* [HGM] here we really should compare with the maximum supported board size */
8736                }
8737            }
8738       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8739       sprintf(buf, "variant %s\n", b);
8740       SendToProgram(buf, cps);
8741     }
8742     currentlyInitializedVariant = gameInfo.variant;
8743
8744     /* [HGM] send opening position in FRC to first engine */
8745     if(setup) {
8746           SendToProgram("force\n", cps);
8747           SendBoard(cps, 0);
8748           /* engine is now in force mode! Set flag to wake it up after first move. */
8749           setboardSpoiledMachineBlack = 1;
8750     }
8751
8752     if (cps->sendICS) {
8753       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8754       SendToProgram(buf, cps);
8755     }
8756     cps->maybeThinking = FALSE;
8757     cps->offeredDraw = 0;
8758     if (!appData.icsActive) {
8759         SendTimeControl(cps, movesPerSession, timeControl,
8760                         timeIncrement, appData.searchDepth,
8761                         searchTime);
8762     }
8763     if (appData.showThinking 
8764         // [HGM] thinking: four options require thinking output to be sent
8765         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8766                                 ) {
8767         SendToProgram("post\n", cps);
8768     }
8769     SendToProgram("hard\n", cps);
8770     if (!appData.ponderNextMove) {
8771         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8772            it without being sure what state we are in first.  "hard"
8773            is not a toggle, so that one is OK.
8774          */
8775         SendToProgram("easy\n", cps);
8776     }
8777     if (cps->usePing) {
8778       sprintf(buf, "ping %d\n", ++cps->lastPing);
8779       SendToProgram(buf, cps);
8780     }
8781     cps->initDone = TRUE;
8782 }   
8783
8784
8785 void
8786 StartChessProgram(cps)
8787      ChessProgramState *cps;
8788 {
8789     char buf[MSG_SIZ];
8790     int err;
8791
8792     if (appData.noChessProgram) return;
8793     cps->initDone = FALSE;
8794
8795     if (strcmp(cps->host, "localhost") == 0) {
8796         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8797     } else if (*appData.remoteShell == NULLCHAR) {
8798         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8799     } else {
8800         if (*appData.remoteUser == NULLCHAR) {
8801           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8802                     cps->program);
8803         } else {
8804           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8805                     cps->host, appData.remoteUser, cps->program);
8806         }
8807         err = StartChildProcess(buf, "", &cps->pr);
8808     }
8809     
8810     if (err != 0) {
8811         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8812         DisplayFatalError(buf, err, 1);
8813         cps->pr = NoProc;
8814         cps->isr = NULL;
8815         return;
8816     }
8817     
8818     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8819     if (cps->protocolVersion > 1) {
8820       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8821       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8822       cps->comboCnt = 0;  //                and values of combo boxes
8823       SendToProgram(buf, cps);
8824     } else {
8825       SendToProgram("xboard\n", cps);
8826     }
8827 }
8828
8829
8830 void
8831 TwoMachinesEventIfReady P((void))
8832 {
8833   if (first.lastPing != first.lastPong) {
8834     DisplayMessage("", _("Waiting for first chess program"));
8835     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8836     return;
8837   }
8838   if (second.lastPing != second.lastPong) {
8839     DisplayMessage("", _("Waiting for second chess program"));
8840     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8841     return;
8842   }
8843   ThawUI();
8844   TwoMachinesEvent();
8845 }
8846
8847 void
8848 NextMatchGame P((void))
8849 {
8850     int index; /* [HGM] autoinc: step load index during match */
8851     Reset(FALSE, TRUE);
8852     if (*appData.loadGameFile != NULLCHAR) {
8853         index = appData.loadGameIndex;
8854         if(index < 0) { // [HGM] autoinc
8855             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8856             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8857         } 
8858         LoadGameFromFile(appData.loadGameFile,
8859                          index,
8860                          appData.loadGameFile, FALSE);
8861     } else if (*appData.loadPositionFile != NULLCHAR) {
8862         index = appData.loadPositionIndex;
8863         if(index < 0) { // [HGM] autoinc
8864             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8865             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8866         } 
8867         LoadPositionFromFile(appData.loadPositionFile,
8868                              index,
8869                              appData.loadPositionFile);
8870     }
8871     TwoMachinesEventIfReady();
8872 }
8873
8874 void UserAdjudicationEvent( int result )
8875 {
8876     ChessMove gameResult = GameIsDrawn;
8877
8878     if( result > 0 ) {
8879         gameResult = WhiteWins;
8880     }
8881     else if( result < 0 ) {
8882         gameResult = BlackWins;
8883     }
8884
8885     if( gameMode == TwoMachinesPlay ) {
8886         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8887     }
8888 }
8889
8890
8891 // [HGM] save: calculate checksum of game to make games easily identifiable
8892 int StringCheckSum(char *s)
8893 {
8894         int i = 0;
8895         if(s==NULL) return 0;
8896         while(*s) i = i*259 + *s++;
8897         return i;
8898 }
8899
8900 int GameCheckSum()
8901 {
8902         int i, sum=0;
8903         for(i=backwardMostMove; i<forwardMostMove; i++) {
8904                 sum += pvInfoList[i].depth;
8905                 sum += StringCheckSum(parseList[i]);
8906                 sum += StringCheckSum(commentList[i]);
8907                 sum *= 261;
8908         }
8909         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8910         return sum + StringCheckSum(commentList[i]);
8911 } // end of save patch
8912
8913 void
8914 GameEnds(result, resultDetails, whosays)
8915      ChessMove result;
8916      char *resultDetails;
8917      int whosays;
8918 {
8919     GameMode nextGameMode;
8920     int isIcsGame;
8921     char buf[MSG_SIZ];
8922
8923     if(endingGame) return; /* [HGM] crash: forbid recursion */
8924     endingGame = 1;
8925     if(twoBoards) { // [HGM] dual: switch back to one board
8926         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8927         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8928     }
8929     if (appData.debugMode) {
8930       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8931               result, resultDetails ? resultDetails : "(null)", whosays);
8932     }
8933
8934     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8935
8936     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8937         /* If we are playing on ICS, the server decides when the
8938            game is over, but the engine can offer to draw, claim 
8939            a draw, or resign. 
8940          */
8941 #if ZIPPY
8942         if (appData.zippyPlay && first.initDone) {
8943             if (result == GameIsDrawn) {
8944                 /* In case draw still needs to be claimed */
8945                 SendToICS(ics_prefix);
8946                 SendToICS("draw\n");
8947             } else if (StrCaseStr(resultDetails, "resign")) {
8948                 SendToICS(ics_prefix);
8949                 SendToICS("resign\n");
8950             }
8951         }
8952 #endif
8953         endingGame = 0; /* [HGM] crash */
8954         return;
8955     }
8956
8957     /* If we're loading the game from a file, stop */
8958     if (whosays == GE_FILE) {
8959       (void) StopLoadGameTimer();
8960       gameFileFP = NULL;
8961     }
8962
8963     /* Cancel draw offers */
8964     first.offeredDraw = second.offeredDraw = 0;
8965
8966     /* If this is an ICS game, only ICS can really say it's done;
8967        if not, anyone can. */
8968     isIcsGame = (gameMode == IcsPlayingWhite || 
8969                  gameMode == IcsPlayingBlack || 
8970                  gameMode == IcsObserving    || 
8971                  gameMode == IcsExamining);
8972
8973     if (!isIcsGame || whosays == GE_ICS) {
8974         /* OK -- not an ICS game, or ICS said it was done */
8975         StopClocks();
8976         if (!isIcsGame && !appData.noChessProgram) 
8977           SetUserThinkingEnables();
8978     
8979         /* [HGM] if a machine claims the game end we verify this claim */
8980         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8981             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8982                 char claimer;
8983                 ChessMove trueResult = (ChessMove) -1;
8984
8985                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8986                                             first.twoMachinesColor[0] :
8987                                             second.twoMachinesColor[0] ;
8988
8989                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8990                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8991                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8992                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8993                 } else
8994                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8995                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8996                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8997                 } else
8998                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8999                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9000                 }
9001
9002                 // now verify win claims, but not in drop games, as we don't understand those yet
9003                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9004                                                  || gameInfo.variant == VariantGreat) &&
9005                     (result == WhiteWins && claimer == 'w' ||
9006                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9007                       if (appData.debugMode) {
9008                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9009                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9010                       }
9011                       if(result != trueResult) {
9012                               sprintf(buf, "False win claim: '%s'", resultDetails);
9013                               result = claimer == 'w' ? BlackWins : WhiteWins;
9014                               resultDetails = buf;
9015                       }
9016                 } else
9017                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9018                     && (forwardMostMove <= backwardMostMove ||
9019                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9020                         (claimer=='b')==(forwardMostMove&1))
9021                                                                                   ) {
9022                       /* [HGM] verify: draws that were not flagged are false claims */
9023                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9024                       result = claimer == 'w' ? BlackWins : WhiteWins;
9025                       resultDetails = buf;
9026                 }
9027                 /* (Claiming a loss is accepted no questions asked!) */
9028             }
9029             /* [HGM] bare: don't allow bare King to win */
9030             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9031                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9032                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9033                && result != GameIsDrawn)
9034             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9035                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9036                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9037                         if(p >= 0 && p <= (int)WhiteKing) k++;
9038                 }
9039                 if (appData.debugMode) {
9040                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9041                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9042                 }
9043                 if(k <= 1) {
9044                         result = GameIsDrawn;
9045                         sprintf(buf, "%s but bare king", resultDetails);
9046                         resultDetails = buf;
9047                 }
9048             }
9049         }
9050
9051
9052         if(serverMoves != NULL && !loadFlag) { char c = '=';
9053             if(result==WhiteWins) c = '+';
9054             if(result==BlackWins) c = '-';
9055             if(resultDetails != NULL)
9056                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9057         }
9058         if (resultDetails != NULL) {
9059             gameInfo.result = result;
9060             gameInfo.resultDetails = StrSave(resultDetails);
9061
9062             /* display last move only if game was not loaded from file */
9063             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9064                 DisplayMove(currentMove - 1);
9065     
9066             if (forwardMostMove != 0) {
9067                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9068                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9069                                                                 ) {
9070                     if (*appData.saveGameFile != NULLCHAR) {
9071                         SaveGameToFile(appData.saveGameFile, TRUE);
9072                     } else if (appData.autoSaveGames) {
9073                         AutoSaveGame();
9074                     }
9075                     if (*appData.savePositionFile != NULLCHAR) {
9076                         SavePositionToFile(appData.savePositionFile);
9077                     }
9078                 }
9079             }
9080
9081             /* Tell program how game ended in case it is learning */
9082             /* [HGM] Moved this to after saving the PGN, just in case */
9083             /* engine died and we got here through time loss. In that */
9084             /* case we will get a fatal error writing the pipe, which */
9085             /* would otherwise lose us the PGN.                       */
9086             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9087             /* output during GameEnds should never be fatal anymore   */
9088             if (gameMode == MachinePlaysWhite ||
9089                 gameMode == MachinePlaysBlack ||
9090                 gameMode == TwoMachinesPlay ||
9091                 gameMode == IcsPlayingWhite ||
9092                 gameMode == IcsPlayingBlack ||
9093                 gameMode == BeginningOfGame) {
9094                 char buf[MSG_SIZ];
9095                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9096                         resultDetails);
9097                 if (first.pr != NoProc) {
9098                     SendToProgram(buf, &first);
9099                 }
9100                 if (second.pr != NoProc &&
9101                     gameMode == TwoMachinesPlay) {
9102                     SendToProgram(buf, &second);
9103                 }
9104             }
9105         }
9106
9107         if (appData.icsActive) {
9108             if (appData.quietPlay &&
9109                 (gameMode == IcsPlayingWhite ||
9110                  gameMode == IcsPlayingBlack)) {
9111                 SendToICS(ics_prefix);
9112                 SendToICS("set shout 1\n");
9113             }
9114             nextGameMode = IcsIdle;
9115             ics_user_moved = FALSE;
9116             /* clean up premove.  It's ugly when the game has ended and the
9117              * premove highlights are still on the board.
9118              */
9119             if (gotPremove) {
9120               gotPremove = FALSE;
9121               ClearPremoveHighlights();
9122               DrawPosition(FALSE, boards[currentMove]);
9123             }
9124             if (whosays == GE_ICS) {
9125                 switch (result) {
9126                 case WhiteWins:
9127                     if (gameMode == IcsPlayingWhite)
9128                         PlayIcsWinSound();
9129                     else if(gameMode == IcsPlayingBlack)
9130                         PlayIcsLossSound();
9131                     break;
9132                 case BlackWins:
9133                     if (gameMode == IcsPlayingBlack)
9134                         PlayIcsWinSound();
9135                     else if(gameMode == IcsPlayingWhite)
9136                         PlayIcsLossSound();
9137                     break;
9138                 case GameIsDrawn:
9139                     PlayIcsDrawSound();
9140                     break;
9141                 default:
9142                     PlayIcsUnfinishedSound();
9143                 }
9144             }
9145         } else if (gameMode == EditGame ||
9146                    gameMode == PlayFromGameFile || 
9147                    gameMode == AnalyzeMode || 
9148                    gameMode == AnalyzeFile) {
9149             nextGameMode = gameMode;
9150         } else {
9151             nextGameMode = EndOfGame;
9152         }
9153         pausing = FALSE;
9154         ModeHighlight();
9155     } else {
9156         nextGameMode = gameMode;
9157     }
9158
9159     if (appData.noChessProgram) {
9160         gameMode = nextGameMode;
9161         ModeHighlight();
9162         endingGame = 0; /* [HGM] crash */
9163         return;
9164     }
9165
9166     if (first.reuse) {
9167         /* Put first chess program into idle state */
9168         if (first.pr != NoProc &&
9169             (gameMode == MachinePlaysWhite ||
9170              gameMode == MachinePlaysBlack ||
9171              gameMode == TwoMachinesPlay ||
9172              gameMode == IcsPlayingWhite ||
9173              gameMode == IcsPlayingBlack ||
9174              gameMode == BeginningOfGame)) {
9175             SendToProgram("force\n", &first);
9176             if (first.usePing) {
9177               char buf[MSG_SIZ];
9178               sprintf(buf, "ping %d\n", ++first.lastPing);
9179               SendToProgram(buf, &first);
9180             }
9181         }
9182     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9183         /* Kill off first chess program */
9184         if (first.isr != NULL)
9185           RemoveInputSource(first.isr);
9186         first.isr = NULL;
9187     
9188         if (first.pr != NoProc) {
9189             ExitAnalyzeMode();
9190             DoSleep( appData.delayBeforeQuit );
9191             SendToProgram("quit\n", &first);
9192             DoSleep( appData.delayAfterQuit );
9193             DestroyChildProcess(first.pr, first.useSigterm);
9194         }
9195         first.pr = NoProc;
9196     }
9197     if (second.reuse) {
9198         /* Put second chess program into idle state */
9199         if (second.pr != NoProc &&
9200             gameMode == TwoMachinesPlay) {
9201             SendToProgram("force\n", &second);
9202             if (second.usePing) {
9203               char buf[MSG_SIZ];
9204               sprintf(buf, "ping %d\n", ++second.lastPing);
9205               SendToProgram(buf, &second);
9206             }
9207         }
9208     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9209         /* Kill off second chess program */
9210         if (second.isr != NULL)
9211           RemoveInputSource(second.isr);
9212         second.isr = NULL;
9213     
9214         if (second.pr != NoProc) {
9215             DoSleep( appData.delayBeforeQuit );
9216             SendToProgram("quit\n", &second);
9217             DoSleep( appData.delayAfterQuit );
9218             DestroyChildProcess(second.pr, second.useSigterm);
9219         }
9220         second.pr = NoProc;
9221     }
9222
9223     if (matchMode && gameMode == TwoMachinesPlay) {
9224         switch (result) {
9225         case WhiteWins:
9226           if (first.twoMachinesColor[0] == 'w') {
9227             first.matchWins++;
9228           } else {
9229             second.matchWins++;
9230           }
9231           break;
9232         case BlackWins:
9233           if (first.twoMachinesColor[0] == 'b') {
9234             first.matchWins++;
9235           } else {
9236             second.matchWins++;
9237           }
9238           break;
9239         default:
9240           break;
9241         }
9242         if (matchGame < appData.matchGames) {
9243             char *tmp;
9244             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9245                 tmp = first.twoMachinesColor;
9246                 first.twoMachinesColor = second.twoMachinesColor;
9247                 second.twoMachinesColor = tmp;
9248             }
9249             gameMode = nextGameMode;
9250             matchGame++;
9251             if(appData.matchPause>10000 || appData.matchPause<10)
9252                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9253             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9254             endingGame = 0; /* [HGM] crash */
9255             return;
9256         } else {
9257             char buf[MSG_SIZ];
9258             gameMode = nextGameMode;
9259             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9260                     first.tidy, second.tidy,
9261                     first.matchWins, second.matchWins,
9262                     appData.matchGames - (first.matchWins + second.matchWins));
9263             DisplayFatalError(buf, 0, 0);
9264         }
9265     }
9266     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9267         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9268       ExitAnalyzeMode();
9269     gameMode = nextGameMode;
9270     ModeHighlight();
9271     endingGame = 0;  /* [HGM] crash */
9272 }
9273
9274 /* Assumes program was just initialized (initString sent).
9275    Leaves program in force mode. */
9276 void
9277 FeedMovesToProgram(cps, upto) 
9278      ChessProgramState *cps;
9279      int upto;
9280 {
9281     int i;
9282     
9283     if (appData.debugMode)
9284       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9285               startedFromSetupPosition ? "position and " : "",
9286               backwardMostMove, upto, cps->which);
9287     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9288         // [HGM] variantswitch: make engine aware of new variant
9289         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9290                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9291         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9292         SendToProgram(buf, cps);
9293         currentlyInitializedVariant = gameInfo.variant;
9294     }
9295     SendToProgram("force\n", cps);
9296     if (startedFromSetupPosition) {
9297         SendBoard(cps, backwardMostMove);
9298     if (appData.debugMode) {
9299         fprintf(debugFP, "feedMoves\n");
9300     }
9301     }
9302     for (i = backwardMostMove; i < upto; i++) {
9303         SendMoveToProgram(i, cps);
9304     }
9305 }
9306
9307
9308 void
9309 ResurrectChessProgram()
9310 {
9311      /* The chess program may have exited.
9312         If so, restart it and feed it all the moves made so far. */
9313
9314     if (appData.noChessProgram || first.pr != NoProc) return;
9315     
9316     StartChessProgram(&first);
9317     InitChessProgram(&first, FALSE);
9318     FeedMovesToProgram(&first, currentMove);
9319
9320     if (!first.sendTime) {
9321         /* can't tell gnuchess what its clock should read,
9322            so we bow to its notion. */
9323         ResetClocks();
9324         timeRemaining[0][currentMove] = whiteTimeRemaining;
9325         timeRemaining[1][currentMove] = blackTimeRemaining;
9326     }
9327
9328     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9329                 appData.icsEngineAnalyze) && first.analysisSupport) {
9330       SendToProgram("analyze\n", &first);
9331       first.analyzing = TRUE;
9332     }
9333 }
9334
9335 /*
9336  * Button procedures
9337  */
9338 void
9339 Reset(redraw, init)
9340      int redraw, init;
9341 {
9342     int i;
9343
9344     if (appData.debugMode) {
9345         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9346                 redraw, init, gameMode);
9347     }
9348     CleanupTail(); // [HGM] vari: delete any stored variations
9349     pausing = pauseExamInvalid = FALSE;
9350     startedFromSetupPosition = blackPlaysFirst = FALSE;
9351     firstMove = TRUE;
9352     whiteFlag = blackFlag = FALSE;
9353     userOfferedDraw = FALSE;
9354     hintRequested = bookRequested = FALSE;
9355     first.maybeThinking = FALSE;
9356     second.maybeThinking = FALSE;
9357     first.bookSuspend = FALSE; // [HGM] book
9358     second.bookSuspend = FALSE;
9359     thinkOutput[0] = NULLCHAR;
9360     lastHint[0] = NULLCHAR;
9361     ClearGameInfo(&gameInfo);
9362     gameInfo.variant = StringToVariant(appData.variant);
9363     ics_user_moved = ics_clock_paused = FALSE;
9364     ics_getting_history = H_FALSE;
9365     ics_gamenum = -1;
9366     white_holding[0] = black_holding[0] = NULLCHAR;
9367     ClearProgramStats();
9368     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9369     
9370     ResetFrontEnd();
9371     ClearHighlights();
9372     flipView = appData.flipView;
9373     ClearPremoveHighlights();
9374     gotPremove = FALSE;
9375     alarmSounded = FALSE;
9376
9377     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9378     if(appData.serverMovesName != NULL) {
9379         /* [HGM] prepare to make moves file for broadcasting */
9380         clock_t t = clock();
9381         if(serverMoves != NULL) fclose(serverMoves);
9382         serverMoves = fopen(appData.serverMovesName, "r");
9383         if(serverMoves != NULL) {
9384             fclose(serverMoves);
9385             /* delay 15 sec before overwriting, so all clients can see end */
9386             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9387         }
9388         serverMoves = fopen(appData.serverMovesName, "w");
9389     }
9390
9391     ExitAnalyzeMode();
9392     gameMode = BeginningOfGame;
9393     ModeHighlight();
9394     if(appData.icsActive) gameInfo.variant = VariantNormal;
9395     currentMove = forwardMostMove = backwardMostMove = 0;
9396     InitPosition(redraw);
9397     for (i = 0; i < MAX_MOVES; i++) {
9398         if (commentList[i] != NULL) {
9399             free(commentList[i]);
9400             commentList[i] = NULL;
9401         }
9402     }
9403     ResetClocks();
9404     timeRemaining[0][0] = whiteTimeRemaining;
9405     timeRemaining[1][0] = blackTimeRemaining;
9406     if (first.pr == NULL) {
9407         StartChessProgram(&first);
9408     }
9409     if (init) {
9410             InitChessProgram(&first, startedFromSetupPosition);
9411     }
9412     DisplayTitle("");
9413     DisplayMessage("", "");
9414     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9415     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9416 }
9417
9418 void
9419 AutoPlayGameLoop()
9420 {
9421     for (;;) {
9422         if (!AutoPlayOneMove())
9423           return;
9424         if (matchMode || appData.timeDelay == 0)
9425           continue;
9426         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9427           return;
9428         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9429         break;
9430     }
9431 }
9432
9433
9434 int
9435 AutoPlayOneMove()
9436 {
9437     int fromX, fromY, toX, toY;
9438
9439     if (appData.debugMode) {
9440       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9441     }
9442
9443     if (gameMode != PlayFromGameFile)
9444       return FALSE;
9445
9446     if (currentMove >= forwardMostMove) {
9447       gameMode = EditGame;
9448       ModeHighlight();
9449
9450       /* [AS] Clear current move marker at the end of a game */
9451       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9452
9453       return FALSE;
9454     }
9455     
9456     toX = moveList[currentMove][2] - AAA;
9457     toY = moveList[currentMove][3] - ONE;
9458
9459     if (moveList[currentMove][1] == '@') {
9460         if (appData.highlightLastMove) {
9461             SetHighlights(-1, -1, toX, toY);
9462         }
9463     } else {
9464         fromX = moveList[currentMove][0] - AAA;
9465         fromY = moveList[currentMove][1] - ONE;
9466
9467         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9468
9469         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9470
9471         if (appData.highlightLastMove) {
9472             SetHighlights(fromX, fromY, toX, toY);
9473         }
9474     }
9475     DisplayMove(currentMove);
9476     SendMoveToProgram(currentMove++, &first);
9477     DisplayBothClocks();
9478     DrawPosition(FALSE, boards[currentMove]);
9479     // [HGM] PV info: always display, routine tests if empty
9480     DisplayComment(currentMove - 1, commentList[currentMove]);
9481     return TRUE;
9482 }
9483
9484
9485 int
9486 LoadGameOneMove(readAhead)
9487      ChessMove readAhead;
9488 {
9489     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9490     char promoChar = NULLCHAR;
9491     ChessMove moveType;
9492     char move[MSG_SIZ];
9493     char *p, *q;
9494     
9495     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9496         gameMode != AnalyzeMode && gameMode != Training) {
9497         gameFileFP = NULL;
9498         return FALSE;
9499     }
9500     
9501     yyboardindex = forwardMostMove;
9502     if (readAhead != (ChessMove)0) {
9503       moveType = readAhead;
9504     } else {
9505       if (gameFileFP == NULL)
9506           return FALSE;
9507       moveType = (ChessMove) yylex();
9508     }
9509     
9510     done = FALSE;
9511     switch (moveType) {
9512       case Comment:
9513         if (appData.debugMode) 
9514           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9515         p = yy_text;
9516
9517         /* append the comment but don't display it */
9518         AppendComment(currentMove, p, FALSE);
9519         return TRUE;
9520
9521       case WhiteCapturesEnPassant:
9522       case BlackCapturesEnPassant:
9523       case WhitePromotionChancellor:
9524       case BlackPromotionChancellor:
9525       case WhitePromotionArchbishop:
9526       case BlackPromotionArchbishop:
9527       case WhitePromotionCentaur:
9528       case BlackPromotionCentaur:
9529       case WhitePromotionQueen:
9530       case BlackPromotionQueen:
9531       case WhitePromotionRook:
9532       case BlackPromotionRook:
9533       case WhitePromotionBishop:
9534       case BlackPromotionBishop:
9535       case WhitePromotionKnight:
9536       case BlackPromotionKnight:
9537       case WhitePromotionKing:
9538       case BlackPromotionKing:
9539       case NormalMove:
9540       case WhiteKingSideCastle:
9541       case WhiteQueenSideCastle:
9542       case BlackKingSideCastle:
9543       case BlackQueenSideCastle:
9544       case WhiteKingSideCastleWild:
9545       case WhiteQueenSideCastleWild:
9546       case BlackKingSideCastleWild:
9547       case BlackQueenSideCastleWild:
9548       /* PUSH Fabien */
9549       case WhiteHSideCastleFR:
9550       case WhiteASideCastleFR:
9551       case BlackHSideCastleFR:
9552       case BlackASideCastleFR:
9553       /* POP Fabien */
9554         if (appData.debugMode)
9555           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9556         fromX = currentMoveString[0] - AAA;
9557         fromY = currentMoveString[1] - ONE;
9558         toX = currentMoveString[2] - AAA;
9559         toY = currentMoveString[3] - ONE;
9560         promoChar = currentMoveString[4];
9561         break;
9562
9563       case WhiteDrop:
9564       case BlackDrop:
9565         if (appData.debugMode)
9566           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9567         fromX = moveType == WhiteDrop ?
9568           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9569         (int) CharToPiece(ToLower(currentMoveString[0]));
9570         fromY = DROP_RANK;
9571         toX = currentMoveString[2] - AAA;
9572         toY = currentMoveString[3] - ONE;
9573         break;
9574
9575       case WhiteWins:
9576       case BlackWins:
9577       case GameIsDrawn:
9578       case GameUnfinished:
9579         if (appData.debugMode)
9580           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9581         p = strchr(yy_text, '{');
9582         if (p == NULL) p = strchr(yy_text, '(');
9583         if (p == NULL) {
9584             p = yy_text;
9585             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9586         } else {
9587             q = strchr(p, *p == '{' ? '}' : ')');
9588             if (q != NULL) *q = NULLCHAR;
9589             p++;
9590         }
9591         GameEnds(moveType, p, GE_FILE);
9592         done = TRUE;
9593         if (cmailMsgLoaded) {
9594             ClearHighlights();
9595             flipView = WhiteOnMove(currentMove);
9596             if (moveType == GameUnfinished) flipView = !flipView;
9597             if (appData.debugMode)
9598               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9599         }
9600         break;
9601
9602       case (ChessMove) 0:       /* end of file */
9603         if (appData.debugMode)
9604           fprintf(debugFP, "Parser hit end of file\n");
9605         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9606           case MT_NONE:
9607           case MT_CHECK:
9608             break;
9609           case MT_CHECKMATE:
9610           case MT_STAINMATE:
9611             if (WhiteOnMove(currentMove)) {
9612                 GameEnds(BlackWins, "Black mates", GE_FILE);
9613             } else {
9614                 GameEnds(WhiteWins, "White mates", GE_FILE);
9615             }
9616             break;
9617           case MT_STALEMATE:
9618             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9619             break;
9620         }
9621         done = TRUE;
9622         break;
9623
9624       case MoveNumberOne:
9625         if (lastLoadGameStart == GNUChessGame) {
9626             /* GNUChessGames have numbers, but they aren't move numbers */
9627             if (appData.debugMode)
9628               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9629                       yy_text, (int) moveType);
9630             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9631         }
9632         /* else fall thru */
9633
9634       case XBoardGame:
9635       case GNUChessGame:
9636       case PGNTag:
9637         /* Reached start of next game in file */
9638         if (appData.debugMode)
9639           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9640         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9641           case MT_NONE:
9642           case MT_CHECK:
9643             break;
9644           case MT_CHECKMATE:
9645           case MT_STAINMATE:
9646             if (WhiteOnMove(currentMove)) {
9647                 GameEnds(BlackWins, "Black mates", GE_FILE);
9648             } else {
9649                 GameEnds(WhiteWins, "White mates", GE_FILE);
9650             }
9651             break;
9652           case MT_STALEMATE:
9653             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9654             break;
9655         }
9656         done = TRUE;
9657         break;
9658
9659       case PositionDiagram:     /* should not happen; ignore */
9660       case ElapsedTime:         /* ignore */
9661       case NAG:                 /* ignore */
9662         if (appData.debugMode)
9663           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9664                   yy_text, (int) moveType);
9665         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9666
9667       case IllegalMove:
9668         if (appData.testLegality) {
9669             if (appData.debugMode)
9670               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9671             sprintf(move, _("Illegal move: %d.%s%s"),
9672                     (forwardMostMove / 2) + 1,
9673                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9674             DisplayError(move, 0);
9675             done = TRUE;
9676         } else {
9677             if (appData.debugMode)
9678               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9679                       yy_text, currentMoveString);
9680             fromX = currentMoveString[0] - AAA;
9681             fromY = currentMoveString[1] - ONE;
9682             toX = currentMoveString[2] - AAA;
9683             toY = currentMoveString[3] - ONE;
9684             promoChar = currentMoveString[4];
9685         }
9686         break;
9687
9688       case AmbiguousMove:
9689         if (appData.debugMode)
9690           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9691         sprintf(move, _("Ambiguous move: %d.%s%s"),
9692                 (forwardMostMove / 2) + 1,
9693                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9694         DisplayError(move, 0);
9695         done = TRUE;
9696         break;
9697
9698       default:
9699       case ImpossibleMove:
9700         if (appData.debugMode)
9701           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9702         sprintf(move, _("Illegal move: %d.%s%s"),
9703                 (forwardMostMove / 2) + 1,
9704                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9705         DisplayError(move, 0);
9706         done = TRUE;
9707         break;
9708     }
9709
9710     if (done) {
9711         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9712             DrawPosition(FALSE, boards[currentMove]);
9713             DisplayBothClocks();
9714             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9715               DisplayComment(currentMove - 1, commentList[currentMove]);
9716         }
9717         (void) StopLoadGameTimer();
9718         gameFileFP = NULL;
9719         cmailOldMove = forwardMostMove;
9720         return FALSE;
9721     } else {
9722         /* currentMoveString is set as a side-effect of yylex */
9723         strcat(currentMoveString, "\n");
9724         strcpy(moveList[forwardMostMove], currentMoveString);
9725         
9726         thinkOutput[0] = NULLCHAR;
9727         MakeMove(fromX, fromY, toX, toY, promoChar);
9728         currentMove = forwardMostMove;
9729         return TRUE;
9730     }
9731 }
9732
9733 /* Load the nth game from the given file */
9734 int
9735 LoadGameFromFile(filename, n, title, useList)
9736      char *filename;
9737      int n;
9738      char *title;
9739      /*Boolean*/ int useList;
9740 {
9741     FILE *f;
9742     char buf[MSG_SIZ];
9743
9744     if (strcmp(filename, "-") == 0) {
9745         f = stdin;
9746         title = "stdin";
9747     } else {
9748         f = fopen(filename, "rb");
9749         if (f == NULL) {
9750           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9751             DisplayError(buf, errno);
9752             return FALSE;
9753         }
9754     }
9755     if (fseek(f, 0, 0) == -1) {
9756         /* f is not seekable; probably a pipe */
9757         useList = FALSE;
9758     }
9759     if (useList && n == 0) {
9760         int error = GameListBuild(f);
9761         if (error) {
9762             DisplayError(_("Cannot build game list"), error);
9763         } else if (!ListEmpty(&gameList) &&
9764                    ((ListGame *) gameList.tailPred)->number > 1) {
9765             GameListPopUp(f, title);
9766             return TRUE;
9767         }
9768         GameListDestroy();
9769         n = 1;
9770     }
9771     if (n == 0) n = 1;
9772     return LoadGame(f, n, title, FALSE);
9773 }
9774
9775
9776 void
9777 MakeRegisteredMove()
9778 {
9779     int fromX, fromY, toX, toY;
9780     char promoChar;
9781     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9782         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9783           case CMAIL_MOVE:
9784           case CMAIL_DRAW:
9785             if (appData.debugMode)
9786               fprintf(debugFP, "Restoring %s for game %d\n",
9787                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9788     
9789             thinkOutput[0] = NULLCHAR;
9790             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9791             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9792             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9793             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9794             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9795             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9796             MakeMove(fromX, fromY, toX, toY, promoChar);
9797             ShowMove(fromX, fromY, toX, toY);
9798               
9799             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9800               case MT_NONE:
9801               case MT_CHECK:
9802                 break;
9803                 
9804               case MT_CHECKMATE:
9805               case MT_STAINMATE:
9806                 if (WhiteOnMove(currentMove)) {
9807                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9808                 } else {
9809                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9810                 }
9811                 break;
9812                 
9813               case MT_STALEMATE:
9814                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9815                 break;
9816             }
9817
9818             break;
9819             
9820           case CMAIL_RESIGN:
9821             if (WhiteOnMove(currentMove)) {
9822                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9823             } else {
9824                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9825             }
9826             break;
9827             
9828           case CMAIL_ACCEPT:
9829             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9830             break;
9831               
9832           default:
9833             break;
9834         }
9835     }
9836
9837     return;
9838 }
9839
9840 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9841 int
9842 CmailLoadGame(f, gameNumber, title, useList)
9843      FILE *f;
9844      int gameNumber;
9845      char *title;
9846      int useList;
9847 {
9848     int retVal;
9849
9850     if (gameNumber > nCmailGames) {
9851         DisplayError(_("No more games in this message"), 0);
9852         return FALSE;
9853     }
9854     if (f == lastLoadGameFP) {
9855         int offset = gameNumber - lastLoadGameNumber;
9856         if (offset == 0) {
9857             cmailMsg[0] = NULLCHAR;
9858             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9859                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9860                 nCmailMovesRegistered--;
9861             }
9862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9863             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9864                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9865             }
9866         } else {
9867             if (! RegisterMove()) return FALSE;
9868         }
9869     }
9870
9871     retVal = LoadGame(f, gameNumber, title, useList);
9872
9873     /* Make move registered during previous look at this game, if any */
9874     MakeRegisteredMove();
9875
9876     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9877         commentList[currentMove]
9878           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9879         DisplayComment(currentMove - 1, commentList[currentMove]);
9880     }
9881
9882     return retVal;
9883 }
9884
9885 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9886 int
9887 ReloadGame(offset)
9888      int offset;
9889 {
9890     int gameNumber = lastLoadGameNumber + offset;
9891     if (lastLoadGameFP == NULL) {
9892         DisplayError(_("No game has been loaded yet"), 0);
9893         return FALSE;
9894     }
9895     if (gameNumber <= 0) {
9896         DisplayError(_("Can't back up any further"), 0);
9897         return FALSE;
9898     }
9899     if (cmailMsgLoaded) {
9900         return CmailLoadGame(lastLoadGameFP, gameNumber,
9901                              lastLoadGameTitle, lastLoadGameUseList);
9902     } else {
9903         return LoadGame(lastLoadGameFP, gameNumber,
9904                         lastLoadGameTitle, lastLoadGameUseList);
9905     }
9906 }
9907
9908
9909
9910 /* Load the nth game from open file f */
9911 int
9912 LoadGame(f, gameNumber, title, useList)
9913      FILE *f;
9914      int gameNumber;
9915      char *title;
9916      int useList;
9917 {
9918     ChessMove cm;
9919     char buf[MSG_SIZ];
9920     int gn = gameNumber;
9921     ListGame *lg = NULL;
9922     int numPGNTags = 0;
9923     int err;
9924     GameMode oldGameMode;
9925     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9926
9927     if (appData.debugMode) 
9928         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9929
9930     if (gameMode == Training )
9931         SetTrainingModeOff();
9932
9933     oldGameMode = gameMode;
9934     if (gameMode != BeginningOfGame) {
9935       Reset(FALSE, TRUE);
9936     }
9937
9938     gameFileFP = f;
9939     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9940         fclose(lastLoadGameFP);
9941     }
9942
9943     if (useList) {
9944         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9945         
9946         if (lg) {
9947             fseek(f, lg->offset, 0);
9948             GameListHighlight(gameNumber);
9949             gn = 1;
9950         }
9951         else {
9952             DisplayError(_("Game number out of range"), 0);
9953             return FALSE;
9954         }
9955     } else {
9956         GameListDestroy();
9957         if (fseek(f, 0, 0) == -1) {
9958             if (f == lastLoadGameFP ?
9959                 gameNumber == lastLoadGameNumber + 1 :
9960                 gameNumber == 1) {
9961                 gn = 1;
9962             } else {
9963                 DisplayError(_("Can't seek on game file"), 0);
9964                 return FALSE;
9965             }
9966         }
9967     }
9968     lastLoadGameFP = f;
9969     lastLoadGameNumber = gameNumber;
9970     strcpy(lastLoadGameTitle, title);
9971     lastLoadGameUseList = useList;
9972
9973     yynewfile(f);
9974
9975     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9976       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9977                 lg->gameInfo.black);
9978             DisplayTitle(buf);
9979     } else if (*title != NULLCHAR) {
9980         if (gameNumber > 1) {
9981             sprintf(buf, "%s %d", title, gameNumber);
9982             DisplayTitle(buf);
9983         } else {
9984             DisplayTitle(title);
9985         }
9986     }
9987
9988     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9989         gameMode = PlayFromGameFile;
9990         ModeHighlight();
9991     }
9992
9993     currentMove = forwardMostMove = backwardMostMove = 0;
9994     CopyBoard(boards[0], initialPosition);
9995     StopClocks();
9996
9997     /*
9998      * Skip the first gn-1 games in the file.
9999      * Also skip over anything that precedes an identifiable 
10000      * start of game marker, to avoid being confused by 
10001      * garbage at the start of the file.  Currently 
10002      * recognized start of game markers are the move number "1",
10003      * the pattern "gnuchess .* game", the pattern
10004      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10005      * A game that starts with one of the latter two patterns
10006      * will also have a move number 1, possibly
10007      * following a position diagram.
10008      * 5-4-02: Let's try being more lenient and allowing a game to
10009      * start with an unnumbered move.  Does that break anything?
10010      */
10011     cm = lastLoadGameStart = (ChessMove) 0;
10012     while (gn > 0) {
10013         yyboardindex = forwardMostMove;
10014         cm = (ChessMove) yylex();
10015         switch (cm) {
10016           case (ChessMove) 0:
10017             if (cmailMsgLoaded) {
10018                 nCmailGames = CMAIL_MAX_GAMES - gn;
10019             } else {
10020                 Reset(TRUE, TRUE);
10021                 DisplayError(_("Game not found in file"), 0);
10022             }
10023             return FALSE;
10024
10025           case GNUChessGame:
10026           case XBoardGame:
10027             gn--;
10028             lastLoadGameStart = cm;
10029             break;
10030             
10031           case MoveNumberOne:
10032             switch (lastLoadGameStart) {
10033               case GNUChessGame:
10034               case XBoardGame:
10035               case PGNTag:
10036                 break;
10037               case MoveNumberOne:
10038               case (ChessMove) 0:
10039                 gn--;           /* count this game */
10040                 lastLoadGameStart = cm;
10041                 break;
10042               default:
10043                 /* impossible */
10044                 break;
10045             }
10046             break;
10047
10048           case PGNTag:
10049             switch (lastLoadGameStart) {
10050               case GNUChessGame:
10051               case PGNTag:
10052               case MoveNumberOne:
10053               case (ChessMove) 0:
10054                 gn--;           /* count this game */
10055                 lastLoadGameStart = cm;
10056                 break;
10057               case XBoardGame:
10058                 lastLoadGameStart = cm; /* game counted already */
10059                 break;
10060               default:
10061                 /* impossible */
10062                 break;
10063             }
10064             if (gn > 0) {
10065                 do {
10066                     yyboardindex = forwardMostMove;
10067                     cm = (ChessMove) yylex();
10068                 } while (cm == PGNTag || cm == Comment);
10069             }
10070             break;
10071
10072           case WhiteWins:
10073           case BlackWins:
10074           case GameIsDrawn:
10075             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10076                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10077                     != CMAIL_OLD_RESULT) {
10078                     nCmailResults ++ ;
10079                     cmailResult[  CMAIL_MAX_GAMES
10080                                 - gn - 1] = CMAIL_OLD_RESULT;
10081                 }
10082             }
10083             break;
10084
10085           case NormalMove:
10086             /* Only a NormalMove can be at the start of a game
10087              * without a position diagram. */
10088             if (lastLoadGameStart == (ChessMove) 0) {
10089               gn--;
10090               lastLoadGameStart = MoveNumberOne;
10091             }
10092             break;
10093
10094           default:
10095             break;
10096         }
10097     }
10098     
10099     if (appData.debugMode)
10100       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10101
10102     if (cm == XBoardGame) {
10103         /* Skip any header junk before position diagram and/or move 1 */
10104         for (;;) {
10105             yyboardindex = forwardMostMove;
10106             cm = (ChessMove) yylex();
10107
10108             if (cm == (ChessMove) 0 ||
10109                 cm == GNUChessGame || cm == XBoardGame) {
10110                 /* Empty game; pretend end-of-file and handle later */
10111                 cm = (ChessMove) 0;
10112                 break;
10113             }
10114
10115             if (cm == MoveNumberOne || cm == PositionDiagram ||
10116                 cm == PGNTag || cm == Comment)
10117               break;
10118         }
10119     } else if (cm == GNUChessGame) {
10120         if (gameInfo.event != NULL) {
10121             free(gameInfo.event);
10122         }
10123         gameInfo.event = StrSave(yy_text);
10124     }   
10125
10126     startedFromSetupPosition = FALSE;
10127     while (cm == PGNTag) {
10128         if (appData.debugMode) 
10129           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10130         err = ParsePGNTag(yy_text, &gameInfo);
10131         if (!err) numPGNTags++;
10132
10133         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10134         if(gameInfo.variant != oldVariant) {
10135             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10136             InitPosition(TRUE);
10137             oldVariant = gameInfo.variant;
10138             if (appData.debugMode) 
10139               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10140         }
10141
10142
10143         if (gameInfo.fen != NULL) {
10144           Board initial_position;
10145           startedFromSetupPosition = TRUE;
10146           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10147             Reset(TRUE, TRUE);
10148             DisplayError(_("Bad FEN position in file"), 0);
10149             return FALSE;
10150           }
10151           CopyBoard(boards[0], initial_position);
10152           if (blackPlaysFirst) {
10153             currentMove = forwardMostMove = backwardMostMove = 1;
10154             CopyBoard(boards[1], initial_position);
10155             strcpy(moveList[0], "");
10156             strcpy(parseList[0], "");
10157             timeRemaining[0][1] = whiteTimeRemaining;
10158             timeRemaining[1][1] = blackTimeRemaining;
10159             if (commentList[0] != NULL) {
10160               commentList[1] = commentList[0];
10161               commentList[0] = NULL;
10162             }
10163           } else {
10164             currentMove = forwardMostMove = backwardMostMove = 0;
10165           }
10166           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10167           {   int i;
10168               initialRulePlies = FENrulePlies;
10169               for( i=0; i< nrCastlingRights; i++ )
10170                   initialRights[i] = initial_position[CASTLING][i];
10171           }
10172           yyboardindex = forwardMostMove;
10173           free(gameInfo.fen);
10174           gameInfo.fen = NULL;
10175         }
10176
10177         yyboardindex = forwardMostMove;
10178         cm = (ChessMove) yylex();
10179
10180         /* Handle comments interspersed among the tags */
10181         while (cm == Comment) {
10182             char *p;
10183             if (appData.debugMode) 
10184               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10185             p = yy_text;
10186             AppendComment(currentMove, p, FALSE);
10187             yyboardindex = forwardMostMove;
10188             cm = (ChessMove) yylex();
10189         }
10190     }
10191
10192     /* don't rely on existence of Event tag since if game was
10193      * pasted from clipboard the Event tag may not exist
10194      */
10195     if (numPGNTags > 0){
10196         char *tags;
10197         if (gameInfo.variant == VariantNormal) {
10198           VariantClass v = StringToVariant(gameInfo.event);
10199           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10200           if(v < VariantShogi) gameInfo.variant = v;
10201         }
10202         if (!matchMode) {
10203           if( appData.autoDisplayTags ) {
10204             tags = PGNTags(&gameInfo);
10205             TagsPopUp(tags, CmailMsg());
10206             free(tags);
10207           }
10208         }
10209     } else {
10210         /* Make something up, but don't display it now */
10211         SetGameInfo();
10212         TagsPopDown();
10213     }
10214
10215     if (cm == PositionDiagram) {
10216         int i, j;
10217         char *p;
10218         Board initial_position;
10219
10220         if (appData.debugMode)
10221           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10222
10223         if (!startedFromSetupPosition) {
10224             p = yy_text;
10225             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10226               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10227                 switch (*p) {
10228                   case '[':
10229                   case '-':
10230                   case ' ':
10231                   case '\t':
10232                   case '\n':
10233                   case '\r':
10234                     break;
10235                   default:
10236                     initial_position[i][j++] = CharToPiece(*p);
10237                     break;
10238                 }
10239             while (*p == ' ' || *p == '\t' ||
10240                    *p == '\n' || *p == '\r') p++;
10241         
10242             if (strncmp(p, "black", strlen("black"))==0)
10243               blackPlaysFirst = TRUE;
10244             else
10245               blackPlaysFirst = FALSE;
10246             startedFromSetupPosition = TRUE;
10247         
10248             CopyBoard(boards[0], initial_position);
10249             if (blackPlaysFirst) {
10250                 currentMove = forwardMostMove = backwardMostMove = 1;
10251                 CopyBoard(boards[1], initial_position);
10252                 strcpy(moveList[0], "");
10253                 strcpy(parseList[0], "");
10254                 timeRemaining[0][1] = whiteTimeRemaining;
10255                 timeRemaining[1][1] = blackTimeRemaining;
10256                 if (commentList[0] != NULL) {
10257                     commentList[1] = commentList[0];
10258                     commentList[0] = NULL;
10259                 }
10260             } else {
10261                 currentMove = forwardMostMove = backwardMostMove = 0;
10262             }
10263         }
10264         yyboardindex = forwardMostMove;
10265         cm = (ChessMove) yylex();
10266     }
10267
10268     if (first.pr == NoProc) {
10269         StartChessProgram(&first);
10270     }
10271     InitChessProgram(&first, FALSE);
10272     SendToProgram("force\n", &first);
10273     if (startedFromSetupPosition) {
10274         SendBoard(&first, forwardMostMove);
10275     if (appData.debugMode) {
10276         fprintf(debugFP, "Load Game\n");
10277     }
10278         DisplayBothClocks();
10279     }      
10280
10281     /* [HGM] server: flag to write setup moves in broadcast file as one */
10282     loadFlag = appData.suppressLoadMoves;
10283
10284     while (cm == Comment) {
10285         char *p;
10286         if (appData.debugMode) 
10287           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10288         p = yy_text;
10289         AppendComment(currentMove, p, FALSE);
10290         yyboardindex = forwardMostMove;
10291         cm = (ChessMove) yylex();
10292     }
10293
10294     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10295         cm == WhiteWins || cm == BlackWins ||
10296         cm == GameIsDrawn || cm == GameUnfinished) {
10297         DisplayMessage("", _("No moves in game"));
10298         if (cmailMsgLoaded) {
10299             if (appData.debugMode)
10300               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10301             ClearHighlights();
10302             flipView = FALSE;
10303         }
10304         DrawPosition(FALSE, boards[currentMove]);
10305         DisplayBothClocks();
10306         gameMode = EditGame;
10307         ModeHighlight();
10308         gameFileFP = NULL;
10309         cmailOldMove = 0;
10310         return TRUE;
10311     }
10312
10313     // [HGM] PV info: routine tests if comment empty
10314     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10315         DisplayComment(currentMove - 1, commentList[currentMove]);
10316     }
10317     if (!matchMode && appData.timeDelay != 0) 
10318       DrawPosition(FALSE, boards[currentMove]);
10319
10320     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10321       programStats.ok_to_send = 1;
10322     }
10323
10324     /* if the first token after the PGN tags is a move
10325      * and not move number 1, retrieve it from the parser 
10326      */
10327     if (cm != MoveNumberOne)
10328         LoadGameOneMove(cm);
10329
10330     /* load the remaining moves from the file */
10331     while (LoadGameOneMove((ChessMove)0)) {
10332       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10333       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10334     }
10335
10336     /* rewind to the start of the game */
10337     currentMove = backwardMostMove;
10338
10339     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10340
10341     if (oldGameMode == AnalyzeFile ||
10342         oldGameMode == AnalyzeMode) {
10343       AnalyzeFileEvent();
10344     }
10345
10346     if (matchMode || appData.timeDelay == 0) {
10347       ToEndEvent();
10348       gameMode = EditGame;
10349       ModeHighlight();
10350     } else if (appData.timeDelay > 0) {
10351       AutoPlayGameLoop();
10352     }
10353
10354     if (appData.debugMode) 
10355         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10356
10357     loadFlag = 0; /* [HGM] true game starts */
10358     return TRUE;
10359 }
10360
10361 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10362 int
10363 ReloadPosition(offset)
10364      int offset;
10365 {
10366     int positionNumber = lastLoadPositionNumber + offset;
10367     if (lastLoadPositionFP == NULL) {
10368         DisplayError(_("No position has been loaded yet"), 0);
10369         return FALSE;
10370     }
10371     if (positionNumber <= 0) {
10372         DisplayError(_("Can't back up any further"), 0);
10373         return FALSE;
10374     }
10375     return LoadPosition(lastLoadPositionFP, positionNumber,
10376                         lastLoadPositionTitle);
10377 }
10378
10379 /* Load the nth position from the given file */
10380 int
10381 LoadPositionFromFile(filename, n, title)
10382      char *filename;
10383      int n;
10384      char *title;
10385 {
10386     FILE *f;
10387     char buf[MSG_SIZ];
10388
10389     if (strcmp(filename, "-") == 0) {
10390         return LoadPosition(stdin, n, "stdin");
10391     } else {
10392         f = fopen(filename, "rb");
10393         if (f == NULL) {
10394             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10395             DisplayError(buf, errno);
10396             return FALSE;
10397         } else {
10398             return LoadPosition(f, n, title);
10399         }
10400     }
10401 }
10402
10403 /* Load the nth position from the given open file, and close it */
10404 int
10405 LoadPosition(f, positionNumber, title)
10406      FILE *f;
10407      int positionNumber;
10408      char *title;
10409 {
10410     char *p, line[MSG_SIZ];
10411     Board initial_position;
10412     int i, j, fenMode, pn;
10413     
10414     if (gameMode == Training )
10415         SetTrainingModeOff();
10416
10417     if (gameMode != BeginningOfGame) {
10418         Reset(FALSE, TRUE);
10419     }
10420     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10421         fclose(lastLoadPositionFP);
10422     }
10423     if (positionNumber == 0) positionNumber = 1;
10424     lastLoadPositionFP = f;
10425     lastLoadPositionNumber = positionNumber;
10426     strcpy(lastLoadPositionTitle, title);
10427     if (first.pr == NoProc) {
10428       StartChessProgram(&first);
10429       InitChessProgram(&first, FALSE);
10430     }    
10431     pn = positionNumber;
10432     if (positionNumber < 0) {
10433         /* Negative position number means to seek to that byte offset */
10434         if (fseek(f, -positionNumber, 0) == -1) {
10435             DisplayError(_("Can't seek on position file"), 0);
10436             return FALSE;
10437         };
10438         pn = 1;
10439     } else {
10440         if (fseek(f, 0, 0) == -1) {
10441             if (f == lastLoadPositionFP ?
10442                 positionNumber == lastLoadPositionNumber + 1 :
10443                 positionNumber == 1) {
10444                 pn = 1;
10445             } else {
10446                 DisplayError(_("Can't seek on position file"), 0);
10447                 return FALSE;
10448             }
10449         }
10450     }
10451     /* See if this file is FEN or old-style xboard */
10452     if (fgets(line, MSG_SIZ, f) == NULL) {
10453         DisplayError(_("Position not found in file"), 0);
10454         return FALSE;
10455     }
10456     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10457     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10458
10459     if (pn >= 2) {
10460         if (fenMode || line[0] == '#') pn--;
10461         while (pn > 0) {
10462             /* skip positions before number pn */
10463             if (fgets(line, MSG_SIZ, f) == NULL) {
10464                 Reset(TRUE, TRUE);
10465                 DisplayError(_("Position not found in file"), 0);
10466                 return FALSE;
10467             }
10468             if (fenMode || line[0] == '#') pn--;
10469         }
10470     }
10471
10472     if (fenMode) {
10473         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10474             DisplayError(_("Bad FEN position in file"), 0);
10475             return FALSE;
10476         }
10477     } else {
10478         (void) fgets(line, MSG_SIZ, f);
10479         (void) fgets(line, MSG_SIZ, f);
10480     
10481         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10482             (void) fgets(line, MSG_SIZ, f);
10483             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10484                 if (*p == ' ')
10485                   continue;
10486                 initial_position[i][j++] = CharToPiece(*p);
10487             }
10488         }
10489     
10490         blackPlaysFirst = FALSE;
10491         if (!feof(f)) {
10492             (void) fgets(line, MSG_SIZ, f);
10493             if (strncmp(line, "black", strlen("black"))==0)
10494               blackPlaysFirst = TRUE;
10495         }
10496     }
10497     startedFromSetupPosition = TRUE;
10498     
10499     SendToProgram("force\n", &first);
10500     CopyBoard(boards[0], initial_position);
10501     if (blackPlaysFirst) {
10502         currentMove = forwardMostMove = backwardMostMove = 1;
10503         strcpy(moveList[0], "");
10504         strcpy(parseList[0], "");
10505         CopyBoard(boards[1], initial_position);
10506         DisplayMessage("", _("Black to play"));
10507     } else {
10508         currentMove = forwardMostMove = backwardMostMove = 0;
10509         DisplayMessage("", _("White to play"));
10510     }
10511     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10512     SendBoard(&first, forwardMostMove);
10513     if (appData.debugMode) {
10514 int i, j;
10515   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10516   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10517         fprintf(debugFP, "Load Position\n");
10518     }
10519
10520     if (positionNumber > 1) {
10521         sprintf(line, "%s %d", title, positionNumber);
10522         DisplayTitle(line);
10523     } else {
10524         DisplayTitle(title);
10525     }
10526     gameMode = EditGame;
10527     ModeHighlight();
10528     ResetClocks();
10529     timeRemaining[0][1] = whiteTimeRemaining;
10530     timeRemaining[1][1] = blackTimeRemaining;
10531     DrawPosition(FALSE, boards[currentMove]);
10532    
10533     return TRUE;
10534 }
10535
10536
10537 void
10538 CopyPlayerNameIntoFileName(dest, src)
10539      char **dest, *src;
10540 {
10541     while (*src != NULLCHAR && *src != ',') {
10542         if (*src == ' ') {
10543             *(*dest)++ = '_';
10544             src++;
10545         } else {
10546             *(*dest)++ = *src++;
10547         }
10548     }
10549 }
10550
10551 char *DefaultFileName(ext)
10552      char *ext;
10553 {
10554     static char def[MSG_SIZ];
10555     char *p;
10556
10557     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10558         p = def;
10559         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10560         *p++ = '-';
10561         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10562         *p++ = '.';
10563         strcpy(p, ext);
10564     } else {
10565         def[0] = NULLCHAR;
10566     }
10567     return def;
10568 }
10569
10570 /* Save the current game to the given file */
10571 int
10572 SaveGameToFile(filename, append)
10573      char *filename;
10574      int append;
10575 {
10576     FILE *f;
10577     char buf[MSG_SIZ];
10578
10579     if (strcmp(filename, "-") == 0) {
10580         return SaveGame(stdout, 0, NULL);
10581     } else {
10582         f = fopen(filename, append ? "a" : "w");
10583         if (f == NULL) {
10584             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10585             DisplayError(buf, errno);
10586             return FALSE;
10587         } else {
10588             return SaveGame(f, 0, NULL);
10589         }
10590     }
10591 }
10592
10593 char *
10594 SavePart(str)
10595      char *str;
10596 {
10597     static char buf[MSG_SIZ];
10598     char *p;
10599     
10600     p = strchr(str, ' ');
10601     if (p == NULL) return str;
10602     strncpy(buf, str, p - str);
10603     buf[p - str] = NULLCHAR;
10604     return buf;
10605 }
10606
10607 #define PGN_MAX_LINE 75
10608
10609 #define PGN_SIDE_WHITE  0
10610 #define PGN_SIDE_BLACK  1
10611
10612 /* [AS] */
10613 static int FindFirstMoveOutOfBook( int side )
10614 {
10615     int result = -1;
10616
10617     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10618         int index = backwardMostMove;
10619         int has_book_hit = 0;
10620
10621         if( (index % 2) != side ) {
10622             index++;
10623         }
10624
10625         while( index < forwardMostMove ) {
10626             /* Check to see if engine is in book */
10627             int depth = pvInfoList[index].depth;
10628             int score = pvInfoList[index].score;
10629             int in_book = 0;
10630
10631             if( depth <= 2 ) {
10632                 in_book = 1;
10633             }
10634             else if( score == 0 && depth == 63 ) {
10635                 in_book = 1; /* Zappa */
10636             }
10637             else if( score == 2 && depth == 99 ) {
10638                 in_book = 1; /* Abrok */
10639             }
10640
10641             has_book_hit += in_book;
10642
10643             if( ! in_book ) {
10644                 result = index;
10645
10646                 break;
10647             }
10648
10649             index += 2;
10650         }
10651     }
10652
10653     return result;
10654 }
10655
10656 /* [AS] */
10657 void GetOutOfBookInfo( char * buf )
10658 {
10659     int oob[2];
10660     int i;
10661     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10662
10663     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10664     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10665
10666     *buf = '\0';
10667
10668     if( oob[0] >= 0 || oob[1] >= 0 ) {
10669         for( i=0; i<2; i++ ) {
10670             int idx = oob[i];
10671
10672             if( idx >= 0 ) {
10673                 if( i > 0 && oob[0] >= 0 ) {
10674                     strcat( buf, "   " );
10675                 }
10676
10677                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10678                 sprintf( buf+strlen(buf), "%s%.2f", 
10679                     pvInfoList[idx].score >= 0 ? "+" : "",
10680                     pvInfoList[idx].score / 100.0 );
10681             }
10682         }
10683     }
10684 }
10685
10686 /* Save game in PGN style and close the file */
10687 int
10688 SaveGamePGN(f)
10689      FILE *f;
10690 {
10691     int i, offset, linelen, newblock;
10692     time_t tm;
10693 //    char *movetext;
10694     char numtext[32];
10695     int movelen, numlen, blank;
10696     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10697
10698     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10699     
10700     tm = time((time_t *) NULL);
10701     
10702     PrintPGNTags(f, &gameInfo);
10703     
10704     if (backwardMostMove > 0 || startedFromSetupPosition) {
10705         char *fen = PositionToFEN(backwardMostMove, NULL);
10706         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10707         fprintf(f, "\n{--------------\n");
10708         PrintPosition(f, backwardMostMove);
10709         fprintf(f, "--------------}\n");
10710         free(fen);
10711     }
10712     else {
10713         /* [AS] Out of book annotation */
10714         if( appData.saveOutOfBookInfo ) {
10715             char buf[64];
10716
10717             GetOutOfBookInfo( buf );
10718
10719             if( buf[0] != '\0' ) {
10720                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10721             }
10722         }
10723
10724         fprintf(f, "\n");
10725     }
10726
10727     i = backwardMostMove;
10728     linelen = 0;
10729     newblock = TRUE;
10730
10731     while (i < forwardMostMove) {
10732         /* Print comments preceding this move */
10733         if (commentList[i] != NULL) {
10734             if (linelen > 0) fprintf(f, "\n");
10735             fprintf(f, "%s", commentList[i]);
10736             linelen = 0;
10737             newblock = TRUE;
10738         }
10739
10740         /* Format move number */
10741         if ((i % 2) == 0) {
10742             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10743         } else {
10744             if (newblock) {
10745                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10746             } else {
10747                 numtext[0] = NULLCHAR;
10748             }
10749         }
10750         numlen = strlen(numtext);
10751         newblock = FALSE;
10752
10753         /* Print move number */
10754         blank = linelen > 0 && numlen > 0;
10755         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10756             fprintf(f, "\n");
10757             linelen = 0;
10758             blank = 0;
10759         }
10760         if (blank) {
10761             fprintf(f, " ");
10762             linelen++;
10763         }
10764         fprintf(f, "%s", numtext);
10765         linelen += numlen;
10766
10767         /* Get move */
10768         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10769         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10770
10771         /* Print move */
10772         blank = linelen > 0 && movelen > 0;
10773         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10774             fprintf(f, "\n");
10775             linelen = 0;
10776             blank = 0;
10777         }
10778         if (blank) {
10779             fprintf(f, " ");
10780             linelen++;
10781         }
10782         fprintf(f, "%s", move_buffer);
10783         linelen += movelen;
10784
10785         /* [AS] Add PV info if present */
10786         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10787             /* [HGM] add time */
10788             char buf[MSG_SIZ]; int seconds;
10789
10790             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10791
10792             if( seconds <= 0) buf[0] = 0; else
10793             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10794                 seconds = (seconds + 4)/10; // round to full seconds
10795                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10796                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10797             }
10798
10799             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10800                 pvInfoList[i].score >= 0 ? "+" : "",
10801                 pvInfoList[i].score / 100.0,
10802                 pvInfoList[i].depth,
10803                 buf );
10804
10805             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10806
10807             /* Print score/depth */
10808             blank = linelen > 0 && movelen > 0;
10809             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10810                 fprintf(f, "\n");
10811                 linelen = 0;
10812                 blank = 0;
10813             }
10814             if (blank) {
10815                 fprintf(f, " ");
10816                 linelen++;
10817             }
10818             fprintf(f, "%s", move_buffer);
10819             linelen += movelen;
10820         }
10821
10822         i++;
10823     }
10824     
10825     /* Start a new line */
10826     if (linelen > 0) fprintf(f, "\n");
10827
10828     /* Print comments after last move */
10829     if (commentList[i] != NULL) {
10830         fprintf(f, "%s\n", commentList[i]);
10831     }
10832
10833     /* Print result */
10834     if (gameInfo.resultDetails != NULL &&
10835         gameInfo.resultDetails[0] != NULLCHAR) {
10836         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10837                 PGNResult(gameInfo.result));
10838     } else {
10839         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10840     }
10841
10842     fclose(f);
10843     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10844     return TRUE;
10845 }
10846
10847 /* Save game in old style and close the file */
10848 int
10849 SaveGameOldStyle(f)
10850      FILE *f;
10851 {
10852     int i, offset;
10853     time_t tm;
10854     
10855     tm = time((time_t *) NULL);
10856     
10857     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10858     PrintOpponents(f);
10859     
10860     if (backwardMostMove > 0 || startedFromSetupPosition) {
10861         fprintf(f, "\n[--------------\n");
10862         PrintPosition(f, backwardMostMove);
10863         fprintf(f, "--------------]\n");
10864     } else {
10865         fprintf(f, "\n");
10866     }
10867
10868     i = backwardMostMove;
10869     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10870
10871     while (i < forwardMostMove) {
10872         if (commentList[i] != NULL) {
10873             fprintf(f, "[%s]\n", commentList[i]);
10874         }
10875
10876         if ((i % 2) == 1) {
10877             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10878             i++;
10879         } else {
10880             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10881             i++;
10882             if (commentList[i] != NULL) {
10883                 fprintf(f, "\n");
10884                 continue;
10885             }
10886             if (i >= forwardMostMove) {
10887                 fprintf(f, "\n");
10888                 break;
10889             }
10890             fprintf(f, "%s\n", parseList[i]);
10891             i++;
10892         }
10893     }
10894     
10895     if (commentList[i] != NULL) {
10896         fprintf(f, "[%s]\n", commentList[i]);
10897     }
10898
10899     /* This isn't really the old style, but it's close enough */
10900     if (gameInfo.resultDetails != NULL &&
10901         gameInfo.resultDetails[0] != NULLCHAR) {
10902         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10903                 gameInfo.resultDetails);
10904     } else {
10905         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10906     }
10907
10908     fclose(f);
10909     return TRUE;
10910 }
10911
10912 /* Save the current game to open file f and close the file */
10913 int
10914 SaveGame(f, dummy, dummy2)
10915      FILE *f;
10916      int dummy;
10917      char *dummy2;
10918 {
10919     if (gameMode == EditPosition) EditPositionDone(TRUE);
10920     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10921     if (appData.oldSaveStyle)
10922       return SaveGameOldStyle(f);
10923     else
10924       return SaveGamePGN(f);
10925 }
10926
10927 /* Save the current position to the given file */
10928 int
10929 SavePositionToFile(filename)
10930      char *filename;
10931 {
10932     FILE *f;
10933     char buf[MSG_SIZ];
10934
10935     if (strcmp(filename, "-") == 0) {
10936         return SavePosition(stdout, 0, NULL);
10937     } else {
10938         f = fopen(filename, "a");
10939         if (f == NULL) {
10940             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10941             DisplayError(buf, errno);
10942             return FALSE;
10943         } else {
10944             SavePosition(f, 0, NULL);
10945             return TRUE;
10946         }
10947     }
10948 }
10949
10950 /* Save the current position to the given open file and close the file */
10951 int
10952 SavePosition(f, dummy, dummy2)
10953      FILE *f;
10954      int dummy;
10955      char *dummy2;
10956 {
10957     time_t tm;
10958     char *fen;
10959     
10960     if (gameMode == EditPosition) EditPositionDone(TRUE);
10961     if (appData.oldSaveStyle) {
10962         tm = time((time_t *) NULL);
10963     
10964         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10965         PrintOpponents(f);
10966         fprintf(f, "[--------------\n");
10967         PrintPosition(f, currentMove);
10968         fprintf(f, "--------------]\n");
10969     } else {
10970         fen = PositionToFEN(currentMove, NULL);
10971         fprintf(f, "%s\n", fen);
10972         free(fen);
10973     }
10974     fclose(f);
10975     return TRUE;
10976 }
10977
10978 void
10979 ReloadCmailMsgEvent(unregister)
10980      int unregister;
10981 {
10982 #if !WIN32
10983     static char *inFilename = NULL;
10984     static char *outFilename;
10985     int i;
10986     struct stat inbuf, outbuf;
10987     int status;
10988     
10989     /* Any registered moves are unregistered if unregister is set, */
10990     /* i.e. invoked by the signal handler */
10991     if (unregister) {
10992         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10993             cmailMoveRegistered[i] = FALSE;
10994             if (cmailCommentList[i] != NULL) {
10995                 free(cmailCommentList[i]);
10996                 cmailCommentList[i] = NULL;
10997             }
10998         }
10999         nCmailMovesRegistered = 0;
11000     }
11001
11002     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11003         cmailResult[i] = CMAIL_NOT_RESULT;
11004     }
11005     nCmailResults = 0;
11006
11007     if (inFilename == NULL) {
11008         /* Because the filenames are static they only get malloced once  */
11009         /* and they never get freed                                      */
11010         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11011         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11012
11013         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11014         sprintf(outFilename, "%s.out", appData.cmailGameName);
11015     }
11016     
11017     status = stat(outFilename, &outbuf);
11018     if (status < 0) {
11019         cmailMailedMove = FALSE;
11020     } else {
11021         status = stat(inFilename, &inbuf);
11022         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11023     }
11024     
11025     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11026        counts the games, notes how each one terminated, etc.
11027        
11028        It would be nice to remove this kludge and instead gather all
11029        the information while building the game list.  (And to keep it
11030        in the game list nodes instead of having a bunch of fixed-size
11031        parallel arrays.)  Note this will require getting each game's
11032        termination from the PGN tags, as the game list builder does
11033        not process the game moves.  --mann
11034        */
11035     cmailMsgLoaded = TRUE;
11036     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11037     
11038     /* Load first game in the file or popup game menu */
11039     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11040
11041 #endif /* !WIN32 */
11042     return;
11043 }
11044
11045 int
11046 RegisterMove()
11047 {
11048     FILE *f;
11049     char string[MSG_SIZ];
11050
11051     if (   cmailMailedMove
11052         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11053         return TRUE;            /* Allow free viewing  */
11054     }
11055
11056     /* Unregister move to ensure that we don't leave RegisterMove        */
11057     /* with the move registered when the conditions for registering no   */
11058     /* longer hold                                                       */
11059     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11060         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11061         nCmailMovesRegistered --;
11062
11063         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11064           {
11065               free(cmailCommentList[lastLoadGameNumber - 1]);
11066               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11067           }
11068     }
11069
11070     if (cmailOldMove == -1) {
11071         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11072         return FALSE;
11073     }
11074
11075     if (currentMove > cmailOldMove + 1) {
11076         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11077         return FALSE;
11078     }
11079
11080     if (currentMove < cmailOldMove) {
11081         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11082         return FALSE;
11083     }
11084
11085     if (forwardMostMove > currentMove) {
11086         /* Silently truncate extra moves */
11087         TruncateGame();
11088     }
11089
11090     if (   (currentMove == cmailOldMove + 1)
11091         || (   (currentMove == cmailOldMove)
11092             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11093                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11094         if (gameInfo.result != GameUnfinished) {
11095             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11096         }
11097
11098         if (commentList[currentMove] != NULL) {
11099             cmailCommentList[lastLoadGameNumber - 1]
11100               = StrSave(commentList[currentMove]);
11101         }
11102         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11103
11104         if (appData.debugMode)
11105           fprintf(debugFP, "Saving %s for game %d\n",
11106                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11107
11108         sprintf(string,
11109                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11110         
11111         f = fopen(string, "w");
11112         if (appData.oldSaveStyle) {
11113             SaveGameOldStyle(f); /* also closes the file */
11114             
11115             sprintf(string, "%s.pos.out", appData.cmailGameName);
11116             f = fopen(string, "w");
11117             SavePosition(f, 0, NULL); /* also closes the file */
11118         } else {
11119             fprintf(f, "{--------------\n");
11120             PrintPosition(f, currentMove);
11121             fprintf(f, "--------------}\n\n");
11122             
11123             SaveGame(f, 0, NULL); /* also closes the file*/
11124         }
11125         
11126         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11127         nCmailMovesRegistered ++;
11128     } else if (nCmailGames == 1) {
11129         DisplayError(_("You have not made a move yet"), 0);
11130         return FALSE;
11131     }
11132
11133     return TRUE;
11134 }
11135
11136 void
11137 MailMoveEvent()
11138 {
11139 #if !WIN32
11140     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11141     FILE *commandOutput;
11142     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11143     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11144     int nBuffers;
11145     int i;
11146     int archived;
11147     char *arcDir;
11148
11149     if (! cmailMsgLoaded) {
11150         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11151         return;
11152     }
11153
11154     if (nCmailGames == nCmailResults) {
11155         DisplayError(_("No unfinished games"), 0);
11156         return;
11157     }
11158
11159 #if CMAIL_PROHIBIT_REMAIL
11160     if (cmailMailedMove) {
11161         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);
11162         DisplayError(msg, 0);
11163         return;
11164     }
11165 #endif
11166
11167     if (! (cmailMailedMove || RegisterMove())) return;
11168     
11169     if (   cmailMailedMove
11170         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11171         sprintf(string, partCommandString,
11172                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11173         commandOutput = popen(string, "r");
11174
11175         if (commandOutput == NULL) {
11176             DisplayError(_("Failed to invoke cmail"), 0);
11177         } else {
11178             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11179                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11180             }
11181             if (nBuffers > 1) {
11182                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11183                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11184                 nBytes = MSG_SIZ - 1;
11185             } else {
11186                 (void) memcpy(msg, buffer, nBytes);
11187             }
11188             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11189
11190             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11191                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11192
11193                 archived = TRUE;
11194                 for (i = 0; i < nCmailGames; i ++) {
11195                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11196                         archived = FALSE;
11197                     }
11198                 }
11199                 if (   archived
11200                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11201                         != NULL)) {
11202                     sprintf(buffer, "%s/%s.%s.archive",
11203                             arcDir,
11204                             appData.cmailGameName,
11205                             gameInfo.date);
11206                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11207                     cmailMsgLoaded = FALSE;
11208                 }
11209             }
11210
11211             DisplayInformation(msg);
11212             pclose(commandOutput);
11213         }
11214     } else {
11215         if ((*cmailMsg) != '\0') {
11216             DisplayInformation(cmailMsg);
11217         }
11218     }
11219
11220     return;
11221 #endif /* !WIN32 */
11222 }
11223
11224 char *
11225 CmailMsg()
11226 {
11227 #if WIN32
11228     return NULL;
11229 #else
11230     int  prependComma = 0;
11231     char number[5];
11232     char string[MSG_SIZ];       /* Space for game-list */
11233     int  i;
11234     
11235     if (!cmailMsgLoaded) return "";
11236
11237     if (cmailMailedMove) {
11238         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11239     } else {
11240         /* Create a list of games left */
11241         sprintf(string, "[");
11242         for (i = 0; i < nCmailGames; i ++) {
11243             if (! (   cmailMoveRegistered[i]
11244                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11245                 if (prependComma) {
11246                     sprintf(number, ",%d", i + 1);
11247                 } else {
11248                     sprintf(number, "%d", i + 1);
11249                     prependComma = 1;
11250                 }
11251                 
11252                 strcat(string, number);
11253             }
11254         }
11255         strcat(string, "]");
11256
11257         if (nCmailMovesRegistered + nCmailResults == 0) {
11258             switch (nCmailGames) {
11259               case 1:
11260                 sprintf(cmailMsg,
11261                         _("Still need to make move for game\n"));
11262                 break;
11263                 
11264               case 2:
11265                 sprintf(cmailMsg,
11266                         _("Still need to make moves for both games\n"));
11267                 break;
11268                 
11269               default:
11270                 sprintf(cmailMsg,
11271                         _("Still need to make moves for all %d games\n"),
11272                         nCmailGames);
11273                 break;
11274             }
11275         } else {
11276             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11277               case 1:
11278                 sprintf(cmailMsg,
11279                         _("Still need to make a move for game %s\n"),
11280                         string);
11281                 break;
11282                 
11283               case 0:
11284                 if (nCmailResults == nCmailGames) {
11285                     sprintf(cmailMsg, _("No unfinished games\n"));
11286                 } else {
11287                     sprintf(cmailMsg, _("Ready to send mail\n"));
11288                 }
11289                 break;
11290                 
11291               default:
11292                 sprintf(cmailMsg,
11293                         _("Still need to make moves for games %s\n"),
11294                         string);
11295             }
11296         }
11297     }
11298     return cmailMsg;
11299 #endif /* WIN32 */
11300 }
11301
11302 void
11303 ResetGameEvent()
11304 {
11305     if (gameMode == Training)
11306       SetTrainingModeOff();
11307
11308     Reset(TRUE, TRUE);
11309     cmailMsgLoaded = FALSE;
11310     if (appData.icsActive) {
11311       SendToICS(ics_prefix);
11312       SendToICS("refresh\n");
11313     }
11314 }
11315
11316 void
11317 ExitEvent(status)
11318      int status;
11319 {
11320     exiting++;
11321     if (exiting > 2) {
11322       /* Give up on clean exit */
11323       exit(status);
11324     }
11325     if (exiting > 1) {
11326       /* Keep trying for clean exit */
11327       return;
11328     }
11329
11330     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11331
11332     if (telnetISR != NULL) {
11333       RemoveInputSource(telnetISR);
11334     }
11335     if (icsPR != NoProc) {
11336       DestroyChildProcess(icsPR, TRUE);
11337     }
11338
11339     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11340     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11341
11342     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11343     /* make sure this other one finishes before killing it!                  */
11344     if(endingGame) { int count = 0;
11345         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11346         while(endingGame && count++ < 10) DoSleep(1);
11347         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11348     }
11349
11350     /* Kill off chess programs */
11351     if (first.pr != NoProc) {
11352         ExitAnalyzeMode();
11353         
11354         DoSleep( appData.delayBeforeQuit );
11355         SendToProgram("quit\n", &first);
11356         DoSleep( appData.delayAfterQuit );
11357         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11358     }
11359     if (second.pr != NoProc) {
11360         DoSleep( appData.delayBeforeQuit );
11361         SendToProgram("quit\n", &second);
11362         DoSleep( appData.delayAfterQuit );
11363         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11364     }
11365     if (first.isr != NULL) {
11366         RemoveInputSource(first.isr);
11367     }
11368     if (second.isr != NULL) {
11369         RemoveInputSource(second.isr);
11370     }
11371
11372     ShutDownFrontEnd();
11373     exit(status);
11374 }
11375
11376 void
11377 PauseEvent()
11378 {
11379     if (appData.debugMode)
11380         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11381     if (pausing) {
11382         pausing = FALSE;
11383         ModeHighlight();
11384         if (gameMode == MachinePlaysWhite ||
11385             gameMode == MachinePlaysBlack) {
11386             StartClocks();
11387         } else {
11388             DisplayBothClocks();
11389         }
11390         if (gameMode == PlayFromGameFile) {
11391             if (appData.timeDelay >= 0) 
11392                 AutoPlayGameLoop();
11393         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11394             Reset(FALSE, TRUE);
11395             SendToICS(ics_prefix);
11396             SendToICS("refresh\n");
11397         } else if (currentMove < forwardMostMove) {
11398             ForwardInner(forwardMostMove);
11399         }
11400         pauseExamInvalid = FALSE;
11401     } else {
11402         switch (gameMode) {
11403           default:
11404             return;
11405           case IcsExamining:
11406             pauseExamForwardMostMove = forwardMostMove;
11407             pauseExamInvalid = FALSE;
11408             /* fall through */
11409           case IcsObserving:
11410           case IcsPlayingWhite:
11411           case IcsPlayingBlack:
11412             pausing = TRUE;
11413             ModeHighlight();
11414             return;
11415           case PlayFromGameFile:
11416             (void) StopLoadGameTimer();
11417             pausing = TRUE;
11418             ModeHighlight();
11419             break;
11420           case BeginningOfGame:
11421             if (appData.icsActive) return;
11422             /* else fall through */
11423           case MachinePlaysWhite:
11424           case MachinePlaysBlack:
11425           case TwoMachinesPlay:
11426             if (forwardMostMove == 0)
11427               return;           /* don't pause if no one has moved */
11428             if ((gameMode == MachinePlaysWhite &&
11429                  !WhiteOnMove(forwardMostMove)) ||
11430                 (gameMode == MachinePlaysBlack &&
11431                  WhiteOnMove(forwardMostMove))) {
11432                 StopClocks();
11433             }
11434             pausing = TRUE;
11435             ModeHighlight();
11436             break;
11437         }
11438     }
11439 }
11440
11441 void
11442 EditCommentEvent()
11443 {
11444     char title[MSG_SIZ];
11445
11446     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11447         strcpy(title, _("Edit comment"));
11448     } else {
11449         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11450                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11451                 parseList[currentMove - 1]);
11452     }
11453
11454     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11455 }
11456
11457
11458 void
11459 EditTagsEvent()
11460 {
11461     char *tags = PGNTags(&gameInfo);
11462     EditTagsPopUp(tags);
11463     free(tags);
11464 }
11465
11466 void
11467 AnalyzeModeEvent()
11468 {
11469     if (appData.noChessProgram || gameMode == AnalyzeMode)
11470       return;
11471
11472     if (gameMode != AnalyzeFile) {
11473         if (!appData.icsEngineAnalyze) {
11474                EditGameEvent();
11475                if (gameMode != EditGame) return;
11476         }
11477         ResurrectChessProgram();
11478         SendToProgram("analyze\n", &first);
11479         first.analyzing = TRUE;
11480         /*first.maybeThinking = TRUE;*/
11481         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11482         EngineOutputPopUp();
11483     }
11484     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11485     pausing = FALSE;
11486     ModeHighlight();
11487     SetGameInfo();
11488
11489     StartAnalysisClock();
11490     GetTimeMark(&lastNodeCountTime);
11491     lastNodeCount = 0;
11492 }
11493
11494 void
11495 AnalyzeFileEvent()
11496 {
11497     if (appData.noChessProgram || gameMode == AnalyzeFile)
11498       return;
11499
11500     if (gameMode != AnalyzeMode) {
11501         EditGameEvent();
11502         if (gameMode != EditGame) return;
11503         ResurrectChessProgram();
11504         SendToProgram("analyze\n", &first);
11505         first.analyzing = TRUE;
11506         /*first.maybeThinking = TRUE;*/
11507         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11508         EngineOutputPopUp();
11509     }
11510     gameMode = AnalyzeFile;
11511     pausing = FALSE;
11512     ModeHighlight();
11513     SetGameInfo();
11514
11515     StartAnalysisClock();
11516     GetTimeMark(&lastNodeCountTime);
11517     lastNodeCount = 0;
11518 }
11519
11520 void
11521 MachineWhiteEvent()
11522 {
11523     char buf[MSG_SIZ];
11524     char *bookHit = NULL;
11525
11526     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11527       return;
11528
11529
11530     if (gameMode == PlayFromGameFile || 
11531         gameMode == TwoMachinesPlay  || 
11532         gameMode == Training         || 
11533         gameMode == AnalyzeMode      || 
11534         gameMode == EndOfGame)
11535         EditGameEvent();
11536
11537     if (gameMode == EditPosition) 
11538         EditPositionDone(TRUE);
11539
11540     if (!WhiteOnMove(currentMove)) {
11541         DisplayError(_("It is not White's turn"), 0);
11542         return;
11543     }
11544   
11545     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11546       ExitAnalyzeMode();
11547
11548     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11549         gameMode == AnalyzeFile)
11550         TruncateGame();
11551
11552     ResurrectChessProgram();    /* in case it isn't running */
11553     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11554         gameMode = MachinePlaysWhite;
11555         ResetClocks();
11556     } else
11557     gameMode = MachinePlaysWhite;
11558     pausing = FALSE;
11559     ModeHighlight();
11560     SetGameInfo();
11561     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11562     DisplayTitle(buf);
11563     if (first.sendName) {
11564       sprintf(buf, "name %s\n", gameInfo.black);
11565       SendToProgram(buf, &first);
11566     }
11567     if (first.sendTime) {
11568       if (first.useColors) {
11569         SendToProgram("black\n", &first); /*gnu kludge*/
11570       }
11571       SendTimeRemaining(&first, TRUE);
11572     }
11573     if (first.useColors) {
11574       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11575     }
11576     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11577     SetMachineThinkingEnables();
11578     first.maybeThinking = TRUE;
11579     StartClocks();
11580     firstMove = FALSE;
11581
11582     if (appData.autoFlipView && !flipView) {
11583       flipView = !flipView;
11584       DrawPosition(FALSE, NULL);
11585       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11586     }
11587
11588     if(bookHit) { // [HGM] book: simulate book reply
11589         static char bookMove[MSG_SIZ]; // a bit generous?
11590
11591         programStats.nodes = programStats.depth = programStats.time = 
11592         programStats.score = programStats.got_only_move = 0;
11593         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11594
11595         strcpy(bookMove, "move ");
11596         strcat(bookMove, bookHit);
11597         HandleMachineMove(bookMove, &first);
11598     }
11599 }
11600
11601 void
11602 MachineBlackEvent()
11603 {
11604     char buf[MSG_SIZ];
11605    char *bookHit = NULL;
11606
11607     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11608         return;
11609
11610
11611     if (gameMode == PlayFromGameFile || 
11612         gameMode == TwoMachinesPlay  || 
11613         gameMode == Training         || 
11614         gameMode == AnalyzeMode      || 
11615         gameMode == EndOfGame)
11616         EditGameEvent();
11617
11618     if (gameMode == EditPosition) 
11619         EditPositionDone(TRUE);
11620
11621     if (WhiteOnMove(currentMove)) {
11622         DisplayError(_("It is not Black's turn"), 0);
11623         return;
11624     }
11625     
11626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11627       ExitAnalyzeMode();
11628
11629     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11630         gameMode == AnalyzeFile)
11631         TruncateGame();
11632
11633     ResurrectChessProgram();    /* in case it isn't running */
11634     gameMode = MachinePlaysBlack;
11635     pausing = FALSE;
11636     ModeHighlight();
11637     SetGameInfo();
11638     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11639     DisplayTitle(buf);
11640     if (first.sendName) {
11641       sprintf(buf, "name %s\n", gameInfo.white);
11642       SendToProgram(buf, &first);
11643     }
11644     if (first.sendTime) {
11645       if (first.useColors) {
11646         SendToProgram("white\n", &first); /*gnu kludge*/
11647       }
11648       SendTimeRemaining(&first, FALSE);
11649     }
11650     if (first.useColors) {
11651       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11652     }
11653     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11654     SetMachineThinkingEnables();
11655     first.maybeThinking = TRUE;
11656     StartClocks();
11657
11658     if (appData.autoFlipView && flipView) {
11659       flipView = !flipView;
11660       DrawPosition(FALSE, NULL);
11661       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11662     }
11663     if(bookHit) { // [HGM] book: simulate book reply
11664         static char bookMove[MSG_SIZ]; // a bit generous?
11665
11666         programStats.nodes = programStats.depth = programStats.time = 
11667         programStats.score = programStats.got_only_move = 0;
11668         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11669
11670         strcpy(bookMove, "move ");
11671         strcat(bookMove, bookHit);
11672         HandleMachineMove(bookMove, &first);
11673     }
11674 }
11675
11676
11677 void
11678 DisplayTwoMachinesTitle()
11679 {
11680     char buf[MSG_SIZ];
11681     if (appData.matchGames > 0) {
11682         if (first.twoMachinesColor[0] == 'w') {
11683             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11684                     gameInfo.white, gameInfo.black,
11685                     first.matchWins, second.matchWins,
11686                     matchGame - 1 - (first.matchWins + second.matchWins));
11687         } else {
11688             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689                     gameInfo.white, gameInfo.black,
11690                     second.matchWins, first.matchWins,
11691                     matchGame - 1 - (first.matchWins + second.matchWins));
11692         }
11693     } else {
11694         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11695     }
11696     DisplayTitle(buf);
11697 }
11698
11699 void
11700 TwoMachinesEvent P((void))
11701 {
11702     int i;
11703     char buf[MSG_SIZ];
11704     ChessProgramState *onmove;
11705     char *bookHit = NULL;
11706     
11707     if (appData.noChessProgram) return;
11708
11709     switch (gameMode) {
11710       case TwoMachinesPlay:
11711         return;
11712       case MachinePlaysWhite:
11713       case MachinePlaysBlack:
11714         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11715             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11716             return;
11717         }
11718         /* fall through */
11719       case BeginningOfGame:
11720       case PlayFromGameFile:
11721       case EndOfGame:
11722         EditGameEvent();
11723         if (gameMode != EditGame) return;
11724         break;
11725       case EditPosition:
11726         EditPositionDone(TRUE);
11727         break;
11728       case AnalyzeMode:
11729       case AnalyzeFile:
11730         ExitAnalyzeMode();
11731         break;
11732       case EditGame:
11733       default:
11734         break;
11735     }
11736
11737 //    forwardMostMove = currentMove;
11738     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11739     ResurrectChessProgram();    /* in case first program isn't running */
11740
11741     if (second.pr == NULL) {
11742         StartChessProgram(&second);
11743         if (second.protocolVersion == 1) {
11744           TwoMachinesEventIfReady();
11745         } else {
11746           /* kludge: allow timeout for initial "feature" command */
11747           FreezeUI();
11748           DisplayMessage("", _("Starting second chess program"));
11749           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11750         }
11751         return;
11752     }
11753     DisplayMessage("", "");
11754     InitChessProgram(&second, FALSE);
11755     SendToProgram("force\n", &second);
11756     if (startedFromSetupPosition) {
11757         SendBoard(&second, backwardMostMove);
11758     if (appData.debugMode) {
11759         fprintf(debugFP, "Two Machines\n");
11760     }
11761     }
11762     for (i = backwardMostMove; i < forwardMostMove; i++) {
11763         SendMoveToProgram(i, &second);
11764     }
11765
11766     gameMode = TwoMachinesPlay;
11767     pausing = FALSE;
11768     ModeHighlight();
11769     SetGameInfo();
11770     DisplayTwoMachinesTitle();
11771     firstMove = TRUE;
11772     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11773         onmove = &first;
11774     } else {
11775         onmove = &second;
11776     }
11777
11778     SendToProgram(first.computerString, &first);
11779     if (first.sendName) {
11780       sprintf(buf, "name %s\n", second.tidy);
11781       SendToProgram(buf, &first);
11782     }
11783     SendToProgram(second.computerString, &second);
11784     if (second.sendName) {
11785       sprintf(buf, "name %s\n", first.tidy);
11786       SendToProgram(buf, &second);
11787     }
11788
11789     ResetClocks();
11790     if (!first.sendTime || !second.sendTime) {
11791         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11792         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11793     }
11794     if (onmove->sendTime) {
11795       if (onmove->useColors) {
11796         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11797       }
11798       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11799     }
11800     if (onmove->useColors) {
11801       SendToProgram(onmove->twoMachinesColor, onmove);
11802     }
11803     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11804 //    SendToProgram("go\n", onmove);
11805     onmove->maybeThinking = TRUE;
11806     SetMachineThinkingEnables();
11807
11808     StartClocks();
11809
11810     if(bookHit) { // [HGM] book: simulate book reply
11811         static char bookMove[MSG_SIZ]; // a bit generous?
11812
11813         programStats.nodes = programStats.depth = programStats.time = 
11814         programStats.score = programStats.got_only_move = 0;
11815         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11816
11817         strcpy(bookMove, "move ");
11818         strcat(bookMove, bookHit);
11819         savedMessage = bookMove; // args for deferred call
11820         savedState = onmove;
11821         ScheduleDelayedEvent(DeferredBookMove, 1);
11822     }
11823 }
11824
11825 void
11826 TrainingEvent()
11827 {
11828     if (gameMode == Training) {
11829       SetTrainingModeOff();
11830       gameMode = PlayFromGameFile;
11831       DisplayMessage("", _("Training mode off"));
11832     } else {
11833       gameMode = Training;
11834       animateTraining = appData.animate;
11835
11836       /* make sure we are not already at the end of the game */
11837       if (currentMove < forwardMostMove) {
11838         SetTrainingModeOn();
11839         DisplayMessage("", _("Training mode on"));
11840       } else {
11841         gameMode = PlayFromGameFile;
11842         DisplayError(_("Already at end of game"), 0);
11843       }
11844     }
11845     ModeHighlight();
11846 }
11847
11848 void
11849 IcsClientEvent()
11850 {
11851     if (!appData.icsActive) return;
11852     switch (gameMode) {
11853       case IcsPlayingWhite:
11854       case IcsPlayingBlack:
11855       case IcsObserving:
11856       case IcsIdle:
11857       case BeginningOfGame:
11858       case IcsExamining:
11859         return;
11860
11861       case EditGame:
11862         break;
11863
11864       case EditPosition:
11865         EditPositionDone(TRUE);
11866         break;
11867
11868       case AnalyzeMode:
11869       case AnalyzeFile:
11870         ExitAnalyzeMode();
11871         break;
11872         
11873       default:
11874         EditGameEvent();
11875         break;
11876     }
11877
11878     gameMode = IcsIdle;
11879     ModeHighlight();
11880     return;
11881 }
11882
11883
11884 void
11885 EditGameEvent()
11886 {
11887     int i;
11888
11889     switch (gameMode) {
11890       case Training:
11891         SetTrainingModeOff();
11892         break;
11893       case MachinePlaysWhite:
11894       case MachinePlaysBlack:
11895       case BeginningOfGame:
11896         SendToProgram("force\n", &first);
11897         SetUserThinkingEnables();
11898         break;
11899       case PlayFromGameFile:
11900         (void) StopLoadGameTimer();
11901         if (gameFileFP != NULL) {
11902             gameFileFP = NULL;
11903         }
11904         break;
11905       case EditPosition:
11906         EditPositionDone(TRUE);
11907         break;
11908       case AnalyzeMode:
11909       case AnalyzeFile:
11910         ExitAnalyzeMode();
11911         SendToProgram("force\n", &first);
11912         break;
11913       case TwoMachinesPlay:
11914         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11915         ResurrectChessProgram();
11916         SetUserThinkingEnables();
11917         break;
11918       case EndOfGame:
11919         ResurrectChessProgram();
11920         break;
11921       case IcsPlayingBlack:
11922       case IcsPlayingWhite:
11923         DisplayError(_("Warning: You are still playing a game"), 0);
11924         break;
11925       case IcsObserving:
11926         DisplayError(_("Warning: You are still observing a game"), 0);
11927         break;
11928       case IcsExamining:
11929         DisplayError(_("Warning: You are still examining a game"), 0);
11930         break;
11931       case IcsIdle:
11932         break;
11933       case EditGame:
11934       default:
11935         return;
11936     }
11937     
11938     pausing = FALSE;
11939     StopClocks();
11940     first.offeredDraw = second.offeredDraw = 0;
11941
11942     if (gameMode == PlayFromGameFile) {
11943         whiteTimeRemaining = timeRemaining[0][currentMove];
11944         blackTimeRemaining = timeRemaining[1][currentMove];
11945         DisplayTitle("");
11946     }
11947
11948     if (gameMode == MachinePlaysWhite ||
11949         gameMode == MachinePlaysBlack ||
11950         gameMode == TwoMachinesPlay ||
11951         gameMode == EndOfGame) {
11952         i = forwardMostMove;
11953         while (i > currentMove) {
11954             SendToProgram("undo\n", &first);
11955             i--;
11956         }
11957         whiteTimeRemaining = timeRemaining[0][currentMove];
11958         blackTimeRemaining = timeRemaining[1][currentMove];
11959         DisplayBothClocks();
11960         if (whiteFlag || blackFlag) {
11961             whiteFlag = blackFlag = 0;
11962         }
11963         DisplayTitle("");
11964     }           
11965     
11966     gameMode = EditGame;
11967     ModeHighlight();
11968     SetGameInfo();
11969 }
11970
11971
11972 void
11973 EditPositionEvent()
11974 {
11975     if (gameMode == EditPosition) {
11976         EditGameEvent();
11977         return;
11978     }
11979     
11980     EditGameEvent();
11981     if (gameMode != EditGame) return;
11982     
11983     gameMode = EditPosition;
11984     ModeHighlight();
11985     SetGameInfo();
11986     if (currentMove > 0)
11987       CopyBoard(boards[0], boards[currentMove]);
11988     
11989     blackPlaysFirst = !WhiteOnMove(currentMove);
11990     ResetClocks();
11991     currentMove = forwardMostMove = backwardMostMove = 0;
11992     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11993     DisplayMove(-1);
11994 }
11995
11996 void
11997 ExitAnalyzeMode()
11998 {
11999     /* [DM] icsEngineAnalyze - possible call from other functions */
12000     if (appData.icsEngineAnalyze) {
12001         appData.icsEngineAnalyze = FALSE;
12002
12003         DisplayMessage("",_("Close ICS engine analyze..."));
12004     }
12005     if (first.analysisSupport && first.analyzing) {
12006       SendToProgram("exit\n", &first);
12007       first.analyzing = FALSE;
12008     }
12009     thinkOutput[0] = NULLCHAR;
12010 }
12011
12012 void
12013 EditPositionDone(Boolean fakeRights)
12014 {
12015     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12016
12017     startedFromSetupPosition = TRUE;
12018     InitChessProgram(&first, FALSE);
12019     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12020       boards[0][EP_STATUS] = EP_NONE;
12021       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12022     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12023         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12024         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12025       } else boards[0][CASTLING][2] = NoRights;
12026     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12027         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12028         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12029       } else boards[0][CASTLING][5] = NoRights;
12030     }
12031     SendToProgram("force\n", &first);
12032     if (blackPlaysFirst) {
12033         strcpy(moveList[0], "");
12034         strcpy(parseList[0], "");
12035         currentMove = forwardMostMove = backwardMostMove = 1;
12036         CopyBoard(boards[1], boards[0]);
12037     } else {
12038         currentMove = forwardMostMove = backwardMostMove = 0;
12039     }
12040     SendBoard(&first, forwardMostMove);
12041     if (appData.debugMode) {
12042         fprintf(debugFP, "EditPosDone\n");
12043     }
12044     DisplayTitle("");
12045     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12046     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12047     gameMode = EditGame;
12048     ModeHighlight();
12049     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12050     ClearHighlights(); /* [AS] */
12051 }
12052
12053 /* Pause for `ms' milliseconds */
12054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12055 void
12056 TimeDelay(ms)
12057      long ms;
12058 {
12059     TimeMark m1, m2;
12060
12061     GetTimeMark(&m1);
12062     do {
12063         GetTimeMark(&m2);
12064     } while (SubtractTimeMarks(&m2, &m1) < ms);
12065 }
12066
12067 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12068 void
12069 SendMultiLineToICS(buf)
12070      char *buf;
12071 {
12072     char temp[MSG_SIZ+1], *p;
12073     int len;
12074
12075     len = strlen(buf);
12076     if (len > MSG_SIZ)
12077       len = MSG_SIZ;
12078   
12079     strncpy(temp, buf, len);
12080     temp[len] = 0;
12081
12082     p = temp;
12083     while (*p) {
12084         if (*p == '\n' || *p == '\r')
12085           *p = ' ';
12086         ++p;
12087     }
12088
12089     strcat(temp, "\n");
12090     SendToICS(temp);
12091     SendToPlayer(temp, strlen(temp));
12092 }
12093
12094 void
12095 SetWhiteToPlayEvent()
12096 {
12097     if (gameMode == EditPosition) {
12098         blackPlaysFirst = FALSE;
12099         DisplayBothClocks();    /* works because currentMove is 0 */
12100     } else if (gameMode == IcsExamining) {
12101         SendToICS(ics_prefix);
12102         SendToICS("tomove white\n");
12103     }
12104 }
12105
12106 void
12107 SetBlackToPlayEvent()
12108 {
12109     if (gameMode == EditPosition) {
12110         blackPlaysFirst = TRUE;
12111         currentMove = 1;        /* kludge */
12112         DisplayBothClocks();
12113         currentMove = 0;
12114     } else if (gameMode == IcsExamining) {
12115         SendToICS(ics_prefix);
12116         SendToICS("tomove black\n");
12117     }
12118 }
12119
12120 void
12121 EditPositionMenuEvent(selection, x, y)
12122      ChessSquare selection;
12123      int x, y;
12124 {
12125     char buf[MSG_SIZ];
12126     ChessSquare piece = boards[0][y][x];
12127
12128     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12129
12130     switch (selection) {
12131       case ClearBoard:
12132         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12133             SendToICS(ics_prefix);
12134             SendToICS("bsetup clear\n");
12135         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12136             SendToICS(ics_prefix);
12137             SendToICS("clearboard\n");
12138         } else {
12139             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12140                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12141                 for (y = 0; y < BOARD_HEIGHT; y++) {
12142                     if (gameMode == IcsExamining) {
12143                         if (boards[currentMove][y][x] != EmptySquare) {
12144                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12145                                     AAA + x, ONE + y);
12146                             SendToICS(buf);
12147                         }
12148                     } else {
12149                         boards[0][y][x] = p;
12150                     }
12151                 }
12152             }
12153         }
12154         if (gameMode == EditPosition) {
12155             DrawPosition(FALSE, boards[0]);
12156         }
12157         break;
12158
12159       case WhitePlay:
12160         SetWhiteToPlayEvent();
12161         break;
12162
12163       case BlackPlay:
12164         SetBlackToPlayEvent();
12165         break;
12166
12167       case EmptySquare:
12168         if (gameMode == IcsExamining) {
12169             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12170             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12171             SendToICS(buf);
12172         } else {
12173             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12174                 if(x == BOARD_LEFT-2) {
12175                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12176                     boards[0][y][1] = 0;
12177                 } else
12178                 if(x == BOARD_RGHT+1) {
12179                     if(y >= gameInfo.holdingsSize) break;
12180                     boards[0][y][BOARD_WIDTH-2] = 0;
12181                 } else break;
12182             }
12183             boards[0][y][x] = EmptySquare;
12184             DrawPosition(FALSE, boards[0]);
12185         }
12186         break;
12187
12188       case PromotePiece:
12189         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12190            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12191             selection = (ChessSquare) (PROMOTED piece);
12192         } else if(piece == EmptySquare) selection = WhiteSilver;
12193         else selection = (ChessSquare)((int)piece - 1);
12194         goto defaultlabel;
12195
12196       case DemotePiece:
12197         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12198            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12199             selection = (ChessSquare) (DEMOTED piece);
12200         } else if(piece == EmptySquare) selection = BlackSilver;
12201         else selection = (ChessSquare)((int)piece + 1);       
12202         goto defaultlabel;
12203
12204       case WhiteQueen:
12205       case BlackQueen:
12206         if(gameInfo.variant == VariantShatranj ||
12207            gameInfo.variant == VariantXiangqi  ||
12208            gameInfo.variant == VariantCourier  ||
12209            gameInfo.variant == VariantMakruk     )
12210             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12211         goto defaultlabel;
12212
12213       case WhiteKing:
12214       case BlackKing:
12215         if(gameInfo.variant == VariantXiangqi)
12216             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12217         if(gameInfo.variant == VariantKnightmate)
12218             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12219       default:
12220         defaultlabel:
12221         if (gameMode == IcsExamining) {
12222             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12223             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12224                     PieceToChar(selection), AAA + x, ONE + y);
12225             SendToICS(buf);
12226         } else {
12227             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12228                 int n;
12229                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12230                     n = PieceToNumber(selection - BlackPawn);
12231                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12232                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12233                     boards[0][BOARD_HEIGHT-1-n][1]++;
12234                 } else
12235                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12236                     n = PieceToNumber(selection);
12237                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12238                     boards[0][n][BOARD_WIDTH-1] = selection;
12239                     boards[0][n][BOARD_WIDTH-2]++;
12240                 }
12241             } else
12242             boards[0][y][x] = selection;
12243             DrawPosition(TRUE, boards[0]);
12244         }
12245         break;
12246     }
12247 }
12248
12249
12250 void
12251 DropMenuEvent(selection, x, y)
12252      ChessSquare selection;
12253      int x, y;
12254 {
12255     ChessMove moveType;
12256
12257     switch (gameMode) {
12258       case IcsPlayingWhite:
12259       case MachinePlaysBlack:
12260         if (!WhiteOnMove(currentMove)) {
12261             DisplayMoveError(_("It is Black's turn"));
12262             return;
12263         }
12264         moveType = WhiteDrop;
12265         break;
12266       case IcsPlayingBlack:
12267       case MachinePlaysWhite:
12268         if (WhiteOnMove(currentMove)) {
12269             DisplayMoveError(_("It is White's turn"));
12270             return;
12271         }
12272         moveType = BlackDrop;
12273         break;
12274       case EditGame:
12275         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12276         break;
12277       default:
12278         return;
12279     }
12280
12281     if (moveType == BlackDrop && selection < BlackPawn) {
12282       selection = (ChessSquare) ((int) selection
12283                                  + (int) BlackPawn - (int) WhitePawn);
12284     }
12285     if (boards[currentMove][y][x] != EmptySquare) {
12286         DisplayMoveError(_("That square is occupied"));
12287         return;
12288     }
12289
12290     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12291 }
12292
12293 void
12294 AcceptEvent()
12295 {
12296     /* Accept a pending offer of any kind from opponent */
12297     
12298     if (appData.icsActive) {
12299         SendToICS(ics_prefix);
12300         SendToICS("accept\n");
12301     } else if (cmailMsgLoaded) {
12302         if (currentMove == cmailOldMove &&
12303             commentList[cmailOldMove] != NULL &&
12304             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12305                    "Black offers a draw" : "White offers a draw")) {
12306             TruncateGame();
12307             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12308             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12309         } else {
12310             DisplayError(_("There is no pending offer on this move"), 0);
12311             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12312         }
12313     } else {
12314         /* Not used for offers from chess program */
12315     }
12316 }
12317
12318 void
12319 DeclineEvent()
12320 {
12321     /* Decline a pending offer of any kind from opponent */
12322     
12323     if (appData.icsActive) {
12324         SendToICS(ics_prefix);
12325         SendToICS("decline\n");
12326     } else if (cmailMsgLoaded) {
12327         if (currentMove == cmailOldMove &&
12328             commentList[cmailOldMove] != NULL &&
12329             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12330                    "Black offers a draw" : "White offers a draw")) {
12331 #ifdef NOTDEF
12332             AppendComment(cmailOldMove, "Draw declined", TRUE);
12333             DisplayComment(cmailOldMove - 1, "Draw declined");
12334 #endif /*NOTDEF*/
12335         } else {
12336             DisplayError(_("There is no pending offer on this move"), 0);
12337         }
12338     } else {
12339         /* Not used for offers from chess program */
12340     }
12341 }
12342
12343 void
12344 RematchEvent()
12345 {
12346     /* Issue ICS rematch command */
12347     if (appData.icsActive) {
12348         SendToICS(ics_prefix);
12349         SendToICS("rematch\n");
12350     }
12351 }
12352
12353 void
12354 CallFlagEvent()
12355 {
12356     /* Call your opponent's flag (claim a win on time) */
12357     if (appData.icsActive) {
12358         SendToICS(ics_prefix);
12359         SendToICS("flag\n");
12360     } else {
12361         switch (gameMode) {
12362           default:
12363             return;
12364           case MachinePlaysWhite:
12365             if (whiteFlag) {
12366                 if (blackFlag)
12367                   GameEnds(GameIsDrawn, "Both players ran out of time",
12368                            GE_PLAYER);
12369                 else
12370                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12371             } else {
12372                 DisplayError(_("Your opponent is not out of time"), 0);
12373             }
12374             break;
12375           case MachinePlaysBlack:
12376             if (blackFlag) {
12377                 if (whiteFlag)
12378                   GameEnds(GameIsDrawn, "Both players ran out of time",
12379                            GE_PLAYER);
12380                 else
12381                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12382             } else {
12383                 DisplayError(_("Your opponent is not out of time"), 0);
12384             }
12385             break;
12386         }
12387     }
12388 }
12389
12390 void
12391 DrawEvent()
12392 {
12393     /* Offer draw or accept pending draw offer from opponent */
12394     
12395     if (appData.icsActive) {
12396         /* Note: tournament rules require draw offers to be
12397            made after you make your move but before you punch
12398            your clock.  Currently ICS doesn't let you do that;
12399            instead, you immediately punch your clock after making
12400            a move, but you can offer a draw at any time. */
12401         
12402         SendToICS(ics_prefix);
12403         SendToICS("draw\n");
12404         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12405     } else if (cmailMsgLoaded) {
12406         if (currentMove == cmailOldMove &&
12407             commentList[cmailOldMove] != NULL &&
12408             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12409                    "Black offers a draw" : "White offers a draw")) {
12410             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12411             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12412         } else if (currentMove == cmailOldMove + 1) {
12413             char *offer = WhiteOnMove(cmailOldMove) ?
12414               "White offers a draw" : "Black offers a draw";
12415             AppendComment(currentMove, offer, TRUE);
12416             DisplayComment(currentMove - 1, offer);
12417             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12418         } else {
12419             DisplayError(_("You must make your move before offering a draw"), 0);
12420             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12421         }
12422     } else if (first.offeredDraw) {
12423         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12424     } else {
12425         if (first.sendDrawOffers) {
12426             SendToProgram("draw\n", &first);
12427             userOfferedDraw = TRUE;
12428         }
12429     }
12430 }
12431
12432 void
12433 AdjournEvent()
12434 {
12435     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12436     
12437     if (appData.icsActive) {
12438         SendToICS(ics_prefix);
12439         SendToICS("adjourn\n");
12440     } else {
12441         /* Currently GNU Chess doesn't offer or accept Adjourns */
12442     }
12443 }
12444
12445
12446 void
12447 AbortEvent()
12448 {
12449     /* Offer Abort or accept pending Abort offer from opponent */
12450     
12451     if (appData.icsActive) {
12452         SendToICS(ics_prefix);
12453         SendToICS("abort\n");
12454     } else {
12455         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12456     }
12457 }
12458
12459 void
12460 ResignEvent()
12461 {
12462     /* Resign.  You can do this even if it's not your turn. */
12463     
12464     if (appData.icsActive) {
12465         SendToICS(ics_prefix);
12466         SendToICS("resign\n");
12467     } else {
12468         switch (gameMode) {
12469           case MachinePlaysWhite:
12470             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12471             break;
12472           case MachinePlaysBlack:
12473             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12474             break;
12475           case EditGame:
12476             if (cmailMsgLoaded) {
12477                 TruncateGame();
12478                 if (WhiteOnMove(cmailOldMove)) {
12479                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12480                 } else {
12481                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12482                 }
12483                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12484             }
12485             break;
12486           default:
12487             break;
12488         }
12489     }
12490 }
12491
12492
12493 void
12494 StopObservingEvent()
12495 {
12496     /* Stop observing current games */
12497     SendToICS(ics_prefix);
12498     SendToICS("unobserve\n");
12499 }
12500
12501 void
12502 StopExaminingEvent()
12503 {
12504     /* Stop observing current game */
12505     SendToICS(ics_prefix);
12506     SendToICS("unexamine\n");
12507 }
12508
12509 void
12510 ForwardInner(target)
12511      int target;
12512 {
12513     int limit;
12514
12515     if (appData.debugMode)
12516         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12517                 target, currentMove, forwardMostMove);
12518
12519     if (gameMode == EditPosition)
12520       return;
12521
12522     if (gameMode == PlayFromGameFile && !pausing)
12523       PauseEvent();
12524     
12525     if (gameMode == IcsExamining && pausing)
12526       limit = pauseExamForwardMostMove;
12527     else
12528       limit = forwardMostMove;
12529     
12530     if (target > limit) target = limit;
12531
12532     if (target > 0 && moveList[target - 1][0]) {
12533         int fromX, fromY, toX, toY;
12534         toX = moveList[target - 1][2] - AAA;
12535         toY = moveList[target - 1][3] - ONE;
12536         if (moveList[target - 1][1] == '@') {
12537             if (appData.highlightLastMove) {
12538                 SetHighlights(-1, -1, toX, toY);
12539             }
12540         } else {
12541             fromX = moveList[target - 1][0] - AAA;
12542             fromY = moveList[target - 1][1] - ONE;
12543             if (target == currentMove + 1) {
12544                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12545             }
12546             if (appData.highlightLastMove) {
12547                 SetHighlights(fromX, fromY, toX, toY);
12548             }
12549         }
12550     }
12551     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12552         gameMode == Training || gameMode == PlayFromGameFile || 
12553         gameMode == AnalyzeFile) {
12554         while (currentMove < target) {
12555             SendMoveToProgram(currentMove++, &first);
12556         }
12557     } else {
12558         currentMove = target;
12559     }
12560     
12561     if (gameMode == EditGame || gameMode == EndOfGame) {
12562         whiteTimeRemaining = timeRemaining[0][currentMove];
12563         blackTimeRemaining = timeRemaining[1][currentMove];
12564     }
12565     DisplayBothClocks();
12566     DisplayMove(currentMove - 1);
12567     DrawPosition(FALSE, boards[currentMove]);
12568     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12569     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12570         DisplayComment(currentMove - 1, commentList[currentMove]);
12571     }
12572 }
12573
12574
12575 void
12576 ForwardEvent()
12577 {
12578     if (gameMode == IcsExamining && !pausing) {
12579         SendToICS(ics_prefix);
12580         SendToICS("forward\n");
12581     } else {
12582         ForwardInner(currentMove + 1);
12583     }
12584 }
12585
12586 void
12587 ToEndEvent()
12588 {
12589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12590         /* to optimze, we temporarily turn off analysis mode while we feed
12591          * the remaining moves to the engine. Otherwise we get analysis output
12592          * after each move.
12593          */ 
12594         if (first.analysisSupport) {
12595           SendToProgram("exit\nforce\n", &first);
12596           first.analyzing = FALSE;
12597         }
12598     }
12599         
12600     if (gameMode == IcsExamining && !pausing) {
12601         SendToICS(ics_prefix);
12602         SendToICS("forward 999999\n");
12603     } else {
12604         ForwardInner(forwardMostMove);
12605     }
12606
12607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12608         /* we have fed all the moves, so reactivate analysis mode */
12609         SendToProgram("analyze\n", &first);
12610         first.analyzing = TRUE;
12611         /*first.maybeThinking = TRUE;*/
12612         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12613     }
12614 }
12615
12616 void
12617 BackwardInner(target)
12618      int target;
12619 {
12620     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12621
12622     if (appData.debugMode)
12623         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12624                 target, currentMove, forwardMostMove);
12625
12626     if (gameMode == EditPosition) return;
12627     if (currentMove <= backwardMostMove) {
12628         ClearHighlights();
12629         DrawPosition(full_redraw, boards[currentMove]);
12630         return;
12631     }
12632     if (gameMode == PlayFromGameFile && !pausing)
12633       PauseEvent();
12634     
12635     if (moveList[target][0]) {
12636         int fromX, fromY, toX, toY;
12637         toX = moveList[target][2] - AAA;
12638         toY = moveList[target][3] - ONE;
12639         if (moveList[target][1] == '@') {
12640             if (appData.highlightLastMove) {
12641                 SetHighlights(-1, -1, toX, toY);
12642             }
12643         } else {
12644             fromX = moveList[target][0] - AAA;
12645             fromY = moveList[target][1] - ONE;
12646             if (target == currentMove - 1) {
12647                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12648             }
12649             if (appData.highlightLastMove) {
12650                 SetHighlights(fromX, fromY, toX, toY);
12651             }
12652         }
12653     }
12654     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12655         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12656         while (currentMove > target) {
12657             SendToProgram("undo\n", &first);
12658             currentMove--;
12659         }
12660     } else {
12661         currentMove = target;
12662     }
12663     
12664     if (gameMode == EditGame || gameMode == EndOfGame) {
12665         whiteTimeRemaining = timeRemaining[0][currentMove];
12666         blackTimeRemaining = timeRemaining[1][currentMove];
12667     }
12668     DisplayBothClocks();
12669     DisplayMove(currentMove - 1);
12670     DrawPosition(full_redraw, boards[currentMove]);
12671     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12672     // [HGM] PV info: routine tests if comment empty
12673     DisplayComment(currentMove - 1, commentList[currentMove]);
12674 }
12675
12676 void
12677 BackwardEvent()
12678 {
12679     if (gameMode == IcsExamining && !pausing) {
12680         SendToICS(ics_prefix);
12681         SendToICS("backward\n");
12682     } else {
12683         BackwardInner(currentMove - 1);
12684     }
12685 }
12686
12687 void
12688 ToStartEvent()
12689 {
12690     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12691         /* to optimize, we temporarily turn off analysis mode while we undo
12692          * all the moves. Otherwise we get analysis output after each undo.
12693          */ 
12694         if (first.analysisSupport) {
12695           SendToProgram("exit\nforce\n", &first);
12696           first.analyzing = FALSE;
12697         }
12698     }
12699
12700     if (gameMode == IcsExamining && !pausing) {
12701         SendToICS(ics_prefix);
12702         SendToICS("backward 999999\n");
12703     } else {
12704         BackwardInner(backwardMostMove);
12705     }
12706
12707     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12708         /* we have fed all the moves, so reactivate analysis mode */
12709         SendToProgram("analyze\n", &first);
12710         first.analyzing = TRUE;
12711         /*first.maybeThinking = TRUE;*/
12712         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12713     }
12714 }
12715
12716 void
12717 ToNrEvent(int to)
12718 {
12719   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12720   if (to >= forwardMostMove) to = forwardMostMove;
12721   if (to <= backwardMostMove) to = backwardMostMove;
12722   if (to < currentMove) {
12723     BackwardInner(to);
12724   } else {
12725     ForwardInner(to);
12726   }
12727 }
12728
12729 void
12730 RevertEvent(Boolean annotate)
12731 {
12732     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12733         return;
12734     }
12735     if (gameMode != IcsExamining) {
12736         DisplayError(_("You are not examining a game"), 0);
12737         return;
12738     }
12739     if (pausing) {
12740         DisplayError(_("You can't revert while pausing"), 0);
12741         return;
12742     }
12743     SendToICS(ics_prefix);
12744     SendToICS("revert\n");
12745 }
12746
12747 void
12748 RetractMoveEvent()
12749 {
12750     switch (gameMode) {
12751       case MachinePlaysWhite:
12752       case MachinePlaysBlack:
12753         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12754             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12755             return;
12756         }
12757         if (forwardMostMove < 2) return;
12758         currentMove = forwardMostMove = forwardMostMove - 2;
12759         whiteTimeRemaining = timeRemaining[0][currentMove];
12760         blackTimeRemaining = timeRemaining[1][currentMove];
12761         DisplayBothClocks();
12762         DisplayMove(currentMove - 1);
12763         ClearHighlights();/*!! could figure this out*/
12764         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12765         SendToProgram("remove\n", &first);
12766         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12767         break;
12768
12769       case BeginningOfGame:
12770       default:
12771         break;
12772
12773       case IcsPlayingWhite:
12774       case IcsPlayingBlack:
12775         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12776             SendToICS(ics_prefix);
12777             SendToICS("takeback 2\n");
12778         } else {
12779             SendToICS(ics_prefix);
12780             SendToICS("takeback 1\n");
12781         }
12782         break;
12783     }
12784 }
12785
12786 void
12787 MoveNowEvent()
12788 {
12789     ChessProgramState *cps;
12790
12791     switch (gameMode) {
12792       case MachinePlaysWhite:
12793         if (!WhiteOnMove(forwardMostMove)) {
12794             DisplayError(_("It is your turn"), 0);
12795             return;
12796         }
12797         cps = &first;
12798         break;
12799       case MachinePlaysBlack:
12800         if (WhiteOnMove(forwardMostMove)) {
12801             DisplayError(_("It is your turn"), 0);
12802             return;
12803         }
12804         cps = &first;
12805         break;
12806       case TwoMachinesPlay:
12807         if (WhiteOnMove(forwardMostMove) ==
12808             (first.twoMachinesColor[0] == 'w')) {
12809             cps = &first;
12810         } else {
12811             cps = &second;
12812         }
12813         break;
12814       case BeginningOfGame:
12815       default:
12816         return;
12817     }
12818     SendToProgram("?\n", cps);
12819 }
12820
12821 void
12822 TruncateGameEvent()
12823 {
12824     EditGameEvent();
12825     if (gameMode != EditGame) return;
12826     TruncateGame();
12827 }
12828
12829 void
12830 TruncateGame()
12831 {
12832     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12833     if (forwardMostMove > currentMove) {
12834         if (gameInfo.resultDetails != NULL) {
12835             free(gameInfo.resultDetails);
12836             gameInfo.resultDetails = NULL;
12837             gameInfo.result = GameUnfinished;
12838         }
12839         forwardMostMove = currentMove;
12840         HistorySet(parseList, backwardMostMove, forwardMostMove,
12841                    currentMove-1);
12842     }
12843 }
12844
12845 void
12846 HintEvent()
12847 {
12848     if (appData.noChessProgram) return;
12849     switch (gameMode) {
12850       case MachinePlaysWhite:
12851         if (WhiteOnMove(forwardMostMove)) {
12852             DisplayError(_("Wait until your turn"), 0);
12853             return;
12854         }
12855         break;
12856       case BeginningOfGame:
12857       case MachinePlaysBlack:
12858         if (!WhiteOnMove(forwardMostMove)) {
12859             DisplayError(_("Wait until your turn"), 0);
12860             return;
12861         }
12862         break;
12863       default:
12864         DisplayError(_("No hint available"), 0);
12865         return;
12866     }
12867     SendToProgram("hint\n", &first);
12868     hintRequested = TRUE;
12869 }
12870
12871 void
12872 BookEvent()
12873 {
12874     if (appData.noChessProgram) return;
12875     switch (gameMode) {
12876       case MachinePlaysWhite:
12877         if (WhiteOnMove(forwardMostMove)) {
12878             DisplayError(_("Wait until your turn"), 0);
12879             return;
12880         }
12881         break;
12882       case BeginningOfGame:
12883       case MachinePlaysBlack:
12884         if (!WhiteOnMove(forwardMostMove)) {
12885             DisplayError(_("Wait until your turn"), 0);
12886             return;
12887         }
12888         break;
12889       case EditPosition:
12890         EditPositionDone(TRUE);
12891         break;
12892       case TwoMachinesPlay:
12893         return;
12894       default:
12895         break;
12896     }
12897     SendToProgram("bk\n", &first);
12898     bookOutput[0] = NULLCHAR;
12899     bookRequested = TRUE;
12900 }
12901
12902 void
12903 AboutGameEvent()
12904 {
12905     char *tags = PGNTags(&gameInfo);
12906     TagsPopUp(tags, CmailMsg());
12907     free(tags);
12908 }
12909
12910 /* end button procedures */
12911
12912 void
12913 PrintPosition(fp, move)
12914      FILE *fp;
12915      int move;
12916 {
12917     int i, j;
12918     
12919     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12920         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12921             char c = PieceToChar(boards[move][i][j]);
12922             fputc(c == 'x' ? '.' : c, fp);
12923             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12924         }
12925     }
12926     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12927       fprintf(fp, "white to play\n");
12928     else
12929       fprintf(fp, "black to play\n");
12930 }
12931
12932 void
12933 PrintOpponents(fp)
12934      FILE *fp;
12935 {
12936     if (gameInfo.white != NULL) {
12937         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12938     } else {
12939         fprintf(fp, "\n");
12940     }
12941 }
12942
12943 /* Find last component of program's own name, using some heuristics */
12944 void
12945 TidyProgramName(prog, host, buf)
12946      char *prog, *host, buf[MSG_SIZ];
12947 {
12948     char *p, *q;
12949     int local = (strcmp(host, "localhost") == 0);
12950     while (!local && (p = strchr(prog, ';')) != NULL) {
12951         p++;
12952         while (*p == ' ') p++;
12953         prog = p;
12954     }
12955     if (*prog == '"' || *prog == '\'') {
12956         q = strchr(prog + 1, *prog);
12957     } else {
12958         q = strchr(prog, ' ');
12959     }
12960     if (q == NULL) q = prog + strlen(prog);
12961     p = q;
12962     while (p >= prog && *p != '/' && *p != '\\') p--;
12963     p++;
12964     if(p == prog && *p == '"') p++;
12965     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12966     memcpy(buf, p, q - p);
12967     buf[q - p] = NULLCHAR;
12968     if (!local) {
12969         strcat(buf, "@");
12970         strcat(buf, host);
12971     }
12972 }
12973
12974 char *
12975 TimeControlTagValue()
12976 {
12977     char buf[MSG_SIZ];
12978     if (!appData.clockMode) {
12979         strcpy(buf, "-");
12980     } else if (movesPerSession > 0) {
12981         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12982     } else if (timeIncrement == 0) {
12983         sprintf(buf, "%ld", timeControl/1000);
12984     } else {
12985         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12986     }
12987     return StrSave(buf);
12988 }
12989
12990 void
12991 SetGameInfo()
12992 {
12993     /* This routine is used only for certain modes */
12994     VariantClass v = gameInfo.variant;
12995     ChessMove r = GameUnfinished;
12996     char *p = NULL;
12997
12998     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12999         r = gameInfo.result; 
13000         p = gameInfo.resultDetails; 
13001         gameInfo.resultDetails = NULL;
13002     }
13003     ClearGameInfo(&gameInfo);
13004     gameInfo.variant = v;
13005
13006     switch (gameMode) {
13007       case MachinePlaysWhite:
13008         gameInfo.event = StrSave( appData.pgnEventHeader );
13009         gameInfo.site = StrSave(HostName());
13010         gameInfo.date = PGNDate();
13011         gameInfo.round = StrSave("-");
13012         gameInfo.white = StrSave(first.tidy);
13013         gameInfo.black = StrSave(UserName());
13014         gameInfo.timeControl = TimeControlTagValue();
13015         break;
13016
13017       case MachinePlaysBlack:
13018         gameInfo.event = StrSave( appData.pgnEventHeader );
13019         gameInfo.site = StrSave(HostName());
13020         gameInfo.date = PGNDate();
13021         gameInfo.round = StrSave("-");
13022         gameInfo.white = StrSave(UserName());
13023         gameInfo.black = StrSave(first.tidy);
13024         gameInfo.timeControl = TimeControlTagValue();
13025         break;
13026
13027       case TwoMachinesPlay:
13028         gameInfo.event = StrSave( appData.pgnEventHeader );
13029         gameInfo.site = StrSave(HostName());
13030         gameInfo.date = PGNDate();
13031         if (matchGame > 0) {
13032             char buf[MSG_SIZ];
13033             sprintf(buf, "%d", matchGame);
13034             gameInfo.round = StrSave(buf);
13035         } else {
13036             gameInfo.round = StrSave("-");
13037         }
13038         if (first.twoMachinesColor[0] == 'w') {
13039             gameInfo.white = StrSave(first.tidy);
13040             gameInfo.black = StrSave(second.tidy);
13041         } else {
13042             gameInfo.white = StrSave(second.tidy);
13043             gameInfo.black = StrSave(first.tidy);
13044         }
13045         gameInfo.timeControl = TimeControlTagValue();
13046         break;
13047
13048       case EditGame:
13049         gameInfo.event = StrSave("Edited game");
13050         gameInfo.site = StrSave(HostName());
13051         gameInfo.date = PGNDate();
13052         gameInfo.round = StrSave("-");
13053         gameInfo.white = StrSave("-");
13054         gameInfo.black = StrSave("-");
13055         gameInfo.result = r;
13056         gameInfo.resultDetails = p;
13057         break;
13058
13059       case EditPosition:
13060         gameInfo.event = StrSave("Edited position");
13061         gameInfo.site = StrSave(HostName());
13062         gameInfo.date = PGNDate();
13063         gameInfo.round = StrSave("-");
13064         gameInfo.white = StrSave("-");
13065         gameInfo.black = StrSave("-");
13066         break;
13067
13068       case IcsPlayingWhite:
13069       case IcsPlayingBlack:
13070       case IcsObserving:
13071       case IcsExamining:
13072         break;
13073
13074       case PlayFromGameFile:
13075         gameInfo.event = StrSave("Game from non-PGN file");
13076         gameInfo.site = StrSave(HostName());
13077         gameInfo.date = PGNDate();
13078         gameInfo.round = StrSave("-");
13079         gameInfo.white = StrSave("?");
13080         gameInfo.black = StrSave("?");
13081         break;
13082
13083       default:
13084         break;
13085     }
13086 }
13087
13088 void
13089 ReplaceComment(index, text)
13090      int index;
13091      char *text;
13092 {
13093     int len;
13094
13095     while (*text == '\n') text++;
13096     len = strlen(text);
13097     while (len > 0 && text[len - 1] == '\n') len--;
13098
13099     if (commentList[index] != NULL)
13100       free(commentList[index]);
13101
13102     if (len == 0) {
13103         commentList[index] = NULL;
13104         return;
13105     }
13106   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13107       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13108       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13109     commentList[index] = (char *) malloc(len + 2);
13110     strncpy(commentList[index], text, len);
13111     commentList[index][len] = '\n';
13112     commentList[index][len + 1] = NULLCHAR;
13113   } else { 
13114     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13115     char *p;
13116     commentList[index] = (char *) malloc(len + 6);
13117     strcpy(commentList[index], "{\n");
13118     strncpy(commentList[index]+2, text, len);
13119     commentList[index][len+2] = NULLCHAR;
13120     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13121     strcat(commentList[index], "\n}\n");
13122   }
13123 }
13124
13125 void
13126 CrushCRs(text)
13127      char *text;
13128 {
13129   char *p = text;
13130   char *q = text;
13131   char ch;
13132
13133   do {
13134     ch = *p++;
13135     if (ch == '\r') continue;
13136     *q++ = ch;
13137   } while (ch != '\0');
13138 }
13139
13140 void
13141 AppendComment(index, text, addBraces)
13142      int index;
13143      char *text;
13144      Boolean addBraces; // [HGM] braces: tells if we should add {}
13145 {
13146     int oldlen, len;
13147     char *old;
13148
13149 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13150     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13151
13152     CrushCRs(text);
13153     while (*text == '\n') text++;
13154     len = strlen(text);
13155     while (len > 0 && text[len - 1] == '\n') len--;
13156
13157     if (len == 0) return;
13158
13159     if (commentList[index] != NULL) {
13160         old = commentList[index];
13161         oldlen = strlen(old);
13162         while(commentList[index][oldlen-1] ==  '\n')
13163           commentList[index][--oldlen] = NULLCHAR;
13164         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13165         strcpy(commentList[index], old);
13166         free(old);
13167         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13168         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13169           if(addBraces) addBraces = FALSE; else { text++; len--; }
13170           while (*text == '\n') { text++; len--; }
13171           commentList[index][--oldlen] = NULLCHAR;
13172       }
13173         if(addBraces) strcat(commentList[index], "\n{\n");
13174         else          strcat(commentList[index], "\n");
13175         strcat(commentList[index], text);
13176         if(addBraces) strcat(commentList[index], "\n}\n");
13177         else          strcat(commentList[index], "\n");
13178     } else {
13179         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13180         if(addBraces)
13181              strcpy(commentList[index], "{\n");
13182         else commentList[index][0] = NULLCHAR;
13183         strcat(commentList[index], text);
13184         strcat(commentList[index], "\n");
13185         if(addBraces) strcat(commentList[index], "}\n");
13186     }
13187 }
13188
13189 static char * FindStr( char * text, char * sub_text )
13190 {
13191     char * result = strstr( text, sub_text );
13192
13193     if( result != NULL ) {
13194         result += strlen( sub_text );
13195     }
13196
13197     return result;
13198 }
13199
13200 /* [AS] Try to extract PV info from PGN comment */
13201 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13202 char *GetInfoFromComment( int index, char * text )
13203 {
13204     char * sep = text;
13205
13206     if( text != NULL && index > 0 ) {
13207         int score = 0;
13208         int depth = 0;
13209         int time = -1, sec = 0, deci;
13210         char * s_eval = FindStr( text, "[%eval " );
13211         char * s_emt = FindStr( text, "[%emt " );
13212
13213         if( s_eval != NULL || s_emt != NULL ) {
13214             /* New style */
13215             char delim;
13216
13217             if( s_eval != NULL ) {
13218                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13219                     return text;
13220                 }
13221
13222                 if( delim != ']' ) {
13223                     return text;
13224                 }
13225             }
13226
13227             if( s_emt != NULL ) {
13228             }
13229                 return text;
13230         }
13231         else {
13232             /* We expect something like: [+|-]nnn.nn/dd */
13233             int score_lo = 0;
13234
13235             if(*text != '{') return text; // [HGM] braces: must be normal comment
13236
13237             sep = strchr( text, '/' );
13238             if( sep == NULL || sep < (text+4) ) {
13239                 return text;
13240             }
13241
13242             time = -1; sec = -1; deci = -1;
13243             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13244                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13245                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13246                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13247                 return text;
13248             }
13249
13250             if( score_lo < 0 || score_lo >= 100 ) {
13251                 return text;
13252             }
13253
13254             if(sec >= 0) time = 600*time + 10*sec; else
13255             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13256
13257             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13258
13259             /* [HGM] PV time: now locate end of PV info */
13260             while( *++sep >= '0' && *sep <= '9'); // strip depth
13261             if(time >= 0)
13262             while( *++sep >= '0' && *sep <= '9'); // strip time
13263             if(sec >= 0)
13264             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13265             if(deci >= 0)
13266             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13267             while(*sep == ' ') sep++;
13268         }
13269
13270         if( depth <= 0 ) {
13271             return text;
13272         }
13273
13274         if( time < 0 ) {
13275             time = -1;
13276         }
13277
13278         pvInfoList[index-1].depth = depth;
13279         pvInfoList[index-1].score = score;
13280         pvInfoList[index-1].time  = 10*time; // centi-sec
13281         if(*sep == '}') *sep = 0; else *--sep = '{';
13282     }
13283     return sep;
13284 }
13285
13286 void
13287 SendToProgram(message, cps)
13288      char *message;
13289      ChessProgramState *cps;
13290 {
13291     int count, outCount, error;
13292     char buf[MSG_SIZ];
13293
13294     if (cps->pr == NULL) return;
13295     Attention(cps);
13296     
13297     if (appData.debugMode) {
13298         TimeMark now;
13299         GetTimeMark(&now);
13300         fprintf(debugFP, "%ld >%-6s: %s", 
13301                 SubtractTimeMarks(&now, &programStartTime),
13302                 cps->which, message);
13303     }
13304     
13305     count = strlen(message);
13306     outCount = OutputToProcess(cps->pr, message, count, &error);
13307     if (outCount < count && !exiting 
13308                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13309         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13310         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13311             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13312                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13313                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13314             } else {
13315                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13316             }
13317             gameInfo.resultDetails = StrSave(buf);
13318         }
13319         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13320     }
13321 }
13322
13323 void
13324 ReceiveFromProgram(isr, closure, message, count, error)
13325      InputSourceRef isr;
13326      VOIDSTAR closure;
13327      char *message;
13328      int count;
13329      int error;
13330 {
13331     char *end_str;
13332     char buf[MSG_SIZ];
13333     ChessProgramState *cps = (ChessProgramState *)closure;
13334
13335     if (isr != cps->isr) return; /* Killed intentionally */
13336     if (count <= 0) {
13337         if (count == 0) {
13338             sprintf(buf,
13339                     _("Error: %s chess program (%s) exited unexpectedly"),
13340                     cps->which, cps->program);
13341         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13342                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13343                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13344                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13345                 } else {
13346                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13347                 }
13348                 gameInfo.resultDetails = StrSave(buf);
13349             }
13350             RemoveInputSource(cps->isr);
13351             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13352         } else {
13353             sprintf(buf,
13354                     _("Error reading from %s chess program (%s)"),
13355                     cps->which, cps->program);
13356             RemoveInputSource(cps->isr);
13357
13358             /* [AS] Program is misbehaving badly... kill it */
13359             if( count == -2 ) {
13360                 DestroyChildProcess( cps->pr, 9 );
13361                 cps->pr = NoProc;
13362             }
13363
13364             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13365         }
13366         return;
13367     }
13368     
13369     if ((end_str = strchr(message, '\r')) != NULL)
13370       *end_str = NULLCHAR;
13371     if ((end_str = strchr(message, '\n')) != NULL)
13372       *end_str = NULLCHAR;
13373     
13374     if (appData.debugMode) {
13375         TimeMark now; int print = 1;
13376         char *quote = ""; char c; int i;
13377
13378         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13379                 char start = message[0];
13380                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13381                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13382                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13383                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13384                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13385                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13386                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13387                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13388                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13389                     print = (appData.engineComments >= 2);
13390                 }
13391                 message[0] = start; // restore original message
13392         }
13393         if(print) {
13394                 GetTimeMark(&now);
13395                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13396                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13397                         quote,
13398                         message);
13399         }
13400     }
13401
13402     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13403     if (appData.icsEngineAnalyze) {
13404         if (strstr(message, "whisper") != NULL ||
13405              strstr(message, "kibitz") != NULL || 
13406             strstr(message, "tellics") != NULL) return;
13407     }
13408
13409     HandleMachineMove(message, cps);
13410 }
13411
13412
13413 void
13414 SendTimeControl(cps, mps, tc, inc, sd, st)
13415      ChessProgramState *cps;
13416      int mps, inc, sd, st;
13417      long tc;
13418 {
13419     char buf[MSG_SIZ];
13420     int seconds;
13421
13422     if( timeControl_2 > 0 ) {
13423         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13424             tc = timeControl_2;
13425         }
13426     }
13427     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13428     inc /= cps->timeOdds;
13429     st  /= cps->timeOdds;
13430
13431     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13432
13433     if (st > 0) {
13434       /* Set exact time per move, normally using st command */
13435       if (cps->stKludge) {
13436         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13437         seconds = st % 60;
13438         if (seconds == 0) {
13439           sprintf(buf, "level 1 %d\n", st/60);
13440         } else {
13441           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13442         }
13443       } else {
13444         sprintf(buf, "st %d\n", st);
13445       }
13446     } else {
13447       /* Set conventional or incremental time control, using level command */
13448       if (seconds == 0) {
13449         /* Note old gnuchess bug -- minutes:seconds used to not work.
13450            Fixed in later versions, but still avoid :seconds
13451            when seconds is 0. */
13452         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13453       } else {
13454         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13455                 seconds, inc/1000);
13456       }
13457     }
13458     SendToProgram(buf, cps);
13459
13460     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13461     /* Orthogonally, limit search to given depth */
13462     if (sd > 0) {
13463       if (cps->sdKludge) {
13464         sprintf(buf, "depth\n%d\n", sd);
13465       } else {
13466         sprintf(buf, "sd %d\n", sd);
13467       }
13468       SendToProgram(buf, cps);
13469     }
13470
13471     if(cps->nps > 0) { /* [HGM] nps */
13472         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13473         else {
13474                 sprintf(buf, "nps %d\n", cps->nps);
13475               SendToProgram(buf, cps);
13476         }
13477     }
13478 }
13479
13480 ChessProgramState *WhitePlayer()
13481 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13482 {
13483     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13484        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13485         return &second;
13486     return &first;
13487 }
13488
13489 void
13490 SendTimeRemaining(cps, machineWhite)
13491      ChessProgramState *cps;
13492      int /*boolean*/ machineWhite;
13493 {
13494     char message[MSG_SIZ];
13495     long time, otime;
13496
13497     /* Note: this routine must be called when the clocks are stopped
13498        or when they have *just* been set or switched; otherwise
13499        it will be off by the time since the current tick started.
13500     */
13501     if (machineWhite) {
13502         time = whiteTimeRemaining / 10;
13503         otime = blackTimeRemaining / 10;
13504     } else {
13505         time = blackTimeRemaining / 10;
13506         otime = whiteTimeRemaining / 10;
13507     }
13508     /* [HGM] translate opponent's time by time-odds factor */
13509     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13510     if (appData.debugMode) {
13511         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13512     }
13513
13514     if (time <= 0) time = 1;
13515     if (otime <= 0) otime = 1;
13516     
13517     sprintf(message, "time %ld\n", time);
13518     SendToProgram(message, cps);
13519
13520     sprintf(message, "otim %ld\n", otime);
13521     SendToProgram(message, cps);
13522 }
13523
13524 int
13525 BoolFeature(p, name, loc, cps)
13526      char **p;
13527      char *name;
13528      int *loc;
13529      ChessProgramState *cps;
13530 {
13531   char buf[MSG_SIZ];
13532   int len = strlen(name);
13533   int val;
13534   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13535     (*p) += len + 1;
13536     sscanf(*p, "%d", &val);
13537     *loc = (val != 0);
13538     while (**p && **p != ' ') (*p)++;
13539     sprintf(buf, "accepted %s\n", name);
13540     SendToProgram(buf, cps);
13541     return TRUE;
13542   }
13543   return FALSE;
13544 }
13545
13546 int
13547 IntFeature(p, name, loc, cps)
13548      char **p;
13549      char *name;
13550      int *loc;
13551      ChessProgramState *cps;
13552 {
13553   char buf[MSG_SIZ];
13554   int len = strlen(name);
13555   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13556     (*p) += len + 1;
13557     sscanf(*p, "%d", loc);
13558     while (**p && **p != ' ') (*p)++;
13559     sprintf(buf, "accepted %s\n", name);
13560     SendToProgram(buf, cps);
13561     return TRUE;
13562   }
13563   return FALSE;
13564 }
13565
13566 int
13567 StringFeature(p, name, loc, cps)
13568      char **p;
13569      char *name;
13570      char loc[];
13571      ChessProgramState *cps;
13572 {
13573   char buf[MSG_SIZ];
13574   int len = strlen(name);
13575   if (strncmp((*p), name, len) == 0
13576       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13577     (*p) += len + 2;
13578     sscanf(*p, "%[^\"]", loc);
13579     while (**p && **p != '\"') (*p)++;
13580     if (**p == '\"') (*p)++;
13581     sprintf(buf, "accepted %s\n", name);
13582     SendToProgram(buf, cps);
13583     return TRUE;
13584   }
13585   return FALSE;
13586 }
13587
13588 int 
13589 ParseOption(Option *opt, ChessProgramState *cps)
13590 // [HGM] options: process the string that defines an engine option, and determine
13591 // name, type, default value, and allowed value range
13592 {
13593         char *p, *q, buf[MSG_SIZ];
13594         int n, min = (-1)<<31, max = 1<<31, def;
13595
13596         if(p = strstr(opt->name, " -spin ")) {
13597             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598             if(max < min) max = min; // enforce consistency
13599             if(def < min) def = min;
13600             if(def > max) def = max;
13601             opt->value = def;
13602             opt->min = min;
13603             opt->max = max;
13604             opt->type = Spin;
13605         } else if((p = strstr(opt->name, " -slider "))) {
13606             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13607             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13608             if(max < min) max = min; // enforce consistency
13609             if(def < min) def = min;
13610             if(def > max) def = max;
13611             opt->value = def;
13612             opt->min = min;
13613             opt->max = max;
13614             opt->type = Spin; // Slider;
13615         } else if((p = strstr(opt->name, " -string "))) {
13616             opt->textValue = p+9;
13617             opt->type = TextBox;
13618         } else if((p = strstr(opt->name, " -file "))) {
13619             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13620             opt->textValue = p+7;
13621             opt->type = TextBox; // FileName;
13622         } else if((p = strstr(opt->name, " -path "))) {
13623             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13624             opt->textValue = p+7;
13625             opt->type = TextBox; // PathName;
13626         } else if(p = strstr(opt->name, " -check ")) {
13627             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13628             opt->value = (def != 0);
13629             opt->type = CheckBox;
13630         } else if(p = strstr(opt->name, " -combo ")) {
13631             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13632             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13633             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13634             opt->value = n = 0;
13635             while(q = StrStr(q, " /// ")) {
13636                 n++; *q = 0;    // count choices, and null-terminate each of them
13637                 q += 5;
13638                 if(*q == '*') { // remember default, which is marked with * prefix
13639                     q++;
13640                     opt->value = n;
13641                 }
13642                 cps->comboList[cps->comboCnt++] = q;
13643             }
13644             cps->comboList[cps->comboCnt++] = NULL;
13645             opt->max = n + 1;
13646             opt->type = ComboBox;
13647         } else if(p = strstr(opt->name, " -button")) {
13648             opt->type = Button;
13649         } else if(p = strstr(opt->name, " -save")) {
13650             opt->type = SaveButton;
13651         } else return FALSE;
13652         *p = 0; // terminate option name
13653         // now look if the command-line options define a setting for this engine option.
13654         if(cps->optionSettings && cps->optionSettings[0])
13655             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13656         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13657                 sprintf(buf, "option %s", p);
13658                 if(p = strstr(buf, ",")) *p = 0;
13659                 strcat(buf, "\n");
13660                 SendToProgram(buf, cps);
13661         }
13662         return TRUE;
13663 }
13664
13665 void
13666 FeatureDone(cps, val)
13667      ChessProgramState* cps;
13668      int val;
13669 {
13670   DelayedEventCallback cb = GetDelayedEvent();
13671   if ((cb == InitBackEnd3 && cps == &first) ||
13672       (cb == TwoMachinesEventIfReady && cps == &second)) {
13673     CancelDelayedEvent();
13674     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13675   }
13676   cps->initDone = val;
13677 }
13678
13679 /* Parse feature command from engine */
13680 void
13681 ParseFeatures(args, cps)
13682      char* args;
13683      ChessProgramState *cps;  
13684 {
13685   char *p = args;
13686   char *q;
13687   int val;
13688   char buf[MSG_SIZ];
13689
13690   for (;;) {
13691     while (*p == ' ') p++;
13692     if (*p == NULLCHAR) return;
13693
13694     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13695     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13696     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13697     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13698     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13699     if (BoolFeature(&p, "reuse", &val, cps)) {
13700       /* Engine can disable reuse, but can't enable it if user said no */
13701       if (!val) cps->reuse = FALSE;
13702       continue;
13703     }
13704     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13705     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13706       if (gameMode == TwoMachinesPlay) {
13707         DisplayTwoMachinesTitle();
13708       } else {
13709         DisplayTitle("");
13710       }
13711       continue;
13712     }
13713     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13714     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13715     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13716     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13717     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13718     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13719     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13720     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13721     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13722     if (IntFeature(&p, "done", &val, cps)) {
13723       FeatureDone(cps, val);
13724       continue;
13725     }
13726     /* Added by Tord: */
13727     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13728     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13729     /* End of additions by Tord */
13730
13731     /* [HGM] added features: */
13732     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13733     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13734     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13735     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13736     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13737     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13738     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13739         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13740             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13741             SendToProgram(buf, cps);
13742             continue;
13743         }
13744         if(cps->nrOptions >= MAX_OPTIONS) {
13745             cps->nrOptions--;
13746             sprintf(buf, "%s engine has too many options\n", cps->which);
13747             DisplayError(buf, 0);
13748         }
13749         continue;
13750     }
13751     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13752     /* End of additions by HGM */
13753
13754     /* unknown feature: complain and skip */
13755     q = p;
13756     while (*q && *q != '=') q++;
13757     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13758     SendToProgram(buf, cps);
13759     p = q;
13760     if (*p == '=') {
13761       p++;
13762       if (*p == '\"') {
13763         p++;
13764         while (*p && *p != '\"') p++;
13765         if (*p == '\"') p++;
13766       } else {
13767         while (*p && *p != ' ') p++;
13768       }
13769     }
13770   }
13771
13772 }
13773
13774 void
13775 PeriodicUpdatesEvent(newState)
13776      int newState;
13777 {
13778     if (newState == appData.periodicUpdates)
13779       return;
13780
13781     appData.periodicUpdates=newState;
13782
13783     /* Display type changes, so update it now */
13784 //    DisplayAnalysis();
13785
13786     /* Get the ball rolling again... */
13787     if (newState) {
13788         AnalysisPeriodicEvent(1);
13789         StartAnalysisClock();
13790     }
13791 }
13792
13793 void
13794 PonderNextMoveEvent(newState)
13795      int newState;
13796 {
13797     if (newState == appData.ponderNextMove) return;
13798     if (gameMode == EditPosition) EditPositionDone(TRUE);
13799     if (newState) {
13800         SendToProgram("hard\n", &first);
13801         if (gameMode == TwoMachinesPlay) {
13802             SendToProgram("hard\n", &second);
13803         }
13804     } else {
13805         SendToProgram("easy\n", &first);
13806         thinkOutput[0] = NULLCHAR;
13807         if (gameMode == TwoMachinesPlay) {
13808             SendToProgram("easy\n", &second);
13809         }
13810     }
13811     appData.ponderNextMove = newState;
13812 }
13813
13814 void
13815 NewSettingEvent(option, command, value)
13816      char *command;
13817      int option, value;
13818 {
13819     char buf[MSG_SIZ];
13820
13821     if (gameMode == EditPosition) EditPositionDone(TRUE);
13822     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13823     SendToProgram(buf, &first);
13824     if (gameMode == TwoMachinesPlay) {
13825         SendToProgram(buf, &second);
13826     }
13827 }
13828
13829 void
13830 ShowThinkingEvent()
13831 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13832 {
13833     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13834     int newState = appData.showThinking
13835         // [HGM] thinking: other features now need thinking output as well
13836         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13837     
13838     if (oldState == newState) return;
13839     oldState = newState;
13840     if (gameMode == EditPosition) EditPositionDone(TRUE);
13841     if (oldState) {
13842         SendToProgram("post\n", &first);
13843         if (gameMode == TwoMachinesPlay) {
13844             SendToProgram("post\n", &second);
13845         }
13846     } else {
13847         SendToProgram("nopost\n", &first);
13848         thinkOutput[0] = NULLCHAR;
13849         if (gameMode == TwoMachinesPlay) {
13850             SendToProgram("nopost\n", &second);
13851         }
13852     }
13853 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13854 }
13855
13856 void
13857 AskQuestionEvent(title, question, replyPrefix, which)
13858      char *title; char *question; char *replyPrefix; char *which;
13859 {
13860   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13861   if (pr == NoProc) return;
13862   AskQuestion(title, question, replyPrefix, pr);
13863 }
13864
13865 void
13866 DisplayMove(moveNumber)
13867      int moveNumber;
13868 {
13869     char message[MSG_SIZ];
13870     char res[MSG_SIZ];
13871     char cpThinkOutput[MSG_SIZ];
13872
13873     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13874     
13875     if (moveNumber == forwardMostMove - 1 || 
13876         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13877
13878         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13879
13880         if (strchr(cpThinkOutput, '\n')) {
13881             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13882         }
13883     } else {
13884         *cpThinkOutput = NULLCHAR;
13885     }
13886
13887     /* [AS] Hide thinking from human user */
13888     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13889         *cpThinkOutput = NULLCHAR;
13890         if( thinkOutput[0] != NULLCHAR ) {
13891             int i;
13892
13893             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13894                 cpThinkOutput[i] = '.';
13895             }
13896             cpThinkOutput[i] = NULLCHAR;
13897             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13898         }
13899     }
13900
13901     if (moveNumber == forwardMostMove - 1 &&
13902         gameInfo.resultDetails != NULL) {
13903         if (gameInfo.resultDetails[0] == NULLCHAR) {
13904             sprintf(res, " %s", PGNResult(gameInfo.result));
13905         } else {
13906             sprintf(res, " {%s} %s",
13907                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13908         }
13909     } else {
13910         res[0] = NULLCHAR;
13911     }
13912
13913     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13914         DisplayMessage(res, cpThinkOutput);
13915     } else {
13916         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13917                 WhiteOnMove(moveNumber) ? " " : ".. ",
13918                 parseList[moveNumber], res);
13919         DisplayMessage(message, cpThinkOutput);
13920     }
13921 }
13922
13923 void
13924 DisplayComment(moveNumber, text)
13925      int moveNumber;
13926      char *text;
13927 {
13928     char title[MSG_SIZ];
13929     char buf[8000]; // comment can be long!
13930     int score, depth;
13931     
13932     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13933       strcpy(title, "Comment");
13934     } else {
13935       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13936               WhiteOnMove(moveNumber) ? " " : ".. ",
13937               parseList[moveNumber]);
13938     }
13939     // [HGM] PV info: display PV info together with (or as) comment
13940     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13941       if(text == NULL) text = "";                                           
13942       score = pvInfoList[moveNumber].score;
13943       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13944               depth, (pvInfoList[moveNumber].time+50)/100, text);
13945       text = buf;
13946     }
13947     if (text != NULL && (appData.autoDisplayComment || commentUp))
13948         CommentPopUp(title, text);
13949 }
13950
13951 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13952  * might be busy thinking or pondering.  It can be omitted if your
13953  * gnuchess is configured to stop thinking immediately on any user
13954  * input.  However, that gnuchess feature depends on the FIONREAD
13955  * ioctl, which does not work properly on some flavors of Unix.
13956  */
13957 void
13958 Attention(cps)
13959      ChessProgramState *cps;
13960 {
13961 #if ATTENTION
13962     if (!cps->useSigint) return;
13963     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13964     switch (gameMode) {
13965       case MachinePlaysWhite:
13966       case MachinePlaysBlack:
13967       case TwoMachinesPlay:
13968       case IcsPlayingWhite:
13969       case IcsPlayingBlack:
13970       case AnalyzeMode:
13971       case AnalyzeFile:
13972         /* Skip if we know it isn't thinking */
13973         if (!cps->maybeThinking) return;
13974         if (appData.debugMode)
13975           fprintf(debugFP, "Interrupting %s\n", cps->which);
13976         InterruptChildProcess(cps->pr);
13977         cps->maybeThinking = FALSE;
13978         break;
13979       default:
13980         break;
13981     }
13982 #endif /*ATTENTION*/
13983 }
13984
13985 int
13986 CheckFlags()
13987 {
13988     if (whiteTimeRemaining <= 0) {
13989         if (!whiteFlag) {
13990             whiteFlag = TRUE;
13991             if (appData.icsActive) {
13992                 if (appData.autoCallFlag &&
13993                     gameMode == IcsPlayingBlack && !blackFlag) {
13994                   SendToICS(ics_prefix);
13995                   SendToICS("flag\n");
13996                 }
13997             } else {
13998                 if (blackFlag) {
13999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14000                 } else {
14001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14002                     if (appData.autoCallFlag) {
14003                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14004                         return TRUE;
14005                     }
14006                 }
14007             }
14008         }
14009     }
14010     if (blackTimeRemaining <= 0) {
14011         if (!blackFlag) {
14012             blackFlag = TRUE;
14013             if (appData.icsActive) {
14014                 if (appData.autoCallFlag &&
14015                     gameMode == IcsPlayingWhite && !whiteFlag) {
14016                   SendToICS(ics_prefix);
14017                   SendToICS("flag\n");
14018                 }
14019             } else {
14020                 if (whiteFlag) {
14021                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14022                 } else {
14023                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14024                     if (appData.autoCallFlag) {
14025                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14026                         return TRUE;
14027                     }
14028                 }
14029             }
14030         }
14031     }
14032     return FALSE;
14033 }
14034
14035 void
14036 CheckTimeControl()
14037 {
14038     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14039         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14040
14041     /*
14042      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14043      */
14044     if ( !WhiteOnMove(forwardMostMove) )
14045         /* White made time control */
14046         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14047         /* [HGM] time odds: correct new time quota for time odds! */
14048                                             / WhitePlayer()->timeOdds;
14049       else
14050         /* Black made time control */
14051         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14052                                             / WhitePlayer()->other->timeOdds;
14053 }
14054
14055 void
14056 DisplayBothClocks()
14057 {
14058     int wom = gameMode == EditPosition ?
14059       !blackPlaysFirst : WhiteOnMove(currentMove);
14060     DisplayWhiteClock(whiteTimeRemaining, wom);
14061     DisplayBlackClock(blackTimeRemaining, !wom);
14062 }
14063
14064
14065 /* Timekeeping seems to be a portability nightmare.  I think everyone
14066    has ftime(), but I'm really not sure, so I'm including some ifdefs
14067    to use other calls if you don't.  Clocks will be less accurate if
14068    you have neither ftime nor gettimeofday.
14069 */
14070
14071 /* VS 2008 requires the #include outside of the function */
14072 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14073 #include <sys/timeb.h>
14074 #endif
14075
14076 /* Get the current time as a TimeMark */
14077 void
14078 GetTimeMark(tm)
14079      TimeMark *tm;
14080 {
14081 #if HAVE_GETTIMEOFDAY
14082
14083     struct timeval timeVal;
14084     struct timezone timeZone;
14085
14086     gettimeofday(&timeVal, &timeZone);
14087     tm->sec = (long) timeVal.tv_sec; 
14088     tm->ms = (int) (timeVal.tv_usec / 1000L);
14089
14090 #else /*!HAVE_GETTIMEOFDAY*/
14091 #if HAVE_FTIME
14092
14093 // include <sys/timeb.h> / moved to just above start of function
14094     struct timeb timeB;
14095
14096     ftime(&timeB);
14097     tm->sec = (long) timeB.time;
14098     tm->ms = (int) timeB.millitm;
14099
14100 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14101     tm->sec = (long) time(NULL);
14102     tm->ms = 0;
14103 #endif
14104 #endif
14105 }
14106
14107 /* Return the difference in milliseconds between two
14108    time marks.  We assume the difference will fit in a long!
14109 */
14110 long
14111 SubtractTimeMarks(tm2, tm1)
14112      TimeMark *tm2, *tm1;
14113 {
14114     return 1000L*(tm2->sec - tm1->sec) +
14115            (long) (tm2->ms - tm1->ms);
14116 }
14117
14118
14119 /*
14120  * Code to manage the game clocks.
14121  *
14122  * In tournament play, black starts the clock and then white makes a move.
14123  * We give the human user a slight advantage if he is playing white---the
14124  * clocks don't run until he makes his first move, so it takes zero time.
14125  * Also, we don't account for network lag, so we could get out of sync
14126  * with GNU Chess's clock -- but then, referees are always right.  
14127  */
14128
14129 static TimeMark tickStartTM;
14130 static long intendedTickLength;
14131
14132 long
14133 NextTickLength(timeRemaining)
14134      long timeRemaining;
14135 {
14136     long nominalTickLength, nextTickLength;
14137
14138     if (timeRemaining > 0L && timeRemaining <= 10000L)
14139       nominalTickLength = 100L;
14140     else
14141       nominalTickLength = 1000L;
14142     nextTickLength = timeRemaining % nominalTickLength;
14143     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14144
14145     return nextTickLength;
14146 }
14147
14148 /* Adjust clock one minute up or down */
14149 void
14150 AdjustClock(Boolean which, int dir)
14151 {
14152     if(which) blackTimeRemaining += 60000*dir;
14153     else      whiteTimeRemaining += 60000*dir;
14154     DisplayBothClocks();
14155 }
14156
14157 /* Stop clocks and reset to a fresh time control */
14158 void
14159 ResetClocks() 
14160 {
14161     (void) StopClockTimer();
14162     if (appData.icsActive) {
14163         whiteTimeRemaining = blackTimeRemaining = 0;
14164     } else if (searchTime) {
14165         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14166         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14167     } else { /* [HGM] correct new time quote for time odds */
14168         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14169         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14170     }
14171     if (whiteFlag || blackFlag) {
14172         DisplayTitle("");
14173         whiteFlag = blackFlag = FALSE;
14174     }
14175     DisplayBothClocks();
14176 }
14177
14178 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14179
14180 /* Decrement running clock by amount of time that has passed */
14181 void
14182 DecrementClocks()
14183 {
14184     long timeRemaining;
14185     long lastTickLength, fudge;
14186     TimeMark now;
14187
14188     if (!appData.clockMode) return;
14189     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14190         
14191     GetTimeMark(&now);
14192
14193     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14194
14195     /* Fudge if we woke up a little too soon */
14196     fudge = intendedTickLength - lastTickLength;
14197     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14198
14199     if (WhiteOnMove(forwardMostMove)) {
14200         if(whiteNPS >= 0) lastTickLength = 0;
14201         timeRemaining = whiteTimeRemaining -= lastTickLength;
14202         DisplayWhiteClock(whiteTimeRemaining - fudge,
14203                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14204     } else {
14205         if(blackNPS >= 0) lastTickLength = 0;
14206         timeRemaining = blackTimeRemaining -= lastTickLength;
14207         DisplayBlackClock(blackTimeRemaining - fudge,
14208                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14209     }
14210
14211     if (CheckFlags()) return;
14212         
14213     tickStartTM = now;
14214     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14215     StartClockTimer(intendedTickLength);
14216
14217     /* if the time remaining has fallen below the alarm threshold, sound the
14218      * alarm. if the alarm has sounded and (due to a takeback or time control
14219      * with increment) the time remaining has increased to a level above the
14220      * threshold, reset the alarm so it can sound again. 
14221      */
14222     
14223     if (appData.icsActive && appData.icsAlarm) {
14224
14225         /* make sure we are dealing with the user's clock */
14226         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14227                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14228            )) return;
14229
14230         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14231             alarmSounded = FALSE;
14232         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14233             PlayAlarmSound();
14234             alarmSounded = TRUE;
14235         }
14236     }
14237 }
14238
14239
14240 /* A player has just moved, so stop the previously running
14241    clock and (if in clock mode) start the other one.
14242    We redisplay both clocks in case we're in ICS mode, because
14243    ICS gives us an update to both clocks after every move.
14244    Note that this routine is called *after* forwardMostMove
14245    is updated, so the last fractional tick must be subtracted
14246    from the color that is *not* on move now.
14247 */
14248 void
14249 SwitchClocks(int newMoveNr)
14250 {
14251     long lastTickLength;
14252     TimeMark now;
14253     int flagged = FALSE;
14254
14255     GetTimeMark(&now);
14256
14257     if (StopClockTimer() && appData.clockMode) {
14258         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14259         if (!WhiteOnMove(forwardMostMove)) {
14260             if(blackNPS >= 0) lastTickLength = 0;
14261             blackTimeRemaining -= lastTickLength;
14262            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14263 //         if(pvInfoList[forwardMostMove-1].time == -1)
14264                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14265                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14266         } else {
14267            if(whiteNPS >= 0) lastTickLength = 0;
14268            whiteTimeRemaining -= lastTickLength;
14269            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14270 //         if(pvInfoList[forwardMostMove-1].time == -1)
14271                  pvInfoList[forwardMostMove-1].time = 
14272                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14273         }
14274         flagged = CheckFlags();
14275     }
14276     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14277     CheckTimeControl();
14278
14279     if (flagged || !appData.clockMode) return;
14280
14281     switch (gameMode) {
14282       case MachinePlaysBlack:
14283       case MachinePlaysWhite:
14284       case BeginningOfGame:
14285         if (pausing) return;
14286         break;
14287
14288       case EditGame:
14289       case PlayFromGameFile:
14290       case IcsExamining:
14291         return;
14292
14293       default:
14294         break;
14295     }
14296
14297     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14298         if(WhiteOnMove(forwardMostMove))
14299              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14300         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14301     }
14302
14303     tickStartTM = now;
14304     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14305       whiteTimeRemaining : blackTimeRemaining);
14306     StartClockTimer(intendedTickLength);
14307 }
14308         
14309
14310 /* Stop both clocks */
14311 void
14312 StopClocks()
14313 {       
14314     long lastTickLength;
14315     TimeMark now;
14316
14317     if (!StopClockTimer()) return;
14318     if (!appData.clockMode) return;
14319
14320     GetTimeMark(&now);
14321
14322     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14323     if (WhiteOnMove(forwardMostMove)) {
14324         if(whiteNPS >= 0) lastTickLength = 0;
14325         whiteTimeRemaining -= lastTickLength;
14326         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14327     } else {
14328         if(blackNPS >= 0) lastTickLength = 0;
14329         blackTimeRemaining -= lastTickLength;
14330         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14331     }
14332     CheckFlags();
14333 }
14334         
14335 /* Start clock of player on move.  Time may have been reset, so
14336    if clock is already running, stop and restart it. */
14337 void
14338 StartClocks()
14339 {
14340     (void) StopClockTimer(); /* in case it was running already */
14341     DisplayBothClocks();
14342     if (CheckFlags()) return;
14343
14344     if (!appData.clockMode) return;
14345     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14346
14347     GetTimeMark(&tickStartTM);
14348     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14349       whiteTimeRemaining : blackTimeRemaining);
14350
14351    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14352     whiteNPS = blackNPS = -1; 
14353     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14354        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14355         whiteNPS = first.nps;
14356     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14357        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14358         blackNPS = first.nps;
14359     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14360         whiteNPS = second.nps;
14361     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14362         blackNPS = second.nps;
14363     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14364
14365     StartClockTimer(intendedTickLength);
14366 }
14367
14368 char *
14369 TimeString(ms)
14370      long ms;
14371 {
14372     long second, minute, hour, day;
14373     char *sign = "";
14374     static char buf[32];
14375     
14376     if (ms > 0 && ms <= 9900) {
14377       /* convert milliseconds to tenths, rounding up */
14378       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14379
14380       sprintf(buf, " %03.1f ", tenths/10.0);
14381       return buf;
14382     }
14383
14384     /* convert milliseconds to seconds, rounding up */
14385     /* use floating point to avoid strangeness of integer division
14386        with negative dividends on many machines */
14387     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14388
14389     if (second < 0) {
14390         sign = "-";
14391         second = -second;
14392     }
14393     
14394     day = second / (60 * 60 * 24);
14395     second = second % (60 * 60 * 24);
14396     hour = second / (60 * 60);
14397     second = second % (60 * 60);
14398     minute = second / 60;
14399     second = second % 60;
14400     
14401     if (day > 0)
14402       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14403               sign, day, hour, minute, second);
14404     else if (hour > 0)
14405       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14406     else
14407       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14408     
14409     return buf;
14410 }
14411
14412
14413 /*
14414  * This is necessary because some C libraries aren't ANSI C compliant yet.
14415  */
14416 char *
14417 StrStr(string, match)
14418      char *string, *match;
14419 {
14420     int i, length;
14421     
14422     length = strlen(match);
14423     
14424     for (i = strlen(string) - length; i >= 0; i--, string++)
14425       if (!strncmp(match, string, length))
14426         return string;
14427     
14428     return NULL;
14429 }
14430
14431 char *
14432 StrCaseStr(string, match)
14433      char *string, *match;
14434 {
14435     int i, j, length;
14436     
14437     length = strlen(match);
14438     
14439     for (i = strlen(string) - length; i >= 0; i--, string++) {
14440         for (j = 0; j < length; j++) {
14441             if (ToLower(match[j]) != ToLower(string[j]))
14442               break;
14443         }
14444         if (j == length) return string;
14445     }
14446
14447     return NULL;
14448 }
14449
14450 #ifndef _amigados
14451 int
14452 StrCaseCmp(s1, s2)
14453      char *s1, *s2;
14454 {
14455     char c1, c2;
14456     
14457     for (;;) {
14458         c1 = ToLower(*s1++);
14459         c2 = ToLower(*s2++);
14460         if (c1 > c2) return 1;
14461         if (c1 < c2) return -1;
14462         if (c1 == NULLCHAR) return 0;
14463     }
14464 }
14465
14466
14467 int
14468 ToLower(c)
14469      int c;
14470 {
14471     return isupper(c) ? tolower(c) : c;
14472 }
14473
14474
14475 int
14476 ToUpper(c)
14477      int c;
14478 {
14479     return islower(c) ? toupper(c) : c;
14480 }
14481 #endif /* !_amigados    */
14482
14483 char *
14484 StrSave(s)
14485      char *s;
14486 {
14487     char *ret;
14488
14489     if ((ret = (char *) malloc(strlen(s) + 1))) {
14490         strcpy(ret, s);
14491     }
14492     return ret;
14493 }
14494
14495 char *
14496 StrSavePtr(s, savePtr)
14497      char *s, **savePtr;
14498 {
14499     if (*savePtr) {
14500         free(*savePtr);
14501     }
14502     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14503         strcpy(*savePtr, s);
14504     }
14505     return(*savePtr);
14506 }
14507
14508 char *
14509 PGNDate()
14510 {
14511     time_t clock;
14512     struct tm *tm;
14513     char buf[MSG_SIZ];
14514
14515     clock = time((time_t *)NULL);
14516     tm = localtime(&clock);
14517     sprintf(buf, "%04d.%02d.%02d",
14518             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14519     return StrSave(buf);
14520 }
14521
14522
14523 char *
14524 PositionToFEN(move, overrideCastling)
14525      int move;
14526      char *overrideCastling;
14527 {
14528     int i, j, fromX, fromY, toX, toY;
14529     int whiteToPlay;
14530     char buf[128];
14531     char *p, *q;
14532     int emptycount;
14533     ChessSquare piece;
14534
14535     whiteToPlay = (gameMode == EditPosition) ?
14536       !blackPlaysFirst : (move % 2 == 0);
14537     p = buf;
14538
14539     /* Piece placement data */
14540     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14541         emptycount = 0;
14542         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14543             if (boards[move][i][j] == EmptySquare) {
14544                 emptycount++;
14545             } else { ChessSquare piece = boards[move][i][j];
14546                 if (emptycount > 0) {
14547                     if(emptycount<10) /* [HGM] can be >= 10 */
14548                         *p++ = '0' + emptycount;
14549                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14550                     emptycount = 0;
14551                 }
14552                 if(PieceToChar(piece) == '+') {
14553                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14554                     *p++ = '+';
14555                     piece = (ChessSquare)(DEMOTED piece);
14556                 } 
14557                 *p++ = PieceToChar(piece);
14558                 if(p[-1] == '~') {
14559                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14560                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14561                     *p++ = '~';
14562                 }
14563             }
14564         }
14565         if (emptycount > 0) {
14566             if(emptycount<10) /* [HGM] can be >= 10 */
14567                 *p++ = '0' + emptycount;
14568             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14569             emptycount = 0;
14570         }
14571         *p++ = '/';
14572     }
14573     *(p - 1) = ' ';
14574
14575     /* [HGM] print Crazyhouse or Shogi holdings */
14576     if( gameInfo.holdingsWidth ) {
14577         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14578         q = p;
14579         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14580             piece = boards[move][i][BOARD_WIDTH-1];
14581             if( piece != EmptySquare )
14582               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14583                   *p++ = PieceToChar(piece);
14584         }
14585         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14586             piece = boards[move][BOARD_HEIGHT-i-1][0];
14587             if( piece != EmptySquare )
14588               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14589                   *p++ = PieceToChar(piece);
14590         }
14591
14592         if( q == p ) *p++ = '-';
14593         *p++ = ']';
14594         *p++ = ' ';
14595     }
14596
14597     /* Active color */
14598     *p++ = whiteToPlay ? 'w' : 'b';
14599     *p++ = ' ';
14600
14601   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14602     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14603   } else {
14604   if(nrCastlingRights) {
14605      q = p;
14606      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14607        /* [HGM] write directly from rights */
14608            if(boards[move][CASTLING][2] != NoRights &&
14609               boards[move][CASTLING][0] != NoRights   )
14610                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14611            if(boards[move][CASTLING][2] != NoRights &&
14612               boards[move][CASTLING][1] != NoRights   )
14613                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14614            if(boards[move][CASTLING][5] != NoRights &&
14615               boards[move][CASTLING][3] != NoRights   )
14616                 *p++ = boards[move][CASTLING][3] + AAA;
14617            if(boards[move][CASTLING][5] != NoRights &&
14618               boards[move][CASTLING][4] != NoRights   )
14619                 *p++ = boards[move][CASTLING][4] + AAA;
14620      } else {
14621
14622         /* [HGM] write true castling rights */
14623         if( nrCastlingRights == 6 ) {
14624             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14625                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14626             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14627                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14628             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14629                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14630             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14631                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14632         }
14633      }
14634      if (q == p) *p++ = '-'; /* No castling rights */
14635      *p++ = ' ';
14636   }
14637
14638   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14639      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14640     /* En passant target square */
14641     if (move > backwardMostMove) {
14642         fromX = moveList[move - 1][0] - AAA;
14643         fromY = moveList[move - 1][1] - ONE;
14644         toX = moveList[move - 1][2] - AAA;
14645         toY = moveList[move - 1][3] - ONE;
14646         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14647             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14648             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14649             fromX == toX) {
14650             /* 2-square pawn move just happened */
14651             *p++ = toX + AAA;
14652             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14653         } else {
14654             *p++ = '-';
14655         }
14656     } else if(move == backwardMostMove) {
14657         // [HGM] perhaps we should always do it like this, and forget the above?
14658         if((signed char)boards[move][EP_STATUS] >= 0) {
14659             *p++ = boards[move][EP_STATUS] + AAA;
14660             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14661         } else {
14662             *p++ = '-';
14663         }
14664     } else {
14665         *p++ = '-';
14666     }
14667     *p++ = ' ';
14668   }
14669   }
14670
14671     /* [HGM] find reversible plies */
14672     {   int i = 0, j=move;
14673
14674         if (appData.debugMode) { int k;
14675             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14676             for(k=backwardMostMove; k<=forwardMostMove; k++)
14677                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14678
14679         }
14680
14681         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14682         if( j == backwardMostMove ) i += initialRulePlies;
14683         sprintf(p, "%d ", i);
14684         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14685     }
14686     /* Fullmove number */
14687     sprintf(p, "%d", (move / 2) + 1);
14688     
14689     return StrSave(buf);
14690 }
14691
14692 Boolean
14693 ParseFEN(board, blackPlaysFirst, fen)
14694     Board board;
14695      int *blackPlaysFirst;
14696      char *fen;
14697 {
14698     int i, j;
14699     char *p;
14700     int emptycount;
14701     ChessSquare piece;
14702
14703     p = fen;
14704
14705     /* [HGM] by default clear Crazyhouse holdings, if present */
14706     if(gameInfo.holdingsWidth) {
14707        for(i=0; i<BOARD_HEIGHT; i++) {
14708            board[i][0]             = EmptySquare; /* black holdings */
14709            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14710            board[i][1]             = (ChessSquare) 0; /* black counts */
14711            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14712        }
14713     }
14714
14715     /* Piece placement data */
14716     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14717         j = 0;
14718         for (;;) {
14719             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14720                 if (*p == '/') p++;
14721                 emptycount = gameInfo.boardWidth - j;
14722                 while (emptycount--)
14723                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14724                 break;
14725 #if(BOARD_FILES >= 10)
14726             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14727                 p++; emptycount=10;
14728                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14729                 while (emptycount--)
14730                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14731 #endif
14732             } else if (isdigit(*p)) {
14733                 emptycount = *p++ - '0';
14734                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14735                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14736                 while (emptycount--)
14737                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14738             } else if (*p == '+' || isalpha(*p)) {
14739                 if (j >= gameInfo.boardWidth) return FALSE;
14740                 if(*p=='+') {
14741                     piece = CharToPiece(*++p);
14742                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14743                     piece = (ChessSquare) (PROMOTED piece ); p++;
14744                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14745                 } else piece = CharToPiece(*p++);
14746
14747                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14748                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14749                     piece = (ChessSquare) (PROMOTED piece);
14750                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14751                     p++;
14752                 }
14753                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14754             } else {
14755                 return FALSE;
14756             }
14757         }
14758     }
14759     while (*p == '/' || *p == ' ') p++;
14760
14761     /* [HGM] look for Crazyhouse holdings here */
14762     while(*p==' ') p++;
14763     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14764         if(*p == '[') p++;
14765         if(*p == '-' ) *p++; /* empty holdings */ else {
14766             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14767             /* if we would allow FEN reading to set board size, we would   */
14768             /* have to add holdings and shift the board read so far here   */
14769             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14770                 *p++;
14771                 if((int) piece >= (int) BlackPawn ) {
14772                     i = (int)piece - (int)BlackPawn;
14773                     i = PieceToNumber((ChessSquare)i);
14774                     if( i >= gameInfo.holdingsSize ) return FALSE;
14775                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14776                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14777                 } else {
14778                     i = (int)piece - (int)WhitePawn;
14779                     i = PieceToNumber((ChessSquare)i);
14780                     if( i >= gameInfo.holdingsSize ) return FALSE;
14781                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14782                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14783                 }
14784             }
14785         }
14786         if(*p == ']') *p++;
14787     }
14788
14789     while(*p == ' ') p++;
14790
14791     /* Active color */
14792     switch (*p++) {
14793       case 'w':
14794         *blackPlaysFirst = FALSE;
14795         break;
14796       case 'b': 
14797         *blackPlaysFirst = TRUE;
14798         break;
14799       default:
14800         return FALSE;
14801     }
14802
14803     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14804     /* return the extra info in global variiables             */
14805
14806     /* set defaults in case FEN is incomplete */
14807     board[EP_STATUS] = EP_UNKNOWN;
14808     for(i=0; i<nrCastlingRights; i++ ) {
14809         board[CASTLING][i] =
14810             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14811     }   /* assume possible unless obviously impossible */
14812     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14813     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14814     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14815                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14816     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14817     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14818     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14819                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14820     FENrulePlies = 0;
14821
14822     while(*p==' ') p++;
14823     if(nrCastlingRights) {
14824       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14825           /* castling indicator present, so default becomes no castlings */
14826           for(i=0; i<nrCastlingRights; i++ ) {
14827                  board[CASTLING][i] = NoRights;
14828           }
14829       }
14830       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14831              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14832              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14833              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14834         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14835
14836         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14837             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14838             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14839         }
14840         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14841             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14842         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14843                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14844         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14845                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14846         switch(c) {
14847           case'K':
14848               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14849               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14850               board[CASTLING][2] = whiteKingFile;
14851               break;
14852           case'Q':
14853               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14854               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14855               board[CASTLING][2] = whiteKingFile;
14856               break;
14857           case'k':
14858               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14859               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14860               board[CASTLING][5] = blackKingFile;
14861               break;
14862           case'q':
14863               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14864               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14865               board[CASTLING][5] = blackKingFile;
14866           case '-':
14867               break;
14868           default: /* FRC castlings */
14869               if(c >= 'a') { /* black rights */
14870                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14871                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14872                   if(i == BOARD_RGHT) break;
14873                   board[CASTLING][5] = i;
14874                   c -= AAA;
14875                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14876                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14877                   if(c > i)
14878                       board[CASTLING][3] = c;
14879                   else
14880                       board[CASTLING][4] = c;
14881               } else { /* white rights */
14882                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14883                     if(board[0][i] == WhiteKing) break;
14884                   if(i == BOARD_RGHT) break;
14885                   board[CASTLING][2] = i;
14886                   c -= AAA - 'a' + 'A';
14887                   if(board[0][c] >= WhiteKing) break;
14888                   if(c > i)
14889                       board[CASTLING][0] = c;
14890                   else
14891                       board[CASTLING][1] = c;
14892               }
14893         }
14894       }
14895       for(i=0; i<nrCastlingRights; i++)
14896         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14897     if (appData.debugMode) {
14898         fprintf(debugFP, "FEN castling rights:");
14899         for(i=0; i<nrCastlingRights; i++)
14900         fprintf(debugFP, " %d", board[CASTLING][i]);
14901         fprintf(debugFP, "\n");
14902     }
14903
14904       while(*p==' ') p++;
14905     }
14906
14907     /* read e.p. field in games that know e.p. capture */
14908     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14909        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14910       if(*p=='-') {
14911         p++; board[EP_STATUS] = EP_NONE;
14912       } else {
14913          char c = *p++ - AAA;
14914
14915          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14916          if(*p >= '0' && *p <='9') *p++;
14917          board[EP_STATUS] = c;
14918       }
14919     }
14920
14921
14922     if(sscanf(p, "%d", &i) == 1) {
14923         FENrulePlies = i; /* 50-move ply counter */
14924         /* (The move number is still ignored)    */
14925     }
14926
14927     return TRUE;
14928 }
14929       
14930 void
14931 EditPositionPasteFEN(char *fen)
14932 {
14933   if (fen != NULL) {
14934     Board initial_position;
14935
14936     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14937       DisplayError(_("Bad FEN position in clipboard"), 0);
14938       return ;
14939     } else {
14940       int savedBlackPlaysFirst = blackPlaysFirst;
14941       EditPositionEvent();
14942       blackPlaysFirst = savedBlackPlaysFirst;
14943       CopyBoard(boards[0], initial_position);
14944       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14945       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14946       DisplayBothClocks();
14947       DrawPosition(FALSE, boards[currentMove]);
14948     }
14949   }
14950 }
14951
14952 static char cseq[12] = "\\   ";
14953
14954 Boolean set_cont_sequence(char *new_seq)
14955 {
14956     int len;
14957     Boolean ret;
14958
14959     // handle bad attempts to set the sequence
14960         if (!new_seq)
14961                 return 0; // acceptable error - no debug
14962
14963     len = strlen(new_seq);
14964     ret = (len > 0) && (len < sizeof(cseq));
14965     if (ret)
14966         strcpy(cseq, new_seq);
14967     else if (appData.debugMode)
14968         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14969     return ret;
14970 }
14971
14972 /*
14973     reformat a source message so words don't cross the width boundary.  internal
14974     newlines are not removed.  returns the wrapped size (no null character unless
14975     included in source message).  If dest is NULL, only calculate the size required
14976     for the dest buffer.  lp argument indicats line position upon entry, and it's
14977     passed back upon exit.
14978 */
14979 int wrap(char *dest, char *src, int count, int width, int *lp)
14980 {
14981     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14982
14983     cseq_len = strlen(cseq);
14984     old_line = line = *lp;
14985     ansi = len = clen = 0;
14986
14987     for (i=0; i < count; i++)
14988     {
14989         if (src[i] == '\033')
14990             ansi = 1;
14991
14992         // if we hit the width, back up
14993         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14994         {
14995             // store i & len in case the word is too long
14996             old_i = i, old_len = len;
14997
14998             // find the end of the last word
14999             while (i && src[i] != ' ' && src[i] != '\n')
15000             {
15001                 i--;
15002                 len--;
15003             }
15004
15005             // word too long?  restore i & len before splitting it
15006             if ((old_i-i+clen) >= width)
15007             {
15008                 i = old_i;
15009                 len = old_len;
15010             }
15011
15012             // extra space?
15013             if (i && src[i-1] == ' ')
15014                 len--;
15015
15016             if (src[i] != ' ' && src[i] != '\n')
15017             {
15018                 i--;
15019                 if (len)
15020                     len--;
15021             }
15022
15023             // now append the newline and continuation sequence
15024             if (dest)
15025                 dest[len] = '\n';
15026             len++;
15027             if (dest)
15028                 strncpy(dest+len, cseq, cseq_len);
15029             len += cseq_len;
15030             line = cseq_len;
15031             clen = cseq_len;
15032             continue;
15033         }
15034
15035         if (dest)
15036             dest[len] = src[i];
15037         len++;
15038         if (!ansi)
15039             line++;
15040         if (src[i] == '\n')
15041             line = 0;
15042         if (src[i] == 'm')
15043             ansi = 0;
15044     }
15045     if (dest && appData.debugMode)
15046     {
15047         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15048             count, width, line, len, *lp);
15049         show_bytes(debugFP, src, count);
15050         fprintf(debugFP, "\ndest: ");
15051         show_bytes(debugFP, dest, len);
15052         fprintf(debugFP, "\n");
15053     }
15054     *lp = dest ? line : old_line;
15055
15056     return len;
15057 }
15058
15059 // [HGM] vari: routines for shelving variations
15060
15061 void 
15062 PushTail(int firstMove, int lastMove)
15063 {
15064         int i, j, nrMoves = lastMove - firstMove;
15065
15066         if(appData.icsActive) { // only in local mode
15067                 forwardMostMove = currentMove; // mimic old ICS behavior
15068                 return;
15069         }
15070         if(storedGames >= MAX_VARIATIONS-1) return;
15071
15072         // push current tail of game on stack
15073         savedResult[storedGames] = gameInfo.result;
15074         savedDetails[storedGames] = gameInfo.resultDetails;
15075         gameInfo.resultDetails = NULL;
15076         savedFirst[storedGames] = firstMove;
15077         savedLast [storedGames] = lastMove;
15078         savedFramePtr[storedGames] = framePtr;
15079         framePtr -= nrMoves; // reserve space for the boards
15080         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15081             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15082             for(j=0; j<MOVE_LEN; j++)
15083                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15084             for(j=0; j<2*MOVE_LEN; j++)
15085                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15086             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15087             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15088             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15089             pvInfoList[firstMove+i-1].depth = 0;
15090             commentList[framePtr+i] = commentList[firstMove+i];
15091             commentList[firstMove+i] = NULL;
15092         }
15093
15094         storedGames++;
15095         forwardMostMove = firstMove; // truncate game so we can start variation
15096         if(storedGames == 1) GreyRevert(FALSE);
15097 }
15098
15099 Boolean
15100 PopTail(Boolean annotate)
15101 {
15102         int i, j, nrMoves;
15103         char buf[8000], moveBuf[20];
15104
15105         if(appData.icsActive) return FALSE; // only in local mode
15106         if(!storedGames) return FALSE; // sanity
15107         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15108
15109         storedGames--;
15110         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15111         nrMoves = savedLast[storedGames] - currentMove;
15112         if(annotate) {
15113                 int cnt = 10;
15114                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15115                 else strcpy(buf, "(");
15116                 for(i=currentMove; i<forwardMostMove; i++) {
15117                         if(WhiteOnMove(i))
15118                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15119                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15120                         strcat(buf, moveBuf);
15121                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15122                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15123                 }
15124                 strcat(buf, ")");
15125         }
15126         for(i=1; i<=nrMoves; i++) { // copy last variation back
15127             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15128             for(j=0; j<MOVE_LEN; j++)
15129                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15130             for(j=0; j<2*MOVE_LEN; j++)
15131                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15132             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15133             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15134             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15135             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15136             commentList[currentMove+i] = commentList[framePtr+i];
15137             commentList[framePtr+i] = NULL;
15138         }
15139         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15140         framePtr = savedFramePtr[storedGames];
15141         gameInfo.result = savedResult[storedGames];
15142         if(gameInfo.resultDetails != NULL) {
15143             free(gameInfo.resultDetails);
15144       }
15145         gameInfo.resultDetails = savedDetails[storedGames];
15146         forwardMostMove = currentMove + nrMoves;
15147         if(storedGames == 0) GreyRevert(TRUE);
15148         return TRUE;
15149 }
15150
15151 void 
15152 CleanupTail()
15153 {       // remove all shelved variations
15154         int i;
15155         for(i=0; i<storedGames; i++) {
15156             if(savedDetails[i])
15157                 free(savedDetails[i]);
15158             savedDetails[i] = NULL;
15159         }
15160         for(i=framePtr; i<MAX_MOVES; i++) {
15161                 if(commentList[i]) free(commentList[i]);
15162                 commentList[i] = NULL;
15163         }
15164         framePtr = MAX_MOVES-1;
15165         storedGames = 0;
15166 }
15167
15168 void
15169 LoadVariation(int index, char *text)
15170 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15171         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15172         int level = 0, move;
15173
15174         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15175         // first find outermost bracketing variation
15176         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15177             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15178                 if(*p == '{') wait = '}'; else
15179                 if(*p == '[') wait = ']'; else
15180                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15181                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15182             }
15183             if(*p == wait) wait = NULLCHAR; // closing ]} found
15184             p++;
15185         }
15186         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15187         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15188         end[1] = NULLCHAR; // clip off comment beyond variation
15189         ToNrEvent(currentMove-1);
15190         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15191         // kludge: use ParsePV() to append variation to game
15192         move = currentMove;
15193         ParsePV(start, TRUE);
15194         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15195         ClearPremoveHighlights();
15196         CommentPopDown();
15197         ToNrEvent(currentMove+1);
15198 }