Redraw second board on expose events
[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: ") &&
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, "* shouts:") ||
2748                                            looking_at(buf, &i, "* c-shouts:") ||
2749                                            looking_at(buf, &i, "--> * ") ||
2750                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2751                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2752                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2753                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2754                 int p;
2755                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2756                 chattingPartner = -1;
2757
2758                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2759                 for(p=0; p<MAX_CHAT; p++) {
2760                     if(channel == atoi(chatPartner[p])) {
2761                     talker[0] = '['; strcat(talker, "] ");
2762                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2763                     chattingPartner = p; break;
2764                     }
2765                 } else
2766                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2767                 for(p=0; p<MAX_CHAT; p++) {
2768                     if(!strcmp("whispers", chatPartner[p])) {
2769                         talker[0] = '['; strcat(talker, "] ");
2770                         chattingPartner = p; break;
2771                     }
2772                 } else
2773                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2774                 for(p=0; p<MAX_CHAT; p++) {
2775                     if(!strcmp("shouts", chatPartner[p])) {
2776                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2777                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2778                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2779                         chattingPartner = p; break;
2780                     }
2781                 }
2782                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2783                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2784                     talker[0] = 0; Colorize(ColorTell, FALSE);
2785                     chattingPartner = p; break;
2786                 }
2787                 if(chattingPartner<0) i = oldi; else {
2788                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2789                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2790                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                     started = STARTED_COMMENT;
2792                     parse_pos = 0; parse[0] = NULLCHAR;
2793                     savingComment = 3 + chattingPartner; // counts as TRUE
2794                     suppressKibitz = TRUE;
2795                     continue;
2796                 }
2797             } // [HGM] chat: end of patch
2798
2799             if (appData.zippyTalk || appData.zippyPlay) {
2800                 /* [DM] Backup address for color zippy lines */
2801                 backup = i;
2802 #if ZIPPY
2803        #ifdef WIN32
2804                if (loggedOn == TRUE)
2805                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2806                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2807        #else
2808                 if (ZippyControl(buf, &i) ||
2809                     ZippyConverse(buf, &i) ||
2810                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2811                       loggedOn = TRUE;
2812                       if (!appData.colorize) continue;
2813                 }
2814        #endif
2815 #endif
2816             } // [DM] 'else { ' deleted
2817                 if (
2818                     /* Regular tells and says */
2819                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2820                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2821                     looking_at(buf, &i, "* says: ") ||
2822                     /* Don't color "message" or "messages" output */
2823                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2824                     looking_at(buf, &i, "*. * at *:*: ") ||
2825                     looking_at(buf, &i, "--* (*:*): ") ||
2826                     /* Message notifications (same color as tells) */
2827                     looking_at(buf, &i, "* has left a message ") ||
2828                     looking_at(buf, &i, "* just sent you a message:\n") ||
2829                     /* Whispers and kibitzes */
2830                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2831                     looking_at(buf, &i, "* kibitzes: ") ||
2832                     /* Channel tells */
2833                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2834
2835                   if (tkind == 1 && strchr(star_match[0], ':')) {
2836                       /* Avoid "tells you:" spoofs in channels */
2837                      tkind = 3;
2838                   }
2839                   if (star_match[0][0] == NULLCHAR ||
2840                       strchr(star_match[0], ' ') ||
2841                       (tkind == 3 && strchr(star_match[1], ' '))) {
2842                     /* Reject bogus matches */
2843                     i = oldi;
2844                   } else {
2845                     if (appData.colorize) {
2846                       if (oldi > next_out) {
2847                         SendToPlayer(&buf[next_out], oldi - next_out);
2848                         next_out = oldi;
2849                       }
2850                       switch (tkind) {
2851                       case 1:
2852                         Colorize(ColorTell, FALSE);
2853                         curColor = ColorTell;
2854                         break;
2855                       case 2:
2856                         Colorize(ColorKibitz, FALSE);
2857                         curColor = ColorKibitz;
2858                         break;
2859                       case 3:
2860                         p = strrchr(star_match[1], '(');
2861                         if (p == NULL) {
2862                           p = star_match[1];
2863                         } else {
2864                           p++;
2865                         }
2866                         if (atoi(p) == 1) {
2867                           Colorize(ColorChannel1, FALSE);
2868                           curColor = ColorChannel1;
2869                         } else {
2870                           Colorize(ColorChannel, FALSE);
2871                           curColor = ColorChannel;
2872                         }
2873                         break;
2874                       case 5:
2875                         curColor = ColorNormal;
2876                         break;
2877                       }
2878                     }
2879                     if (started == STARTED_NONE && appData.autoComment &&
2880                         (gameMode == IcsObserving ||
2881                          gameMode == IcsPlayingWhite ||
2882                          gameMode == IcsPlayingBlack)) {
2883                       parse_pos = i - oldi;
2884                       memcpy(parse, &buf[oldi], parse_pos);
2885                       parse[parse_pos] = NULLCHAR;
2886                       started = STARTED_COMMENT;
2887                       savingComment = TRUE;
2888                     } else {
2889                       started = STARTED_CHATTER;
2890                       savingComment = FALSE;
2891                     }
2892                     loggedOn = TRUE;
2893                     continue;
2894                   }
2895                 }
2896
2897                 if (looking_at(buf, &i, "* s-shouts: ") ||
2898                     looking_at(buf, &i, "* c-shouts: ")) {
2899                     if (appData.colorize) {
2900                         if (oldi > next_out) {
2901                             SendToPlayer(&buf[next_out], oldi - next_out);
2902                             next_out = oldi;
2903                         }
2904                         Colorize(ColorSShout, FALSE);
2905                         curColor = ColorSShout;
2906                     }
2907                     loggedOn = TRUE;
2908                     started = STARTED_CHATTER;
2909                     continue;
2910                 }
2911
2912                 if (looking_at(buf, &i, "--->")) {
2913                     loggedOn = TRUE;
2914                     continue;
2915                 }
2916
2917                 if (looking_at(buf, &i, "* shouts: ") ||
2918                     looking_at(buf, &i, "--> ")) {
2919                     if (appData.colorize) {
2920                         if (oldi > next_out) {
2921                             SendToPlayer(&buf[next_out], oldi - next_out);
2922                             next_out = oldi;
2923                         }
2924                         Colorize(ColorShout, FALSE);
2925                         curColor = ColorShout;
2926                     }
2927                     loggedOn = TRUE;
2928                     started = STARTED_CHATTER;
2929                     continue;
2930                 }
2931
2932                 if (looking_at( buf, &i, "Challenge:")) {
2933                     if (appData.colorize) {
2934                         if (oldi > next_out) {
2935                             SendToPlayer(&buf[next_out], oldi - next_out);
2936                             next_out = oldi;
2937                         }
2938                         Colorize(ColorChallenge, FALSE);
2939                         curColor = ColorChallenge;
2940                     }
2941                     loggedOn = TRUE;
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "* offers you") ||
2946                     looking_at(buf, &i, "* offers to be") ||
2947                     looking_at(buf, &i, "* would like to") ||
2948                     looking_at(buf, &i, "* requests to") ||
2949                     looking_at(buf, &i, "Your opponent offers") ||
2950                     looking_at(buf, &i, "Your opponent requests")) {
2951
2952                     if (appData.colorize) {
2953                         if (oldi > next_out) {
2954                             SendToPlayer(&buf[next_out], oldi - next_out);
2955                             next_out = oldi;
2956                         }
2957                         Colorize(ColorRequest, FALSE);
2958                         curColor = ColorRequest;
2959                     }
2960                     continue;
2961                 }
2962
2963                 if (looking_at(buf, &i, "* (*) seeking")) {
2964                     if (appData.colorize) {
2965                         if (oldi > next_out) {
2966                             SendToPlayer(&buf[next_out], oldi - next_out);
2967                             next_out = oldi;
2968                         }
2969                         Colorize(ColorSeek, FALSE);
2970                         curColor = ColorSeek;
2971                     }
2972                     continue;
2973             }
2974
2975             if (looking_at(buf, &i, "\\   ")) {
2976                 if (prevColor != ColorNormal) {
2977                     if (oldi > next_out) {
2978                         SendToPlayer(&buf[next_out], oldi - next_out);
2979                         next_out = oldi;
2980                     }
2981                     Colorize(prevColor, TRUE);
2982                     curColor = prevColor;
2983                 }
2984                 if (savingComment) {
2985                     parse_pos = i - oldi;
2986                     memcpy(parse, &buf[oldi], parse_pos);
2987                     parse[parse_pos] = NULLCHAR;
2988                     started = STARTED_COMMENT;
2989                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2990                         chattingPartner = savingComment - 3; // kludge to remember the box
2991                 } else {
2992                     started = STARTED_CHATTER;
2993                 }
2994                 continue;
2995             }
2996
2997             if (looking_at(buf, &i, "Black Strength :") ||
2998                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2999                 looking_at(buf, &i, "<10>") ||
3000                 looking_at(buf, &i, "#@#")) {
3001                 /* Wrong board style */
3002                 loggedOn = TRUE;
3003                 SendToICS(ics_prefix);
3004                 SendToICS("set style 12\n");
3005                 SendToICS(ics_prefix);
3006                 SendToICS("refresh\n");
3007                 continue;
3008             }
3009             
3010             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3011                 ICSInitScript();
3012                 have_sent_ICS_logon = 1;
3013                 continue;
3014             }
3015               
3016             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3017                 (looking_at(buf, &i, "\n<12> ") ||
3018                  looking_at(buf, &i, "<12> "))) {
3019                 loggedOn = TRUE;
3020                 if (oldi > next_out) {
3021                     SendToPlayer(&buf[next_out], oldi - next_out);
3022                 }
3023                 next_out = i;
3024                 started = STARTED_BOARD;
3025                 parse_pos = 0;
3026                 continue;
3027             }
3028
3029             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3030                 looking_at(buf, &i, "<b1> ")) {
3031                 if (oldi > next_out) {
3032                     SendToPlayer(&buf[next_out], oldi - next_out);
3033                 }
3034                 next_out = i;
3035                 started = STARTED_HOLDINGS;
3036                 parse_pos = 0;
3037                 continue;
3038             }
3039
3040             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3041                 loggedOn = TRUE;
3042                 /* Header for a move list -- first line */
3043
3044                 switch (ics_getting_history) {
3045                   case H_FALSE:
3046                     switch (gameMode) {
3047                       case IcsIdle:
3048                       case BeginningOfGame:
3049                         /* User typed "moves" or "oldmoves" while we
3050                            were idle.  Pretend we asked for these
3051                            moves and soak them up so user can step
3052                            through them and/or save them.
3053                            */
3054                         Reset(FALSE, TRUE);
3055                         gameMode = IcsObserving;
3056                         ModeHighlight();
3057                         ics_gamenum = -1;
3058                         ics_getting_history = H_GOT_UNREQ_HEADER;
3059                         break;
3060                       case EditGame: /*?*/
3061                       case EditPosition: /*?*/
3062                         /* Should above feature work in these modes too? */
3063                         /* For now it doesn't */
3064                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3065                         break;
3066                       default:
3067                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3068                         break;
3069                     }
3070                     break;
3071                   case H_REQUESTED:
3072                     /* Is this the right one? */
3073                     if (gameInfo.white && gameInfo.black &&
3074                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3075                         strcmp(gameInfo.black, star_match[2]) == 0) {
3076                         /* All is well */
3077                         ics_getting_history = H_GOT_REQ_HEADER;
3078                     }
3079                     break;
3080                   case H_GOT_REQ_HEADER:
3081                   case H_GOT_UNREQ_HEADER:
3082                   case H_GOT_UNWANTED_HEADER:
3083                   case H_GETTING_MOVES:
3084                     /* Should not happen */
3085                     DisplayError(_("Error gathering move list: two headers"), 0);
3086                     ics_getting_history = H_FALSE;
3087                     break;
3088                 }
3089
3090                 /* Save player ratings into gameInfo if needed */
3091                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3092                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3093                     (gameInfo.whiteRating == -1 ||
3094                      gameInfo.blackRating == -1)) {
3095
3096                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3097                     gameInfo.blackRating = string_to_rating(star_match[3]);
3098                     if (appData.debugMode)
3099                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3100                               gameInfo.whiteRating, gameInfo.blackRating);
3101                 }
3102                 continue;
3103             }
3104
3105             if (looking_at(buf, &i,
3106               "* * match, initial time: * minute*, increment: * second")) {
3107                 /* Header for a move list -- second line */
3108                 /* Initial board will follow if this is a wild game */
3109                 if (gameInfo.event != NULL) free(gameInfo.event);
3110                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3111                 gameInfo.event = StrSave(str);
3112                 /* [HGM] we switched variant. Translate boards if needed. */
3113                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3114                 continue;
3115             }
3116
3117             if (looking_at(buf, &i, "Move  ")) {
3118                 /* Beginning of a move list */
3119                 switch (ics_getting_history) {
3120                   case H_FALSE:
3121                     /* Normally should not happen */
3122                     /* Maybe user hit reset while we were parsing */
3123                     break;
3124                   case H_REQUESTED:
3125                     /* Happens if we are ignoring a move list that is not
3126                      * the one we just requested.  Common if the user
3127                      * tries to observe two games without turning off
3128                      * getMoveList */
3129                     break;
3130                   case H_GETTING_MOVES:
3131                     /* Should not happen */
3132                     DisplayError(_("Error gathering move list: nested"), 0);
3133                     ics_getting_history = H_FALSE;
3134                     break;
3135                   case H_GOT_REQ_HEADER:
3136                     ics_getting_history = H_GETTING_MOVES;
3137                     started = STARTED_MOVES;
3138                     parse_pos = 0;
3139                     if (oldi > next_out) {
3140                         SendToPlayer(&buf[next_out], oldi - next_out);
3141                     }
3142                     break;
3143                   case H_GOT_UNREQ_HEADER:
3144                     ics_getting_history = H_GETTING_MOVES;
3145                     started = STARTED_MOVES_NOHIDE;
3146                     parse_pos = 0;
3147                     break;
3148                   case H_GOT_UNWANTED_HEADER:
3149                     ics_getting_history = H_FALSE;
3150                     break;
3151                 }
3152                 continue;
3153             }                           
3154             
3155             if (looking_at(buf, &i, "% ") ||
3156                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3157                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3158                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3159                     soughtPending = FALSE;
3160                     seekGraphUp = TRUE;
3161                     DrawSeekGraph();
3162                 }
3163                 if(suppressKibitz) next_out = i;
3164                 savingComment = FALSE;
3165                 suppressKibitz = 0;
3166                 switch (started) {
3167                   case STARTED_MOVES:
3168                   case STARTED_MOVES_NOHIDE:
3169                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3170                     parse[parse_pos + i - oldi] = NULLCHAR;
3171                     ParseGameHistory(parse);
3172 #if ZIPPY
3173                     if (appData.zippyPlay && first.initDone) {
3174                         FeedMovesToProgram(&first, forwardMostMove);
3175                         if (gameMode == IcsPlayingWhite) {
3176                             if (WhiteOnMove(forwardMostMove)) {
3177                                 if (first.sendTime) {
3178                                   if (first.useColors) {
3179                                     SendToProgram("black\n", &first); 
3180                                   }
3181                                   SendTimeRemaining(&first, TRUE);
3182                                 }
3183                                 if (first.useColors) {
3184                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3185                                 }
3186                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3187                                 first.maybeThinking = TRUE;
3188                             } else {
3189                                 if (first.usePlayother) {
3190                                   if (first.sendTime) {
3191                                     SendTimeRemaining(&first, TRUE);
3192                                   }
3193                                   SendToProgram("playother\n", &first);
3194                                   firstMove = FALSE;
3195                                 } else {
3196                                   firstMove = TRUE;
3197                                 }
3198                             }
3199                         } else if (gameMode == IcsPlayingBlack) {
3200                             if (!WhiteOnMove(forwardMostMove)) {
3201                                 if (first.sendTime) {
3202                                   if (first.useColors) {
3203                                     SendToProgram("white\n", &first);
3204                                   }
3205                                   SendTimeRemaining(&first, FALSE);
3206                                 }
3207                                 if (first.useColors) {
3208                                   SendToProgram("black\n", &first);
3209                                 }
3210                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3211                                 first.maybeThinking = TRUE;
3212                             } else {
3213                                 if (first.usePlayother) {
3214                                   if (first.sendTime) {
3215                                     SendTimeRemaining(&first, FALSE);
3216                                   }
3217                                   SendToProgram("playother\n", &first);
3218                                   firstMove = FALSE;
3219                                 } else {
3220                                   firstMove = TRUE;
3221                                 }
3222                             }
3223                         }                       
3224                     }
3225 #endif
3226                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3227                         /* Moves came from oldmoves or moves command
3228                            while we weren't doing anything else.
3229                            */
3230                         currentMove = forwardMostMove;
3231                         ClearHighlights();/*!!could figure this out*/
3232                         flipView = appData.flipView;
3233                         DrawPosition(TRUE, boards[currentMove]);
3234                         DisplayBothClocks();
3235                         sprintf(str, "%s vs. %s",
3236                                 gameInfo.white, gameInfo.black);
3237                         DisplayTitle(str);
3238                         gameMode = IcsIdle;
3239                     } else {
3240                         /* Moves were history of an active game */
3241                         if (gameInfo.resultDetails != NULL) {
3242                             free(gameInfo.resultDetails);
3243                             gameInfo.resultDetails = NULL;
3244                         }
3245                     }
3246                     HistorySet(parseList, backwardMostMove,
3247                                forwardMostMove, currentMove-1);
3248                     DisplayMove(currentMove - 1);
3249                     if (started == STARTED_MOVES) next_out = i;
3250                     started = STARTED_NONE;
3251                     ics_getting_history = H_FALSE;
3252                     break;
3253
3254                   case STARTED_OBSERVE:
3255                     started = STARTED_NONE;
3256                     SendToICS(ics_prefix);
3257                     SendToICS("refresh\n");
3258                     break;
3259
3260                   default:
3261                     break;
3262                 }
3263                 if(bookHit) { // [HGM] book: simulate book reply
3264                     static char bookMove[MSG_SIZ]; // a bit generous?
3265
3266                     programStats.nodes = programStats.depth = programStats.time = 
3267                     programStats.score = programStats.got_only_move = 0;
3268                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3269
3270                     strcpy(bookMove, "move ");
3271                     strcat(bookMove, bookHit);
3272                     HandleMachineMove(bookMove, &first);
3273                 }
3274                 continue;
3275             }
3276             
3277             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3278                  started == STARTED_HOLDINGS ||
3279                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3280                 /* Accumulate characters in move list or board */
3281                 parse[parse_pos++] = buf[i];
3282             }
3283             
3284             /* Start of game messages.  Mostly we detect start of game
3285                when the first board image arrives.  On some versions
3286                of the ICS, though, we need to do a "refresh" after starting
3287                to observe in order to get the current board right away. */
3288             if (looking_at(buf, &i, "Adding game * to observation list")) {
3289                 started = STARTED_OBSERVE;
3290                 continue;
3291             }
3292
3293             /* Handle auto-observe */
3294             if (appData.autoObserve &&
3295                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3296                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3297                 char *player;
3298                 /* Choose the player that was highlighted, if any. */
3299                 if (star_match[0][0] == '\033' ||
3300                     star_match[1][0] != '\033') {
3301                     player = star_match[0];
3302                 } else {
3303                     player = star_match[2];
3304                 }
3305                 sprintf(str, "%sobserve %s\n",
3306                         ics_prefix, StripHighlightAndTitle(player));
3307                 SendToICS(str);
3308
3309                 /* Save ratings from notify string */
3310                 strcpy(player1Name, star_match[0]);
3311                 player1Rating = string_to_rating(star_match[1]);
3312                 strcpy(player2Name, star_match[2]);
3313                 player2Rating = string_to_rating(star_match[3]);
3314
3315                 if (appData.debugMode)
3316                   fprintf(debugFP, 
3317                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3318                           player1Name, player1Rating,
3319                           player2Name, player2Rating);
3320
3321                 continue;
3322             }
3323
3324             /* Deal with automatic examine mode after a game,
3325                and with IcsObserving -> IcsExamining transition */
3326             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3327                 looking_at(buf, &i, "has made you an examiner of game *")) {
3328
3329                 int gamenum = atoi(star_match[0]);
3330                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3331                     gamenum == ics_gamenum) {
3332                     /* We were already playing or observing this game;
3333                        no need to refetch history */
3334                     gameMode = IcsExamining;
3335                     if (pausing) {
3336                         pauseExamForwardMostMove = forwardMostMove;
3337                     } else if (currentMove < forwardMostMove) {
3338                         ForwardInner(forwardMostMove);
3339                     }
3340                 } else {
3341                     /* I don't think this case really can happen */
3342                     SendToICS(ics_prefix);
3343                     SendToICS("refresh\n");
3344                 }
3345                 continue;
3346             }    
3347             
3348             /* Error messages */
3349 //          if (ics_user_moved) {
3350             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3351                 if (looking_at(buf, &i, "Illegal move") ||
3352                     looking_at(buf, &i, "Not a legal move") ||
3353                     looking_at(buf, &i, "Your king is in check") ||
3354                     looking_at(buf, &i, "It isn't your turn") ||
3355                     looking_at(buf, &i, "It is not your move")) {
3356                     /* Illegal move */
3357                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3358                         currentMove = forwardMostMove-1;
3359                         DisplayMove(currentMove - 1); /* before DMError */
3360                         DrawPosition(FALSE, boards[currentMove]);
3361                         SwitchClocks(forwardMostMove-1); // [HGM] race
3362                         DisplayBothClocks();
3363                     }
3364                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3365                     ics_user_moved = 0;
3366                     continue;
3367                 }
3368             }
3369
3370             if (looking_at(buf, &i, "still have time") ||
3371                 looking_at(buf, &i, "not out of time") ||
3372                 looking_at(buf, &i, "either player is out of time") ||
3373                 looking_at(buf, &i, "has timeseal; checking")) {
3374                 /* We must have called his flag a little too soon */
3375                 whiteFlag = blackFlag = FALSE;
3376                 continue;
3377             }
3378
3379             if (looking_at(buf, &i, "added * seconds to") ||
3380                 looking_at(buf, &i, "seconds were added to")) {
3381                 /* Update the clocks */
3382                 SendToICS(ics_prefix);
3383                 SendToICS("refresh\n");
3384                 continue;
3385             }
3386
3387             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3388                 ics_clock_paused = TRUE;
3389                 StopClocks();
3390                 continue;
3391             }
3392
3393             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3394                 ics_clock_paused = FALSE;
3395                 StartClocks();
3396                 continue;
3397             }
3398
3399             /* Grab player ratings from the Creating: message.
3400                Note we have to check for the special case when
3401                the ICS inserts things like [white] or [black]. */
3402             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3403                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3404                 /* star_matches:
3405                    0    player 1 name (not necessarily white)
3406                    1    player 1 rating
3407                    2    empty, white, or black (IGNORED)
3408                    3    player 2 name (not necessarily black)
3409                    4    player 2 rating
3410                    
3411                    The names/ratings are sorted out when the game
3412                    actually starts (below).
3413                 */
3414                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3415                 player1Rating = string_to_rating(star_match[1]);
3416                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3417                 player2Rating = string_to_rating(star_match[4]);
3418
3419                 if (appData.debugMode)
3420                   fprintf(debugFP, 
3421                           "Ratings from 'Creating:' %s %d, %s %d\n",
3422                           player1Name, player1Rating,
3423                           player2Name, player2Rating);
3424
3425                 continue;
3426             }
3427             
3428             /* Improved generic start/end-of-game messages */
3429             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3430                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3431                 /* If tkind == 0: */
3432                 /* star_match[0] is the game number */
3433                 /*           [1] is the white player's name */
3434                 /*           [2] is the black player's name */
3435                 /* For end-of-game: */
3436                 /*           [3] is the reason for the game end */
3437                 /*           [4] is a PGN end game-token, preceded by " " */
3438                 /* For start-of-game: */
3439                 /*           [3] begins with "Creating" or "Continuing" */
3440                 /*           [4] is " *" or empty (don't care). */
3441                 int gamenum = atoi(star_match[0]);
3442                 char *whitename, *blackname, *why, *endtoken;
3443                 ChessMove endtype = (ChessMove) 0;
3444
3445                 if (tkind == 0) {
3446                   whitename = star_match[1];
3447                   blackname = star_match[2];
3448                   why = star_match[3];
3449                   endtoken = star_match[4];
3450                 } else {
3451                   whitename = star_match[1];
3452                   blackname = star_match[3];
3453                   why = star_match[5];
3454                   endtoken = star_match[6];
3455                 }
3456
3457                 /* Game start messages */
3458                 if (strncmp(why, "Creating ", 9) == 0 ||
3459                     strncmp(why, "Continuing ", 11) == 0) {
3460                     gs_gamenum = gamenum;
3461                     strcpy(gs_kind, strchr(why, ' ') + 1);
3462                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3463 #if ZIPPY
3464                     if (appData.zippyPlay) {
3465                         ZippyGameStart(whitename, blackname);
3466                     }
3467 #endif /*ZIPPY*/
3468                     partnerBoardValid = FALSE; // [HGM] bughouse
3469                     continue;
3470                 }
3471
3472                 /* Game end messages */
3473                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3474                     ics_gamenum != gamenum) {
3475                     continue;
3476                 }
3477                 while (endtoken[0] == ' ') endtoken++;
3478                 switch (endtoken[0]) {
3479                   case '*':
3480                   default:
3481                     endtype = GameUnfinished;
3482                     break;
3483                   case '0':
3484                     endtype = BlackWins;
3485                     break;
3486                   case '1':
3487                     if (endtoken[1] == '/')
3488                       endtype = GameIsDrawn;
3489                     else
3490                       endtype = WhiteWins;
3491                     break;
3492                 }
3493                 GameEnds(endtype, why, GE_ICS);
3494 #if ZIPPY
3495                 if (appData.zippyPlay && first.initDone) {
3496                     ZippyGameEnd(endtype, why);
3497                     if (first.pr == NULL) {
3498                       /* Start the next process early so that we'll
3499                          be ready for the next challenge */
3500                       StartChessProgram(&first);
3501                     }
3502                     /* Send "new" early, in case this command takes
3503                        a long time to finish, so that we'll be ready
3504                        for the next challenge. */
3505                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3506                     Reset(TRUE, TRUE);
3507                 }
3508 #endif /*ZIPPY*/
3509                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Removing game * from observation") ||
3514                 looking_at(buf, &i, "no longer observing game *") ||
3515                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3516                 if (gameMode == IcsObserving &&
3517                     atoi(star_match[0]) == ics_gamenum)
3518                   {
3519                       /* icsEngineAnalyze */
3520                       if (appData.icsEngineAnalyze) {
3521                             ExitAnalyzeMode();
3522                             ModeHighlight();
3523                       }
3524                       StopClocks();
3525                       gameMode = IcsIdle;
3526                       ics_gamenum = -1;
3527                       ics_user_moved = FALSE;
3528                   }
3529                 continue;
3530             }
3531
3532             if (looking_at(buf, &i, "no longer examining game *")) {
3533                 if (gameMode == IcsExamining &&
3534                     atoi(star_match[0]) == ics_gamenum)
3535                   {
3536                       gameMode = IcsIdle;
3537                       ics_gamenum = -1;
3538                       ics_user_moved = FALSE;
3539                   }
3540                 continue;
3541             }
3542
3543             /* Advance leftover_start past any newlines we find,
3544                so only partial lines can get reparsed */
3545             if (looking_at(buf, &i, "\n")) {
3546                 prevColor = curColor;
3547                 if (curColor != ColorNormal) {
3548                     if (oldi > next_out) {
3549                         SendToPlayer(&buf[next_out], oldi - next_out);
3550                         next_out = oldi;
3551                     }
3552                     Colorize(ColorNormal, FALSE);
3553                     curColor = ColorNormal;
3554                 }
3555                 if (started == STARTED_BOARD) {
3556                     started = STARTED_NONE;
3557                     parse[parse_pos] = NULLCHAR;
3558                     ParseBoard12(parse);
3559                     ics_user_moved = 0;
3560
3561                     /* Send premove here */
3562                     if (appData.premove) {
3563                       char str[MSG_SIZ];
3564                       if (currentMove == 0 &&
3565                           gameMode == IcsPlayingWhite &&
3566                           appData.premoveWhite) {
3567                         sprintf(str, "%s\n", appData.premoveWhiteText);
3568                         if (appData.debugMode)
3569                           fprintf(debugFP, "Sending premove:\n");
3570                         SendToICS(str);
3571                       } else if (currentMove == 1 &&
3572                                  gameMode == IcsPlayingBlack &&
3573                                  appData.premoveBlack) {
3574                         sprintf(str, "%s\n", appData.premoveBlackText);
3575                         if (appData.debugMode)
3576                           fprintf(debugFP, "Sending premove:\n");
3577                         SendToICS(str);
3578                       } else if (gotPremove) {
3579                         gotPremove = 0;
3580                         ClearPremoveHighlights();
3581                         if (appData.debugMode)
3582                           fprintf(debugFP, "Sending premove:\n");
3583                           UserMoveEvent(premoveFromX, premoveFromY, 
3584                                         premoveToX, premoveToY, 
3585                                         premovePromoChar);
3586                       }
3587                     }
3588
3589                     /* Usually suppress following prompt */
3590                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3591                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3592                         if (looking_at(buf, &i, "*% ")) {
3593                             savingComment = FALSE;
3594                             suppressKibitz = 0;
3595                         }
3596                     }
3597                     next_out = i;
3598                 } else if (started == STARTED_HOLDINGS) {
3599                     int gamenum;
3600                     char new_piece[MSG_SIZ];
3601                     started = STARTED_NONE;
3602                     parse[parse_pos] = NULLCHAR;
3603                     if (appData.debugMode)
3604                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3605                                                         parse, currentMove);
3606                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3607                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3608                         if (gameInfo.variant == VariantNormal) {
3609                           /* [HGM] We seem to switch variant during a game!
3610                            * Presumably no holdings were displayed, so we have
3611                            * to move the position two files to the right to
3612                            * create room for them!
3613                            */
3614                           VariantClass newVariant;
3615                           switch(gameInfo.boardWidth) { // base guess on board width
3616                                 case 9:  newVariant = VariantShogi; break;
3617                                 case 10: newVariant = VariantGreat; break;
3618                                 default: newVariant = VariantCrazyhouse; break;
3619                           }
3620                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3621                           /* Get a move list just to see the header, which
3622                              will tell us whether this is really bug or zh */
3623                           if (ics_getting_history == H_FALSE) {
3624                             ics_getting_history = H_REQUESTED;
3625                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3626                             SendToICS(str);
3627                           }
3628                         }
3629                         new_piece[0] = NULLCHAR;
3630                         sscanf(parse, "game %d white [%s black [%s <- %s",
3631                                &gamenum, white_holding, black_holding,
3632                                new_piece);
3633                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3634                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3635                         /* [HGM] copy holdings to board holdings area */
3636                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3637                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3638                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3639 #if ZIPPY
3640                         if (appData.zippyPlay && first.initDone) {
3641                             ZippyHoldings(white_holding, black_holding,
3642                                           new_piece);
3643                         }
3644 #endif /*ZIPPY*/
3645                         if (tinyLayout || smallLayout) {
3646                             char wh[16], bh[16];
3647                             PackHolding(wh, white_holding);
3648                             PackHolding(bh, black_holding);
3649                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3650                                     gameInfo.white, gameInfo.black);
3651                         } else {
3652                             sprintf(str, "%s [%s] vs. %s [%s]",
3653                                     gameInfo.white, white_holding,
3654                                     gameInfo.black, black_holding);
3655                         }
3656                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3657                         DrawPosition(FALSE, boards[currentMove]);
3658                         DisplayTitle(str);
3659                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3660                         sscanf(parse, "game %d white [%s black [%s <- %s",
3661                                &gamenum, white_holding, black_holding,
3662                                new_piece);
3663                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3664                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3665                         /* [HGM] copy holdings to partner-board holdings area */
3666                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3667                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3668                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3669                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3670                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3671                       }
3672                     }
3673                     /* Suppress following prompt */
3674                     if (looking_at(buf, &i, "*% ")) {
3675                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3676                         savingComment = FALSE;
3677                         suppressKibitz = 0;
3678                     }
3679                     next_out = i;
3680                 }
3681                 continue;
3682             }
3683
3684             i++;                /* skip unparsed character and loop back */
3685         }
3686         
3687         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3688 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3689 //          SendToPlayer(&buf[next_out], i - next_out);
3690             started != STARTED_HOLDINGS && leftover_start > next_out) {
3691             SendToPlayer(&buf[next_out], leftover_start - next_out);
3692             next_out = i;
3693         }
3694         
3695         leftover_len = buf_len - leftover_start;
3696         /* if buffer ends with something we couldn't parse,
3697            reparse it after appending the next read */
3698         
3699     } else if (count == 0) {
3700         RemoveInputSource(isr);
3701         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3702     } else {
3703         DisplayFatalError(_("Error reading from ICS"), error, 1);
3704     }
3705 }
3706
3707
3708 /* Board style 12 looks like this:
3709    
3710    <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
3711    
3712  * The "<12> " is stripped before it gets to this routine.  The two
3713  * trailing 0's (flip state and clock ticking) are later addition, and
3714  * some chess servers may not have them, or may have only the first.
3715  * Additional trailing fields may be added in the future.  
3716  */
3717
3718 #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"
3719
3720 #define RELATION_OBSERVING_PLAYED    0
3721 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3722 #define RELATION_PLAYING_MYMOVE      1
3723 #define RELATION_PLAYING_NOTMYMOVE  -1
3724 #define RELATION_EXAMINING           2
3725 #define RELATION_ISOLATED_BOARD     -3
3726 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3727
3728 void
3729 ParseBoard12(string)
3730      char *string;
3731
3732     GameMode newGameMode;
3733     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3734     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3735     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3736     char to_play, board_chars[200];
3737     char move_str[500], str[500], elapsed_time[500];
3738     char black[32], white[32];
3739     Board board;
3740     int prevMove = currentMove;
3741     int ticking = 2;
3742     ChessMove moveType;
3743     int fromX, fromY, toX, toY;
3744     char promoChar;
3745     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3746     char *bookHit = NULL; // [HGM] book
3747     Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3748
3749     fromX = fromY = toX = toY = -1;
3750     
3751     newGame = FALSE;
3752
3753     if (appData.debugMode)
3754       fprintf(debugFP, _("Parsing board: %s\n"), string);
3755
3756     move_str[0] = NULLCHAR;
3757     elapsed_time[0] = NULLCHAR;
3758     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3759         int  i = 0, j;
3760         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3761             if(string[i] == ' ') { ranks++; files = 0; }
3762             else files++;
3763             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3764             i++;
3765         }
3766         for(j = 0; j <i; j++) board_chars[j] = string[j];
3767         board_chars[i] = '\0';
3768         string += i + 1;
3769     }
3770     n = sscanf(string, PATTERN, &to_play, &double_push,
3771                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3772                &gamenum, white, black, &relation, &basetime, &increment,
3773                &white_stren, &black_stren, &white_time, &black_time,
3774                &moveNum, str, elapsed_time, move_str, &ics_flip,
3775                &ticking);
3776
3777     if (n < 21) {
3778         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3779         DisplayError(str, 0);
3780         return;
3781     }
3782
3783     /* Convert the move number to internal form */
3784     moveNum = (moveNum - 1) * 2;
3785     if (to_play == 'B') moveNum++;
3786     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3787       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3788                         0, 1);
3789       return;
3790     }
3791     
3792     switch (relation) {
3793       case RELATION_OBSERVING_PLAYED:
3794       case RELATION_OBSERVING_STATIC:
3795         if (gamenum == -1) {
3796             /* Old ICC buglet */
3797             relation = RELATION_OBSERVING_STATIC;
3798         }
3799         newGameMode = IcsObserving;
3800         break;
3801       case RELATION_PLAYING_MYMOVE:
3802       case RELATION_PLAYING_NOTMYMOVE:
3803         newGameMode =
3804           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3805             IcsPlayingWhite : IcsPlayingBlack;
3806         break;
3807       case RELATION_EXAMINING:
3808         newGameMode = IcsExamining;
3809         break;
3810       case RELATION_ISOLATED_BOARD:
3811       default:
3812         /* Just display this board.  If user was doing something else,
3813            we will forget about it until the next board comes. */ 
3814         newGameMode = IcsIdle;
3815         break;
3816       case RELATION_STARTING_POSITION:
3817         newGameMode = gameMode;
3818         break;
3819     }
3820     
3821     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3822          && newGameMode == IcsObserving && appData.bgObserve) {
3823       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3824       for (k = 0; k < ranks; k++) {
3825         for (j = 0; j < files; j++)
3826           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3827         if(gameInfo.holdingsWidth > 1) {
3828              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3829              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3830         }
3831       }
3832       CopyBoard(partnerBoard, board);
3833       if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3834       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3835       if(partnerUp) DrawPosition(repaint, partnerBoard);
3836       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3837       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3838                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3839       DisplayMessage(partnerStatus, "");
3840         partnerBoardValid = TRUE;
3841       return;
3842     }
3843
3844     /* Modify behavior for initial board display on move listing
3845        of wild games.
3846        */
3847     switch (ics_getting_history) {
3848       case H_FALSE:
3849       case H_REQUESTED:
3850         break;
3851       case H_GOT_REQ_HEADER:
3852       case H_GOT_UNREQ_HEADER:
3853         /* This is the initial position of the current game */
3854         gamenum = ics_gamenum;
3855         moveNum = 0;            /* old ICS bug workaround */
3856         if (to_play == 'B') {
3857           startedFromSetupPosition = TRUE;
3858           blackPlaysFirst = TRUE;
3859           moveNum = 1;
3860           if (forwardMostMove == 0) forwardMostMove = 1;
3861           if (backwardMostMove == 0) backwardMostMove = 1;
3862           if (currentMove == 0) currentMove = 1;
3863         }
3864         newGameMode = gameMode;
3865         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3866         break;
3867       case H_GOT_UNWANTED_HEADER:
3868         /* This is an initial board that we don't want */
3869         return;
3870       case H_GETTING_MOVES:
3871         /* Should not happen */
3872         DisplayError(_("Error gathering move list: extra board"), 0);
3873         ics_getting_history = H_FALSE;
3874         return;
3875     }
3876
3877    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3878                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3879      /* [HGM] We seem to have switched variant unexpectedly
3880       * Try to guess new variant from board size
3881       */
3882           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3883           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3884           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3885           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3886           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3887           if(!weird) newVariant = VariantNormal;
3888           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3889           /* Get a move list just to see the header, which
3890              will tell us whether this is really bug or zh */
3891           if (ics_getting_history == H_FALSE) {
3892             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3893             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3894             SendToICS(str);
3895           }
3896     }
3897     
3898     /* Take action if this is the first board of a new game, or of a
3899        different game than is currently being displayed.  */
3900     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3901         relation == RELATION_ISOLATED_BOARD) {
3902         
3903         /* Forget the old game and get the history (if any) of the new one */
3904         if (gameMode != BeginningOfGame) {
3905           Reset(TRUE, TRUE);
3906         }
3907         newGame = TRUE;
3908         if (appData.autoRaiseBoard) BoardToTop();
3909         prevMove = -3;
3910         if (gamenum == -1) {
3911             newGameMode = IcsIdle;
3912         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3913                    appData.getMoveList && !reqFlag) {
3914             /* Need to get game history */
3915             ics_getting_history = H_REQUESTED;
3916             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3917             SendToICS(str);
3918         }
3919         
3920         /* Initially flip the board to have black on the bottom if playing
3921            black or if the ICS flip flag is set, but let the user change
3922            it with the Flip View button. */
3923         flipView = appData.autoFlipView ? 
3924           (newGameMode == IcsPlayingBlack) || ics_flip :
3925           appData.flipView;
3926         
3927         /* Done with values from previous mode; copy in new ones */
3928         gameMode = newGameMode;
3929         ModeHighlight();
3930         ics_gamenum = gamenum;
3931         if (gamenum == gs_gamenum) {
3932             int klen = strlen(gs_kind);
3933             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3934             sprintf(str, "ICS %s", gs_kind);
3935             gameInfo.event = StrSave(str);
3936         } else {
3937             gameInfo.event = StrSave("ICS game");
3938         }
3939         gameInfo.site = StrSave(appData.icsHost);
3940         gameInfo.date = PGNDate();
3941         gameInfo.round = StrSave("-");
3942         gameInfo.white = StrSave(white);
3943         gameInfo.black = StrSave(black);
3944         timeControl = basetime * 60 * 1000;
3945         timeControl_2 = 0;
3946         timeIncrement = increment * 1000;
3947         movesPerSession = 0;
3948         gameInfo.timeControl = TimeControlTagValue();
3949         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3950   if (appData.debugMode) {
3951     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3952     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3953     setbuf(debugFP, NULL);
3954   }
3955
3956         gameInfo.outOfBook = NULL;
3957         
3958         /* Do we have the ratings? */
3959         if (strcmp(player1Name, white) == 0 &&
3960             strcmp(player2Name, black) == 0) {
3961             if (appData.debugMode)
3962               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3963                       player1Rating, player2Rating);
3964             gameInfo.whiteRating = player1Rating;
3965             gameInfo.blackRating = player2Rating;
3966         } else if (strcmp(player2Name, white) == 0 &&
3967                    strcmp(player1Name, black) == 0) {
3968             if (appData.debugMode)
3969               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3970                       player2Rating, player1Rating);
3971             gameInfo.whiteRating = player2Rating;
3972             gameInfo.blackRating = player1Rating;
3973         }
3974         player1Name[0] = player2Name[0] = NULLCHAR;
3975
3976         /* Silence shouts if requested */
3977         if (appData.quietPlay &&
3978             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3979             SendToICS(ics_prefix);
3980             SendToICS("set shout 0\n");
3981         }
3982     }
3983     
3984     /* Deal with midgame name changes */
3985     if (!newGame) {
3986         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3987             if (gameInfo.white) free(gameInfo.white);
3988             gameInfo.white = StrSave(white);
3989         }
3990         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3991             if (gameInfo.black) free(gameInfo.black);
3992             gameInfo.black = StrSave(black);
3993         }
3994     }
3995     
3996     /* Throw away game result if anything actually changes in examine mode */
3997     if (gameMode == IcsExamining && !newGame) {
3998         gameInfo.result = GameUnfinished;
3999         if (gameInfo.resultDetails != NULL) {
4000             free(gameInfo.resultDetails);
4001             gameInfo.resultDetails = NULL;
4002         }
4003     }
4004     
4005     /* In pausing && IcsExamining mode, we ignore boards coming
4006        in if they are in a different variation than we are. */
4007     if (pauseExamInvalid) return;
4008     if (pausing && gameMode == IcsExamining) {
4009         if (moveNum <= pauseExamForwardMostMove) {
4010             pauseExamInvalid = TRUE;
4011             forwardMostMove = pauseExamForwardMostMove;
4012             return;
4013         }
4014     }
4015     
4016   if (appData.debugMode) {
4017     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4018   }
4019     /* Parse the board */
4020     for (k = 0; k < ranks; k++) {
4021       for (j = 0; j < files; j++)
4022         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4023       if(gameInfo.holdingsWidth > 1) {
4024            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4025            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4026       }
4027     }
4028     CopyBoard(boards[moveNum], board);
4029     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4030     if (moveNum == 0) {
4031         startedFromSetupPosition =
4032           !CompareBoards(board, initialPosition);
4033         if(startedFromSetupPosition)
4034             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4035     }
4036
4037     /* [HGM] Set castling rights. Take the outermost Rooks,
4038        to make it also work for FRC opening positions. Note that board12
4039        is really defective for later FRC positions, as it has no way to
4040        indicate which Rook can castle if they are on the same side of King.
4041        For the initial position we grant rights to the outermost Rooks,
4042        and remember thos rights, and we then copy them on positions
4043        later in an FRC game. This means WB might not recognize castlings with
4044        Rooks that have moved back to their original position as illegal,
4045        but in ICS mode that is not its job anyway.
4046     */
4047     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4048     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4049
4050         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051             if(board[0][i] == WhiteRook) j = i;
4052         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054             if(board[0][i] == WhiteRook) j = i;
4055         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4057             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4058         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4059         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4060             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4061         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4062
4063         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4064         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4065             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4066         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4067             if(board[BOARD_HEIGHT-1][k] == bKing)
4068                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4069         if(gameInfo.variant == VariantTwoKings) {
4070             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4071             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4072             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4073         }
4074     } else { int r;
4075         r = boards[moveNum][CASTLING][0] = initialRights[0];
4076         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4077         r = boards[moveNum][CASTLING][1] = initialRights[1];
4078         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4079         r = boards[moveNum][CASTLING][3] = initialRights[3];
4080         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4081         r = boards[moveNum][CASTLING][4] = initialRights[4];
4082         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4083         /* wildcastle kludge: always assume King has rights */
4084         r = boards[moveNum][CASTLING][2] = initialRights[2];
4085         r = boards[moveNum][CASTLING][5] = initialRights[5];
4086     }
4087     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4088     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4089
4090     
4091     if (ics_getting_history == H_GOT_REQ_HEADER ||
4092         ics_getting_history == H_GOT_UNREQ_HEADER) {
4093         /* This was an initial position from a move list, not
4094            the current position */
4095         return;
4096     }
4097     
4098     /* Update currentMove and known move number limits */
4099     newMove = newGame || moveNum > forwardMostMove;
4100
4101     if (newGame) {
4102         forwardMostMove = backwardMostMove = currentMove = moveNum;
4103         if (gameMode == IcsExamining && moveNum == 0) {
4104           /* Workaround for ICS limitation: we are not told the wild
4105              type when starting to examine a game.  But if we ask for
4106              the move list, the move list header will tell us */
4107             ics_getting_history = H_REQUESTED;
4108             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4109             SendToICS(str);
4110         }
4111     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4112                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4113 #if ZIPPY
4114         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4115         /* [HGM] applied this also to an engine that is silently watching        */
4116         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4117             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4118             gameInfo.variant == currentlyInitializedVariant) {
4119           takeback = forwardMostMove - moveNum;
4120           for (i = 0; i < takeback; i++) {
4121             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4122             SendToProgram("undo\n", &first);
4123           }
4124         }
4125 #endif
4126
4127         forwardMostMove = moveNum;
4128         if (!pausing || currentMove > forwardMostMove)
4129           currentMove = forwardMostMove;
4130     } else {
4131         /* New part of history that is not contiguous with old part */ 
4132         if (pausing && gameMode == IcsExamining) {
4133             pauseExamInvalid = TRUE;
4134             forwardMostMove = pauseExamForwardMostMove;
4135             return;
4136         }
4137         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4138 #if ZIPPY
4139             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4140                 // [HGM] when we will receive the move list we now request, it will be
4141                 // fed to the engine from the first move on. So if the engine is not
4142                 // in the initial position now, bring it there.
4143                 InitChessProgram(&first, 0);
4144             }
4145 #endif
4146             ics_getting_history = H_REQUESTED;
4147             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4148             SendToICS(str);
4149         }
4150         forwardMostMove = backwardMostMove = currentMove = moveNum;
4151     }
4152     
4153     /* Update the clocks */
4154     if (strchr(elapsed_time, '.')) {
4155       /* Time is in ms */
4156       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4157       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4158     } else {
4159       /* Time is in seconds */
4160       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4161       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4162     }
4163       
4164
4165 #if ZIPPY
4166     if (appData.zippyPlay && newGame &&
4167         gameMode != IcsObserving && gameMode != IcsIdle &&
4168         gameMode != IcsExamining)
4169       ZippyFirstBoard(moveNum, basetime, increment);
4170 #endif
4171     
4172     /* Put the move on the move list, first converting
4173        to canonical algebraic form. */
4174     if (moveNum > 0) {
4175   if (appData.debugMode) {
4176     if (appData.debugMode) { int f = forwardMostMove;
4177         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4178                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4179                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4180     }
4181     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4182     fprintf(debugFP, "moveNum = %d\n", moveNum);
4183     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4184     setbuf(debugFP, NULL);
4185   }
4186         if (moveNum <= backwardMostMove) {
4187             /* We don't know what the board looked like before
4188                this move.  Punt. */
4189             strcpy(parseList[moveNum - 1], move_str);
4190             strcat(parseList[moveNum - 1], " ");
4191             strcat(parseList[moveNum - 1], elapsed_time);
4192             moveList[moveNum - 1][0] = NULLCHAR;
4193         } else if (strcmp(move_str, "none") == 0) {
4194             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4195             /* Again, we don't know what the board looked like;
4196                this is really the start of the game. */
4197             parseList[moveNum - 1][0] = NULLCHAR;
4198             moveList[moveNum - 1][0] = NULLCHAR;
4199             backwardMostMove = moveNum;
4200             startedFromSetupPosition = TRUE;
4201             fromX = fromY = toX = toY = -1;
4202         } else {
4203           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4204           //                 So we parse the long-algebraic move string in stead of the SAN move
4205           int valid; char buf[MSG_SIZ], *prom;
4206
4207           // str looks something like "Q/a1-a2"; kill the slash
4208           if(str[1] == '/') 
4209                 sprintf(buf, "%c%s", str[0], str+2);
4210           else  strcpy(buf, str); // might be castling
4211           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4212                 strcat(buf, prom); // long move lacks promo specification!
4213           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4214                 if(appData.debugMode) 
4215                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4216                 strcpy(move_str, buf);
4217           }
4218           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4219                                 &fromX, &fromY, &toX, &toY, &promoChar)
4220                || ParseOneMove(buf, moveNum - 1, &moveType,
4221                                 &fromX, &fromY, &toX, &toY, &promoChar);
4222           // end of long SAN patch
4223           if (valid) {
4224             (void) CoordsToAlgebraic(boards[moveNum - 1],
4225                                      PosFlags(moveNum - 1),
4226                                      fromY, fromX, toY, toX, promoChar,
4227                                      parseList[moveNum-1]);
4228             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4229               case MT_NONE:
4230               case MT_STALEMATE:
4231               default:
4232                 break;
4233               case MT_CHECK:
4234                 if(gameInfo.variant != VariantShogi)
4235                     strcat(parseList[moveNum - 1], "+");
4236                 break;
4237               case MT_CHECKMATE:
4238               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4239                 strcat(parseList[moveNum - 1], "#");
4240                 break;
4241             }
4242             strcat(parseList[moveNum - 1], " ");
4243             strcat(parseList[moveNum - 1], elapsed_time);
4244             /* currentMoveString is set as a side-effect of ParseOneMove */
4245             strcpy(moveList[moveNum - 1], currentMoveString);
4246             strcat(moveList[moveNum - 1], "\n");
4247           } else {
4248             /* Move from ICS was illegal!?  Punt. */
4249   if (appData.debugMode) {
4250     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4251     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4252   }
4253             strcpy(parseList[moveNum - 1], move_str);
4254             strcat(parseList[moveNum - 1], " ");
4255             strcat(parseList[moveNum - 1], elapsed_time);
4256             moveList[moveNum - 1][0] = NULLCHAR;
4257             fromX = fromY = toX = toY = -1;
4258           }
4259         }
4260   if (appData.debugMode) {
4261     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4262     setbuf(debugFP, NULL);
4263   }
4264
4265 #if ZIPPY
4266         /* Send move to chess program (BEFORE animating it). */
4267         if (appData.zippyPlay && !newGame && newMove && 
4268            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4269
4270             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4271                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4272                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4273                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4274                             move_str);
4275                     DisplayError(str, 0);
4276                 } else {
4277                     if (first.sendTime) {
4278                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4279                     }
4280                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4281                     if (firstMove && !bookHit) {
4282                         firstMove = FALSE;
4283                         if (first.useColors) {
4284                           SendToProgram(gameMode == IcsPlayingWhite ?
4285                                         "white\ngo\n" :
4286                                         "black\ngo\n", &first);
4287                         } else {
4288                           SendToProgram("go\n", &first);
4289                         }
4290                         first.maybeThinking = TRUE;
4291                     }
4292                 }
4293             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4294               if (moveList[moveNum - 1][0] == NULLCHAR) {
4295                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4296                 DisplayError(str, 0);
4297               } else {
4298                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4299                 SendMoveToProgram(moveNum - 1, &first);
4300               }
4301             }
4302         }
4303 #endif
4304     }
4305
4306     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4307         /* If move comes from a remote source, animate it.  If it
4308            isn't remote, it will have already been animated. */
4309         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4310             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4311         }
4312         if (!pausing && appData.highlightLastMove) {
4313             SetHighlights(fromX, fromY, toX, toY);
4314         }
4315     }
4316     
4317     /* Start the clocks */
4318     whiteFlag = blackFlag = FALSE;
4319     appData.clockMode = !(basetime == 0 && increment == 0);
4320     if (ticking == 0) {
4321       ics_clock_paused = TRUE;
4322       StopClocks();
4323     } else if (ticking == 1) {
4324       ics_clock_paused = FALSE;
4325     }
4326     if (gameMode == IcsIdle ||
4327         relation == RELATION_OBSERVING_STATIC ||
4328         relation == RELATION_EXAMINING ||
4329         ics_clock_paused)
4330       DisplayBothClocks();
4331     else
4332       StartClocks();
4333     
4334     /* Display opponents and material strengths */
4335     if (gameInfo.variant != VariantBughouse &&
4336         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4337         if (tinyLayout || smallLayout) {
4338             if(gameInfo.variant == VariantNormal)
4339                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4340                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4341                     basetime, increment);
4342             else
4343                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4344                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4345                     basetime, increment, (int) gameInfo.variant);
4346         } else {
4347             if(gameInfo.variant == VariantNormal)
4348                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4349                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4350                     basetime, increment);
4351             else
4352                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4353                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4354                     basetime, increment, VariantName(gameInfo.variant));
4355         }
4356         DisplayTitle(str);
4357   if (appData.debugMode) {
4358     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4359   }
4360     }
4361
4362
4363     /* Display the board */
4364     if (!pausing && !appData.noGUI) {
4365       
4366       if (appData.premove)
4367           if (!gotPremove || 
4368              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4369              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4370               ClearPremoveHighlights();
4371
4372       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4373         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4374       DrawPosition(j, boards[currentMove]);
4375
4376       DisplayMove(moveNum - 1);
4377       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4378             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4379               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4380         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4381       }
4382     }
4383
4384     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4385 #if ZIPPY
4386     if(bookHit) { // [HGM] book: simulate book reply
4387         static char bookMove[MSG_SIZ]; // a bit generous?
4388
4389         programStats.nodes = programStats.depth = programStats.time = 
4390         programStats.score = programStats.got_only_move = 0;
4391         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4392
4393         strcpy(bookMove, "move ");
4394         strcat(bookMove, bookHit);
4395         HandleMachineMove(bookMove, &first);
4396     }
4397 #endif
4398 }
4399
4400 void
4401 GetMoveListEvent()
4402 {
4403     char buf[MSG_SIZ];
4404     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4405         ics_getting_history = H_REQUESTED;
4406         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4407         SendToICS(buf);
4408     }
4409 }
4410
4411 void
4412 AnalysisPeriodicEvent(force)
4413      int force;
4414 {
4415     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4416          && !force) || !appData.periodicUpdates)
4417       return;
4418
4419     /* Send . command to Crafty to collect stats */
4420     SendToProgram(".\n", &first);
4421
4422     /* Don't send another until we get a response (this makes
4423        us stop sending to old Crafty's which don't understand
4424        the "." command (sending illegal cmds resets node count & time,
4425        which looks bad)) */
4426     programStats.ok_to_send = 0;
4427 }
4428
4429 void ics_update_width(new_width)
4430         int new_width;
4431 {
4432         ics_printf("set width %d\n", new_width);
4433 }
4434
4435 void
4436 SendMoveToProgram(moveNum, cps)
4437      int moveNum;
4438      ChessProgramState *cps;
4439 {
4440     char buf[MSG_SIZ];
4441
4442     if (cps->useUsermove) {
4443       SendToProgram("usermove ", cps);
4444     }
4445     if (cps->useSAN) {
4446       char *space;
4447       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4448         int len = space - parseList[moveNum];
4449         memcpy(buf, parseList[moveNum], len);
4450         buf[len++] = '\n';
4451         buf[len] = NULLCHAR;
4452       } else {
4453         sprintf(buf, "%s\n", parseList[moveNum]);
4454       }
4455       SendToProgram(buf, cps);
4456     } else {
4457       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4458         AlphaRank(moveList[moveNum], 4);
4459         SendToProgram(moveList[moveNum], cps);
4460         AlphaRank(moveList[moveNum], 4); // and back
4461       } else
4462       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4463        * the engine. It would be nice to have a better way to identify castle 
4464        * moves here. */
4465       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4466                                                                          && cps->useOOCastle) {
4467         int fromX = moveList[moveNum][0] - AAA; 
4468         int fromY = moveList[moveNum][1] - ONE;
4469         int toX = moveList[moveNum][2] - AAA; 
4470         int toY = moveList[moveNum][3] - ONE;
4471         if((boards[moveNum][fromY][fromX] == WhiteKing 
4472             && boards[moveNum][toY][toX] == WhiteRook)
4473            || (boards[moveNum][fromY][fromX] == BlackKing 
4474                && boards[moveNum][toY][toX] == BlackRook)) {
4475           if(toX > fromX) SendToProgram("O-O\n", cps);
4476           else SendToProgram("O-O-O\n", cps);
4477         }
4478         else SendToProgram(moveList[moveNum], cps);
4479       }
4480       else SendToProgram(moveList[moveNum], cps);
4481       /* End of additions by Tord */
4482     }
4483
4484     /* [HGM] setting up the opening has brought engine in force mode! */
4485     /*       Send 'go' if we are in a mode where machine should play. */
4486     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4487         (gameMode == TwoMachinesPlay   ||
4488 #ifdef ZIPPY
4489          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4490 #endif
4491          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4492         SendToProgram("go\n", cps);
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "(extra)\n");
4495   }
4496     }
4497     setboardSpoiledMachineBlack = 0;
4498 }
4499
4500 void
4501 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4502      ChessMove moveType;
4503      int fromX, fromY, toX, toY;
4504 {
4505     char user_move[MSG_SIZ];
4506
4507     switch (moveType) {
4508       default:
4509         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4510                 (int)moveType, fromX, fromY, toX, toY);
4511         DisplayError(user_move + strlen("say "), 0);
4512         break;
4513       case WhiteKingSideCastle:
4514       case BlackKingSideCastle:
4515       case WhiteQueenSideCastleWild:
4516       case BlackQueenSideCastleWild:
4517       /* PUSH Fabien */
4518       case WhiteHSideCastleFR:
4519       case BlackHSideCastleFR:
4520       /* POP Fabien */
4521         sprintf(user_move, "o-o\n");
4522         break;
4523       case WhiteQueenSideCastle:
4524       case BlackQueenSideCastle:
4525       case WhiteKingSideCastleWild:
4526       case BlackKingSideCastleWild:
4527       /* PUSH Fabien */
4528       case WhiteASideCastleFR:
4529       case BlackASideCastleFR:
4530       /* POP Fabien */
4531         sprintf(user_move, "o-o-o\n");
4532         break;
4533       case WhitePromotionQueen:
4534       case BlackPromotionQueen:
4535       case WhitePromotionRook:
4536       case BlackPromotionRook:
4537       case WhitePromotionBishop:
4538       case BlackPromotionBishop:
4539       case WhitePromotionKnight:
4540       case BlackPromotionKnight:
4541       case WhitePromotionKing:
4542       case BlackPromotionKing:
4543       case WhitePromotionChancellor:
4544       case BlackPromotionChancellor:
4545       case WhitePromotionArchbishop:
4546       case BlackPromotionArchbishop:
4547         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4548             sprintf(user_move, "%c%c%c%c=%c\n",
4549                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550                 PieceToChar(WhiteFerz));
4551         else if(gameInfo.variant == VariantGreat)
4552             sprintf(user_move, "%c%c%c%c=%c\n",
4553                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4554                 PieceToChar(WhiteMan));
4555         else
4556             sprintf(user_move, "%c%c%c%c=%c\n",
4557                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4558                 PieceToChar(PromoPiece(moveType)));
4559         break;
4560       case WhiteDrop:
4561       case BlackDrop:
4562         sprintf(user_move, "%c@%c%c\n",
4563                 ToUpper(PieceToChar((ChessSquare) fromX)),
4564                 AAA + toX, ONE + toY);
4565         break;
4566       case NormalMove:
4567       case WhiteCapturesEnPassant:
4568       case BlackCapturesEnPassant:
4569       case IllegalMove:  /* could be a variant we don't quite understand */
4570         sprintf(user_move, "%c%c%c%c\n",
4571                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4572         break;
4573     }
4574     SendToICS(user_move);
4575     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4576         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4577 }
4578
4579 void
4580 UploadGameEvent()
4581 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4582     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4583     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4584     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4585         DisplayError("You cannot do this while you are playing or observing", 0);
4586         return;
4587     }
4588     if(gameMode != IcsExamining) { // is this ever not the case?
4589         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4590
4591         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4592             sprintf(command, "match %s", ics_handle);
4593         } else { // on FICS we must first go to general examine mode
4594             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4595         }
4596         if(gameInfo.variant != VariantNormal) {
4597             // try figure out wild number, as xboard names are not always valid on ICS
4598             for(i=1; i<=36; i++) {
4599                 sprintf(buf, "wild/%d", i);
4600                 if(StringToVariant(buf) == gameInfo.variant) break;
4601             }
4602             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4603             else if(i == 22) sprintf(buf, "%s fr\n", command);
4604             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4605         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4606         SendToICS(ics_prefix);
4607         SendToICS(buf);
4608         if(startedFromSetupPosition || backwardMostMove != 0) {
4609           fen = PositionToFEN(backwardMostMove, NULL);
4610           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4611             sprintf(buf, "loadfen %s\n", fen);
4612             SendToICS(buf);
4613           } else { // FICS: everything has to set by separate bsetup commands
4614             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4615             sprintf(buf, "bsetup fen %s\n", fen);
4616             SendToICS(buf);
4617             if(!WhiteOnMove(backwardMostMove)) {
4618                 SendToICS("bsetup tomove black\n");
4619             }
4620             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4621             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4622             SendToICS(buf);
4623             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4624             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4625             SendToICS(buf);
4626             i = boards[backwardMostMove][EP_STATUS];
4627             if(i >= 0) { // set e.p.
4628                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4629                 SendToICS(buf);
4630             }
4631             bsetup++;
4632           }
4633         }
4634       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4635             SendToICS("bsetup done\n"); // switch to normal examining.
4636     }
4637     for(i = backwardMostMove; i<last; i++) {
4638         char buf[20];
4639         sprintf(buf, "%s\n", parseList[i]);
4640         SendToICS(buf);
4641     }
4642     SendToICS(ics_prefix);
4643     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4644 }
4645
4646 void
4647 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4648      int rf, ff, rt, ft;
4649      char promoChar;
4650      char move[7];
4651 {
4652     if (rf == DROP_RANK) {
4653         sprintf(move, "%c@%c%c\n",
4654                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4655     } else {
4656         if (promoChar == 'x' || promoChar == NULLCHAR) {
4657             sprintf(move, "%c%c%c%c\n",
4658                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4659         } else {
4660             sprintf(move, "%c%c%c%c%c\n",
4661                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4662         }
4663     }
4664 }
4665
4666 void
4667 ProcessICSInitScript(f)
4668      FILE *f;
4669 {
4670     char buf[MSG_SIZ];
4671
4672     while (fgets(buf, MSG_SIZ, f)) {
4673         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4674     }
4675
4676     fclose(f);
4677 }
4678
4679
4680 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4681 void
4682 AlphaRank(char *move, int n)
4683 {
4684 //    char *p = move, c; int x, y;
4685
4686     if (appData.debugMode) {
4687         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4688     }
4689
4690     if(move[1]=='*' && 
4691        move[2]>='0' && move[2]<='9' &&
4692        move[3]>='a' && move[3]<='x'    ) {
4693         move[1] = '@';
4694         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4695         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4696     } else
4697     if(move[0]>='0' && move[0]<='9' &&
4698        move[1]>='a' && move[1]<='x' &&
4699        move[2]>='0' && move[2]<='9' &&
4700        move[3]>='a' && move[3]<='x'    ) {
4701         /* input move, Shogi -> normal */
4702         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4703         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4704         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4705         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4706     } else
4707     if(move[1]=='@' &&
4708        move[3]>='0' && move[3]<='9' &&
4709        move[2]>='a' && move[2]<='x'    ) {
4710         move[1] = '*';
4711         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4712         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4713     } else
4714     if(
4715        move[0]>='a' && move[0]<='x' &&
4716        move[3]>='0' && move[3]<='9' &&
4717        move[2]>='a' && move[2]<='x'    ) {
4718          /* output move, normal -> Shogi */
4719         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4720         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4721         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4722         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4723         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4724     }
4725     if (appData.debugMode) {
4726         fprintf(debugFP, "   out = '%s'\n", move);
4727     }
4728 }
4729
4730 char yy_textstr[8000];
4731
4732 /* Parser for moves from gnuchess, ICS, or user typein box */
4733 Boolean
4734 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4735      char *move;
4736      int moveNum;
4737      ChessMove *moveType;
4738      int *fromX, *fromY, *toX, *toY;
4739      char *promoChar;
4740 {       
4741     if (appData.debugMode) {
4742         fprintf(debugFP, "move to parse: %s\n", move);
4743     }
4744     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4745
4746     switch (*moveType) {
4747       case WhitePromotionChancellor:
4748       case BlackPromotionChancellor:
4749       case WhitePromotionArchbishop:
4750       case BlackPromotionArchbishop:
4751       case WhitePromotionQueen:
4752       case BlackPromotionQueen:
4753       case WhitePromotionRook:
4754       case BlackPromotionRook:
4755       case WhitePromotionBishop:
4756       case BlackPromotionBishop:
4757       case WhitePromotionKnight:
4758       case BlackPromotionKnight:
4759       case WhitePromotionKing:
4760       case BlackPromotionKing:
4761       case NormalMove:
4762       case WhiteCapturesEnPassant:
4763       case BlackCapturesEnPassant:
4764       case WhiteKingSideCastle:
4765       case WhiteQueenSideCastle:
4766       case BlackKingSideCastle:
4767       case BlackQueenSideCastle:
4768       case WhiteKingSideCastleWild:
4769       case WhiteQueenSideCastleWild:
4770       case BlackKingSideCastleWild:
4771       case BlackQueenSideCastleWild:
4772       /* Code added by Tord: */
4773       case WhiteHSideCastleFR:
4774       case WhiteASideCastleFR:
4775       case BlackHSideCastleFR:
4776       case BlackASideCastleFR:
4777       /* End of code added by Tord */
4778       case IllegalMove:         /* bug or odd chess variant */
4779         *fromX = currentMoveString[0] - AAA;
4780         *fromY = currentMoveString[1] - ONE;
4781         *toX = currentMoveString[2] - AAA;
4782         *toY = currentMoveString[3] - ONE;
4783         *promoChar = currentMoveString[4];
4784         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4785             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4786     if (appData.debugMode) {
4787         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4788     }
4789             *fromX = *fromY = *toX = *toY = 0;
4790             return FALSE;
4791         }
4792         if (appData.testLegality) {
4793           return (*moveType != IllegalMove);
4794         } else {
4795           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4796                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4797         }
4798
4799       case WhiteDrop:
4800       case BlackDrop:
4801         *fromX = *moveType == WhiteDrop ?
4802           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4803           (int) CharToPiece(ToLower(currentMoveString[0]));
4804         *fromY = DROP_RANK;
4805         *toX = currentMoveString[2] - AAA;
4806         *toY = currentMoveString[3] - ONE;
4807         *promoChar = NULLCHAR;
4808         return TRUE;
4809
4810       case AmbiguousMove:
4811       case ImpossibleMove:
4812       case (ChessMove) 0:       /* end of file */
4813       case ElapsedTime:
4814       case Comment:
4815       case PGNTag:
4816       case NAG:
4817       case WhiteWins:
4818       case BlackWins:
4819       case GameIsDrawn:
4820       default:
4821     if (appData.debugMode) {
4822         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4823     }
4824         /* bug? */
4825         *fromX = *fromY = *toX = *toY = 0;
4826         *promoChar = NULLCHAR;
4827         return FALSE;
4828     }
4829 }
4830
4831
4832 void
4833 ParsePV(char *pv, Boolean storeComments)
4834 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4835   int fromX, fromY, toX, toY; char promoChar;
4836   ChessMove moveType;
4837   Boolean valid;
4838   int nr = 0;
4839
4840   endPV = forwardMostMove;
4841   do {
4842     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4843     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4844     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4845 if(appData.debugMode){
4846 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);
4847 }
4848     if(!valid && nr == 0 &&
4849        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4850         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4851         // Hande case where played move is different from leading PV move
4852         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4853         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4854         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4855         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4856           endPV += 2; // if position different, keep this
4857           moveList[endPV-1][0] = fromX + AAA;
4858           moveList[endPV-1][1] = fromY + ONE;
4859           moveList[endPV-1][2] = toX + AAA;
4860           moveList[endPV-1][3] = toY + ONE;
4861           parseList[endPV-1][0] = NULLCHAR;
4862           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4863         }
4864       }
4865     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4866     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4867     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4868     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4869         valid++; // allow comments in PV
4870         continue;
4871     }
4872     nr++;
4873     if(endPV+1 > framePtr) break; // no space, truncate
4874     if(!valid) break;
4875     endPV++;
4876     CopyBoard(boards[endPV], boards[endPV-1]);
4877     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4878     moveList[endPV-1][0] = fromX + AAA;
4879     moveList[endPV-1][1] = fromY + ONE;
4880     moveList[endPV-1][2] = toX + AAA;
4881     moveList[endPV-1][3] = toY + ONE;
4882     if(storeComments)
4883         CoordsToAlgebraic(boards[endPV - 1],
4884                              PosFlags(endPV - 1),
4885                              fromY, fromX, toY, toX, promoChar,
4886                              parseList[endPV - 1]);
4887     else
4888         parseList[endPV-1][0] = NULLCHAR;
4889   } while(valid);
4890   currentMove = endPV;
4891   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4892   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4893                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4894   DrawPosition(TRUE, boards[currentMove]);
4895 }
4896
4897 static int lastX, lastY;
4898
4899 Boolean
4900 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4901 {
4902         int startPV;
4903         char *p;
4904
4905         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4906         lastX = x; lastY = y;
4907         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4908         startPV = index;
4909         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4910         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4911         index = startPV;
4912         do{ while(buf[index] && buf[index] != '\n') index++;
4913         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4914         buf[index] = 0;
4915         ParsePV(buf+startPV, FALSE);
4916         *start = startPV; *end = index-1;
4917         return TRUE;
4918 }
4919
4920 Boolean
4921 LoadPV(int x, int y)
4922 { // called on right mouse click to load PV
4923   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4924   lastX = x; lastY = y;
4925   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4926   return TRUE;
4927 }
4928
4929 void
4930 UnLoadPV()
4931 {
4932   if(endPV < 0) return;
4933   endPV = -1;
4934   currentMove = forwardMostMove;
4935   ClearPremoveHighlights();
4936   DrawPosition(TRUE, boards[currentMove]);
4937 }
4938
4939 void
4940 MovePV(int x, int y, int h)
4941 { // step through PV based on mouse coordinates (called on mouse move)
4942   int margin = h>>3, step = 0;
4943
4944   if(endPV < 0) return;
4945   // we must somehow check if right button is still down (might be released off board!)
4946   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4947   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4948   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4949   if(!step) return;
4950   lastX = x; lastY = y;
4951   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4952   currentMove += step;
4953   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4954   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4955                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4956   DrawPosition(FALSE, boards[currentMove]);
4957 }
4958
4959
4960 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4961 // All positions will have equal probability, but the current method will not provide a unique
4962 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4963 #define DARK 1
4964 #define LITE 2
4965 #define ANY 3
4966
4967 int squaresLeft[4];
4968 int piecesLeft[(int)BlackPawn];
4969 int seed, nrOfShuffles;
4970
4971 void GetPositionNumber()
4972 {       // sets global variable seed
4973         int i;
4974
4975         seed = appData.defaultFrcPosition;
4976         if(seed < 0) { // randomize based on time for negative FRC position numbers
4977                 for(i=0; i<50; i++) seed += random();
4978                 seed = random() ^ random() >> 8 ^ random() << 8;
4979                 if(seed<0) seed = -seed;
4980         }
4981 }
4982
4983 int put(Board board, int pieceType, int rank, int n, int shade)
4984 // put the piece on the (n-1)-th empty squares of the given shade
4985 {
4986         int i;
4987
4988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4989                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4990                         board[rank][i] = (ChessSquare) pieceType;
4991                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4992                         squaresLeft[ANY]--;
4993                         piecesLeft[pieceType]--; 
4994                         return i;
4995                 }
4996         }
4997         return -1;
4998 }
4999
5000
5001 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5002 // calculate where the next piece goes, (any empty square), and put it there
5003 {
5004         int i;
5005
5006         i = seed % squaresLeft[shade];
5007         nrOfShuffles *= squaresLeft[shade];
5008         seed /= squaresLeft[shade];
5009         put(board, pieceType, rank, i, shade);
5010 }
5011
5012 void AddTwoPieces(Board board, int pieceType, int rank)
5013 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5014 {
5015         int i, n=squaresLeft[ANY], j=n-1, k;
5016
5017         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5018         i = seed % k;  // pick one
5019         nrOfShuffles *= k;
5020         seed /= k;
5021         while(i >= j) i -= j--;
5022         j = n - 1 - j; i += j;
5023         put(board, pieceType, rank, j, ANY);
5024         put(board, pieceType, rank, i, ANY);
5025 }
5026
5027 void SetUpShuffle(Board board, int number)
5028 {
5029         int i, p, first=1;
5030
5031         GetPositionNumber(); nrOfShuffles = 1;
5032
5033         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5034         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5035         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5036
5037         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5038
5039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5040             p = (int) board[0][i];
5041             if(p < (int) BlackPawn) piecesLeft[p] ++;
5042             board[0][i] = EmptySquare;
5043         }
5044
5045         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5046             // shuffles restricted to allow normal castling put KRR first
5047             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5048                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5049             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5050                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5051             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5052                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5053             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5054                 put(board, WhiteRook, 0, 0, ANY);
5055             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5056         }
5057
5058         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5059             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5060             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5061                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5062                 while(piecesLeft[p] >= 2) {
5063                     AddOnePiece(board, p, 0, LITE);
5064                     AddOnePiece(board, p, 0, DARK);
5065                 }
5066                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5067             }
5068
5069         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5070             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5071             // but we leave King and Rooks for last, to possibly obey FRC restriction
5072             if(p == (int)WhiteRook) continue;
5073             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5074             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5075         }
5076
5077         // now everything is placed, except perhaps King (Unicorn) and Rooks
5078
5079         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5080             // Last King gets castling rights
5081             while(piecesLeft[(int)WhiteUnicorn]) {
5082                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5083                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5084             }
5085
5086             while(piecesLeft[(int)WhiteKing]) {
5087                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5088                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5089             }
5090
5091
5092         } else {
5093             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5094             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5095         }
5096
5097         // Only Rooks can be left; simply place them all
5098         while(piecesLeft[(int)WhiteRook]) {
5099                 i = put(board, WhiteRook, 0, 0, ANY);
5100                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5101                         if(first) {
5102                                 first=0;
5103                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5104                         }
5105                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5106                 }
5107         }
5108         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5109             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5110         }
5111
5112         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5113 }
5114
5115 int SetCharTable( char *table, const char * map )
5116 /* [HGM] moved here from winboard.c because of its general usefulness */
5117 /*       Basically a safe strcpy that uses the last character as King */
5118 {
5119     int result = FALSE; int NrPieces;
5120
5121     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5122                     && NrPieces >= 12 && !(NrPieces&1)) {
5123         int i; /* [HGM] Accept even length from 12 to 34 */
5124
5125         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5126         for( i=0; i<NrPieces/2-1; i++ ) {
5127             table[i] = map[i];
5128             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5129         }
5130         table[(int) WhiteKing]  = map[NrPieces/2-1];
5131         table[(int) BlackKing]  = map[NrPieces-1];
5132
5133         result = TRUE;
5134     }
5135
5136     return result;
5137 }
5138
5139 void Prelude(Board board)
5140 {       // [HGM] superchess: random selection of exo-pieces
5141         int i, j, k; ChessSquare p; 
5142         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5143
5144         GetPositionNumber(); // use FRC position number
5145
5146         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5147             SetCharTable(pieceToChar, appData.pieceToCharTable);
5148             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5149                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5150         }
5151
5152         j = seed%4;                 seed /= 4; 
5153         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5154         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5155         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5156         j = seed%3 + (seed%3 >= j); seed /= 3; 
5157         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5158         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5159         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5160         j = seed%3;                 seed /= 3; 
5161         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5162         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5163         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5164         j = seed%2 + (seed%2 >= j); seed /= 2; 
5165         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5166         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5167         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5168         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5169         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5170         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5171         put(board, exoPieces[0],    0, 0, ANY);
5172         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5173 }
5174
5175 void
5176 InitPosition(redraw)
5177      int redraw;
5178 {
5179     ChessSquare (* pieces)[BOARD_FILES];
5180     int i, j, pawnRow, overrule,
5181     oldx = gameInfo.boardWidth,
5182     oldy = gameInfo.boardHeight,
5183     oldh = gameInfo.holdingsWidth,
5184     oldv = gameInfo.variant;
5185
5186     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5187
5188     /* [AS] Initialize pv info list [HGM] and game status */
5189     {
5190         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5191             pvInfoList[i].depth = 0;
5192             boards[i][EP_STATUS] = EP_NONE;
5193             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5194         }
5195
5196         initialRulePlies = 0; /* 50-move counter start */
5197
5198         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5199         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5200     }
5201
5202     
5203     /* [HGM] logic here is completely changed. In stead of full positions */
5204     /* the initialized data only consist of the two backranks. The switch */
5205     /* selects which one we will use, which is than copied to the Board   */
5206     /* initialPosition, which for the rest is initialized by Pawns and    */
5207     /* empty squares. This initial position is then copied to boards[0],  */
5208     /* possibly after shuffling, so that it remains available.            */
5209
5210     gameInfo.holdingsWidth = 0; /* default board sizes */
5211     gameInfo.boardWidth    = 8;
5212     gameInfo.boardHeight   = 8;
5213     gameInfo.holdingsSize  = 0;
5214     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5215     for(i=0; i<BOARD_FILES-2; i++)
5216       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5217     initialPosition[EP_STATUS] = EP_NONE;
5218     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5219
5220     switch (gameInfo.variant) {
5221     case VariantFischeRandom:
5222       shuffleOpenings = TRUE;
5223     default:
5224       pieces = FIDEArray;
5225       break;
5226     case VariantShatranj:
5227       pieces = ShatranjArray;
5228       nrCastlingRights = 0;
5229       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5230       break;
5231     case VariantMakruk:
5232       pieces = makrukArray;
5233       nrCastlingRights = 0;
5234       startedFromSetupPosition = TRUE;
5235       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5236       break;
5237     case VariantTwoKings:
5238       pieces = twoKingsArray;
5239       break;
5240     case VariantCapaRandom:
5241       shuffleOpenings = TRUE;
5242     case VariantCapablanca:
5243       pieces = CapablancaArray;
5244       gameInfo.boardWidth = 10;
5245       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5246       break;
5247     case VariantGothic:
5248       pieces = GothicArray;
5249       gameInfo.boardWidth = 10;
5250       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5251       break;
5252     case VariantJanus:
5253       pieces = JanusArray;
5254       gameInfo.boardWidth = 10;
5255       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5256       nrCastlingRights = 6;
5257         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5258         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5259         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5260         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5261         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5262         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5263       break;
5264     case VariantFalcon:
5265       pieces = FalconArray;
5266       gameInfo.boardWidth = 10;
5267       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5268       break;
5269     case VariantXiangqi:
5270       pieces = XiangqiArray;
5271       gameInfo.boardWidth  = 9;
5272       gameInfo.boardHeight = 10;
5273       nrCastlingRights = 0;
5274       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5275       break;
5276     case VariantShogi:
5277       pieces = ShogiArray;
5278       gameInfo.boardWidth  = 9;
5279       gameInfo.boardHeight = 9;
5280       gameInfo.holdingsSize = 7;
5281       nrCastlingRights = 0;
5282       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5283       break;
5284     case VariantCourier:
5285       pieces = CourierArray;
5286       gameInfo.boardWidth  = 12;
5287       nrCastlingRights = 0;
5288       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5289       break;
5290     case VariantKnightmate:
5291       pieces = KnightmateArray;
5292       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5293       break;
5294     case VariantFairy:
5295       pieces = fairyArray;
5296       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5297       break;
5298     case VariantGreat:
5299       pieces = GreatArray;
5300       gameInfo.boardWidth = 10;
5301       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5302       gameInfo.holdingsSize = 8;
5303       break;
5304     case VariantSuper:
5305       pieces = FIDEArray;
5306       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5307       gameInfo.holdingsSize = 8;
5308       startedFromSetupPosition = TRUE;
5309       break;
5310     case VariantCrazyhouse:
5311     case VariantBughouse:
5312       pieces = FIDEArray;
5313       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5314       gameInfo.holdingsSize = 5;
5315       break;
5316     case VariantWildCastle:
5317       pieces = FIDEArray;
5318       /* !!?shuffle with kings guaranteed to be on d or e file */
5319       shuffleOpenings = 1;
5320       break;
5321     case VariantNoCastle:
5322       pieces = FIDEArray;
5323       nrCastlingRights = 0;
5324       /* !!?unconstrained back-rank shuffle */
5325       shuffleOpenings = 1;
5326       break;
5327     }
5328
5329     overrule = 0;
5330     if(appData.NrFiles >= 0) {
5331         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5332         gameInfo.boardWidth = appData.NrFiles;
5333     }
5334     if(appData.NrRanks >= 0) {
5335         gameInfo.boardHeight = appData.NrRanks;
5336     }
5337     if(appData.holdingsSize >= 0) {
5338         i = appData.holdingsSize;
5339         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5340         gameInfo.holdingsSize = i;
5341     }
5342     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5343     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5344         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5345
5346     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5347     if(pawnRow < 1) pawnRow = 1;
5348     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5349
5350     /* User pieceToChar list overrules defaults */
5351     if(appData.pieceToCharTable != NULL)
5352         SetCharTable(pieceToChar, appData.pieceToCharTable);
5353
5354     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5355
5356         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5357             s = (ChessSquare) 0; /* account holding counts in guard band */
5358         for( i=0; i<BOARD_HEIGHT; i++ )
5359             initialPosition[i][j] = s;
5360
5361         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5362         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5363         initialPosition[pawnRow][j] = WhitePawn;
5364         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5365         if(gameInfo.variant == VariantXiangqi) {
5366             if(j&1) {
5367                 initialPosition[pawnRow][j] = 
5368                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5369                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5370                    initialPosition[2][j] = WhiteCannon;
5371                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5372                 }
5373             }
5374         }
5375         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5376     }
5377     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5378
5379             j=BOARD_LEFT+1;
5380             initialPosition[1][j] = WhiteBishop;
5381             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5382             j=BOARD_RGHT-2;
5383             initialPosition[1][j] = WhiteRook;
5384             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5385     }
5386
5387     if( nrCastlingRights == -1) {
5388         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5389         /*       This sets default castling rights from none to normal corners   */
5390         /* Variants with other castling rights must set them themselves above    */
5391         nrCastlingRights = 6;
5392        
5393         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5394         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5395         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5396         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5398         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5399      }
5400
5401      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5402      if(gameInfo.variant == VariantGreat) { // promotion commoners
5403         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5404         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5405         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5406         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5407      }
5408   if (appData.debugMode) {
5409     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5410   }
5411     if(shuffleOpenings) {
5412         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5413         startedFromSetupPosition = TRUE;
5414     }
5415     if(startedFromPositionFile) {
5416       /* [HGM] loadPos: use PositionFile for every new game */
5417       CopyBoard(initialPosition, filePosition);
5418       for(i=0; i<nrCastlingRights; i++)
5419           initialRights[i] = filePosition[CASTLING][i];
5420       startedFromSetupPosition = TRUE;
5421     }
5422
5423     CopyBoard(boards[0], initialPosition);
5424
5425     if(oldx != gameInfo.boardWidth ||
5426        oldy != gameInfo.boardHeight ||
5427        oldh != gameInfo.holdingsWidth
5428 #ifdef GOTHIC
5429        || oldv == VariantGothic ||        // For licensing popups
5430        gameInfo.variant == VariantGothic
5431 #endif
5432 #ifdef FALCON
5433        || oldv == VariantFalcon ||
5434        gameInfo.variant == VariantFalcon
5435 #endif
5436                                          )
5437             InitDrawingSizes(-2 ,0);
5438
5439     if (redraw)
5440       DrawPosition(TRUE, boards[currentMove]);
5441 }
5442
5443 void
5444 SendBoard(cps, moveNum)
5445      ChessProgramState *cps;
5446      int moveNum;
5447 {
5448     char message[MSG_SIZ];
5449     
5450     if (cps->useSetboard) {
5451       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5452       sprintf(message, "setboard %s\n", fen);
5453       SendToProgram(message, cps);
5454       free(fen);
5455
5456     } else {
5457       ChessSquare *bp;
5458       int i, j;
5459       /* Kludge to set black to move, avoiding the troublesome and now
5460        * deprecated "black" command.
5461        */
5462       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5463
5464       SendToProgram("edit\n", cps);
5465       SendToProgram("#\n", cps);
5466       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5467         bp = &boards[moveNum][i][BOARD_LEFT];
5468         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5469           if ((int) *bp < (int) BlackPawn) {
5470             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5471                     AAA + j, ONE + i);
5472             if(message[0] == '+' || message[0] == '~') {
5473                 sprintf(message, "%c%c%c+\n",
5474                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5475                         AAA + j, ONE + i);
5476             }
5477             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5478                 message[1] = BOARD_RGHT   - 1 - j + '1';
5479                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5480             }
5481             SendToProgram(message, cps);
5482           }
5483         }
5484       }
5485     
5486       SendToProgram("c\n", cps);
5487       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5488         bp = &boards[moveNum][i][BOARD_LEFT];
5489         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5490           if (((int) *bp != (int) EmptySquare)
5491               && ((int) *bp >= (int) BlackPawn)) {
5492             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5493                     AAA + j, ONE + i);
5494             if(message[0] == '+' || message[0] == '~') {
5495                 sprintf(message, "%c%c%c+\n",
5496                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5497                         AAA + j, ONE + i);
5498             }
5499             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5500                 message[1] = BOARD_RGHT   - 1 - j + '1';
5501                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5502             }
5503             SendToProgram(message, cps);
5504           }
5505         }
5506       }
5507     
5508       SendToProgram(".\n", cps);
5509     }
5510     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5511 }
5512
5513 static int autoQueen; // [HGM] oneclick
5514
5515 int
5516 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5517 {
5518     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5519     /* [HGM] add Shogi promotions */
5520     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5521     ChessSquare piece;
5522     ChessMove moveType;
5523     Boolean premove;
5524
5525     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5526     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5527
5528     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5529       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5530         return FALSE;
5531
5532     piece = boards[currentMove][fromY][fromX];
5533     if(gameInfo.variant == VariantShogi) {
5534         promotionZoneSize = 3;
5535         highestPromotingPiece = (int)WhiteFerz;
5536     } else if(gameInfo.variant == VariantMakruk) {
5537         promotionZoneSize = 3;
5538     }
5539
5540     // next weed out all moves that do not touch the promotion zone at all
5541     if((int)piece >= BlackPawn) {
5542         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5543              return FALSE;
5544         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5545     } else {
5546         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5547            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5548     }
5549
5550     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5551
5552     // weed out mandatory Shogi promotions
5553     if(gameInfo.variant == VariantShogi) {
5554         if(piece >= BlackPawn) {
5555             if(toY == 0 && piece == BlackPawn ||
5556                toY == 0 && piece == BlackQueen ||
5557                toY <= 1 && piece == BlackKnight) {
5558                 *promoChoice = '+';
5559                 return FALSE;
5560             }
5561         } else {
5562             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5563                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5564                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5565                 *promoChoice = '+';
5566                 return FALSE;
5567             }
5568         }
5569     }
5570
5571     // weed out obviously illegal Pawn moves
5572     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5573         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5574         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5575         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5576         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5577         // note we are not allowed to test for valid (non-)capture, due to premove
5578     }
5579
5580     // we either have a choice what to promote to, or (in Shogi) whether to promote
5581     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5582         *promoChoice = PieceToChar(BlackFerz);  // no choice
5583         return FALSE;
5584     }
5585     if(autoQueen) { // predetermined
5586         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5587              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5588         else *promoChoice = PieceToChar(BlackQueen);
5589         return FALSE;
5590     }
5591
5592     // suppress promotion popup on illegal moves that are not premoves
5593     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5594               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5595     if(appData.testLegality && !premove) {
5596         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5597                         fromY, fromX, toY, toX, NULLCHAR);
5598         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5599            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5600             return FALSE;
5601     }
5602
5603     return TRUE;
5604 }
5605
5606 int
5607 InPalace(row, column)
5608      int row, column;
5609 {   /* [HGM] for Xiangqi */
5610     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5611          column < (BOARD_WIDTH + 4)/2 &&
5612          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5613     return FALSE;
5614 }
5615
5616 int
5617 PieceForSquare (x, y)
5618      int x;
5619      int y;
5620 {
5621   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5622      return -1;
5623   else
5624      return boards[currentMove][y][x];
5625 }
5626
5627 int
5628 OKToStartUserMove(x, y)
5629      int x, y;
5630 {
5631     ChessSquare from_piece;
5632     int white_piece;
5633
5634     if (matchMode) return FALSE;
5635     if (gameMode == EditPosition) return TRUE;
5636
5637     if (x >= 0 && y >= 0)
5638       from_piece = boards[currentMove][y][x];
5639     else
5640       from_piece = EmptySquare;
5641
5642     if (from_piece == EmptySquare) return FALSE;
5643
5644     white_piece = (int)from_piece >= (int)WhitePawn &&
5645       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5646
5647     switch (gameMode) {
5648       case PlayFromGameFile:
5649       case AnalyzeFile:
5650       case TwoMachinesPlay:
5651       case EndOfGame:
5652         return FALSE;
5653
5654       case IcsObserving:
5655       case IcsIdle:
5656         return FALSE;
5657
5658       case MachinePlaysWhite:
5659       case IcsPlayingBlack:
5660         if (appData.zippyPlay) return FALSE;
5661         if (white_piece) {
5662             DisplayMoveError(_("You are playing Black"));
5663             return FALSE;
5664         }
5665         break;
5666
5667       case MachinePlaysBlack:
5668       case IcsPlayingWhite:
5669         if (appData.zippyPlay) return FALSE;
5670         if (!white_piece) {
5671             DisplayMoveError(_("You are playing White"));
5672             return FALSE;
5673         }
5674         break;
5675
5676       case EditGame:
5677         if (!white_piece && WhiteOnMove(currentMove)) {
5678             DisplayMoveError(_("It is White's turn"));
5679             return FALSE;
5680         }           
5681         if (white_piece && !WhiteOnMove(currentMove)) {
5682             DisplayMoveError(_("It is Black's turn"));
5683             return FALSE;
5684         }           
5685         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5686             /* Editing correspondence game history */
5687             /* Could disallow this or prompt for confirmation */
5688             cmailOldMove = -1;
5689         }
5690         break;
5691
5692       case BeginningOfGame:
5693         if (appData.icsActive) return FALSE;
5694         if (!appData.noChessProgram) {
5695             if (!white_piece) {
5696                 DisplayMoveError(_("You are playing White"));
5697                 return FALSE;
5698             }
5699         }
5700         break;
5701         
5702       case Training:
5703         if (!white_piece && WhiteOnMove(currentMove)) {
5704             DisplayMoveError(_("It is White's turn"));
5705             return FALSE;
5706         }           
5707         if (white_piece && !WhiteOnMove(currentMove)) {
5708             DisplayMoveError(_("It is Black's turn"));
5709             return FALSE;
5710         }           
5711         break;
5712
5713       default:
5714       case IcsExamining:
5715         break;
5716     }
5717     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5718         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5719         && gameMode != AnalyzeFile && gameMode != Training) {
5720         DisplayMoveError(_("Displayed position is not current"));
5721         return FALSE;
5722     }
5723     return TRUE;
5724 }
5725
5726 Boolean
5727 OnlyMove(int *x, int *y, Boolean captures) {
5728     DisambiguateClosure cl;
5729     if (appData.zippyPlay) return FALSE;
5730     switch(gameMode) {
5731       case MachinePlaysBlack:
5732       case IcsPlayingWhite:
5733       case BeginningOfGame:
5734         if(!WhiteOnMove(currentMove)) return FALSE;
5735         break;
5736       case MachinePlaysWhite:
5737       case IcsPlayingBlack:
5738         if(WhiteOnMove(currentMove)) return FALSE;
5739         break;
5740       default:
5741         return FALSE;
5742     }
5743     cl.pieceIn = EmptySquare; 
5744     cl.rfIn = *y;
5745     cl.ffIn = *x;
5746     cl.rtIn = -1;
5747     cl.ftIn = -1;
5748     cl.promoCharIn = NULLCHAR;
5749     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5750     if( cl.kind == NormalMove ||
5751         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5752         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5753         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5754         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5755       fromX = cl.ff;
5756       fromY = cl.rf;
5757       *x = cl.ft;
5758       *y = cl.rt;
5759       return TRUE;
5760     }
5761     if(cl.kind != ImpossibleMove) return FALSE;
5762     cl.pieceIn = EmptySquare;
5763     cl.rfIn = -1;
5764     cl.ffIn = -1;
5765     cl.rtIn = *y;
5766     cl.ftIn = *x;
5767     cl.promoCharIn = NULLCHAR;
5768     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5769     if( cl.kind == NormalMove ||
5770         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5771         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5772         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5773         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5774       fromX = cl.ff;
5775       fromY = cl.rf;
5776       *x = cl.ft;
5777       *y = cl.rt;
5778       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5779       return TRUE;
5780     }
5781     return FALSE;
5782 }
5783
5784 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5785 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5786 int lastLoadGameUseList = FALSE;
5787 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5788 ChessMove lastLoadGameStart = (ChessMove) 0;
5789
5790 ChessMove
5791 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5792      int fromX, fromY, toX, toY;
5793      int promoChar;
5794      Boolean captureOwn;
5795 {
5796     ChessMove moveType;
5797     ChessSquare pdown, pup;
5798
5799     /* Check if the user is playing in turn.  This is complicated because we
5800        let the user "pick up" a piece before it is his turn.  So the piece he
5801        tried to pick up may have been captured by the time he puts it down!
5802        Therefore we use the color the user is supposed to be playing in this
5803        test, not the color of the piece that is currently on the starting
5804        square---except in EditGame mode, where the user is playing both
5805        sides; fortunately there the capture race can't happen.  (It can
5806        now happen in IcsExamining mode, but that's just too bad.  The user
5807        will get a somewhat confusing message in that case.)
5808        */
5809
5810     switch (gameMode) {
5811       case PlayFromGameFile:
5812       case AnalyzeFile:
5813       case TwoMachinesPlay:
5814       case EndOfGame:
5815       case IcsObserving:
5816       case IcsIdle:
5817         /* We switched into a game mode where moves are not accepted,
5818            perhaps while the mouse button was down. */
5819         return ImpossibleMove;
5820
5821       case MachinePlaysWhite:
5822         /* User is moving for Black */
5823         if (WhiteOnMove(currentMove)) {
5824             DisplayMoveError(_("It is White's turn"));
5825             return ImpossibleMove;
5826         }
5827         break;
5828
5829       case MachinePlaysBlack:
5830         /* User is moving for White */
5831         if (!WhiteOnMove(currentMove)) {
5832             DisplayMoveError(_("It is Black's turn"));
5833             return ImpossibleMove;
5834         }
5835         break;
5836
5837       case EditGame:
5838       case IcsExamining:
5839       case BeginningOfGame:
5840       case AnalyzeMode:
5841       case Training:
5842         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5843             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5844             /* User is moving for Black */
5845             if (WhiteOnMove(currentMove)) {
5846                 DisplayMoveError(_("It is White's turn"));
5847                 return ImpossibleMove;
5848             }
5849         } else {
5850             /* User is moving for White */
5851             if (!WhiteOnMove(currentMove)) {
5852                 DisplayMoveError(_("It is Black's turn"));
5853                 return ImpossibleMove;
5854             }
5855         }
5856         break;
5857
5858       case IcsPlayingBlack:
5859         /* User is moving for Black */
5860         if (WhiteOnMove(currentMove)) {
5861             if (!appData.premove) {
5862                 DisplayMoveError(_("It is White's turn"));
5863             } else if (toX >= 0 && toY >= 0) {
5864                 premoveToX = toX;
5865                 premoveToY = toY;
5866                 premoveFromX = fromX;
5867                 premoveFromY = fromY;
5868                 premovePromoChar = promoChar;
5869                 gotPremove = 1;
5870                 if (appData.debugMode) 
5871                     fprintf(debugFP, "Got premove: fromX %d,"
5872                             "fromY %d, toX %d, toY %d\n",
5873                             fromX, fromY, toX, toY);
5874             }
5875             return ImpossibleMove;
5876         }
5877         break;
5878
5879       case IcsPlayingWhite:
5880         /* User is moving for White */
5881         if (!WhiteOnMove(currentMove)) {
5882             if (!appData.premove) {
5883                 DisplayMoveError(_("It is Black's turn"));
5884             } else if (toX >= 0 && toY >= 0) {
5885                 premoveToX = toX;
5886                 premoveToY = toY;
5887                 premoveFromX = fromX;
5888                 premoveFromY = fromY;
5889                 premovePromoChar = promoChar;
5890                 gotPremove = 1;
5891                 if (appData.debugMode) 
5892                     fprintf(debugFP, "Got premove: fromX %d,"
5893                             "fromY %d, toX %d, toY %d\n",
5894                             fromX, fromY, toX, toY);
5895             }
5896             return ImpossibleMove;
5897         }
5898         break;
5899
5900       default:
5901         break;
5902
5903       case EditPosition:
5904         /* EditPosition, empty square, or different color piece;
5905            click-click move is possible */
5906         if (toX == -2 || toY == -2) {
5907             boards[0][fromY][fromX] = EmptySquare;
5908             return AmbiguousMove;
5909         } else if (toX >= 0 && toY >= 0) {
5910             boards[0][toY][toX] = boards[0][fromY][fromX];
5911             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5912                 if(boards[0][fromY][0] != EmptySquare) {
5913                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5914                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5915                 }
5916             } else
5917             if(fromX == BOARD_RGHT+1) {
5918                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5919                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5920                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5921                 }
5922             } else
5923             boards[0][fromY][fromX] = EmptySquare;
5924             return AmbiguousMove;
5925         }
5926         return ImpossibleMove;
5927     }
5928
5929     if(toX < 0 || toY < 0) return ImpossibleMove;
5930     pdown = boards[currentMove][fromY][fromX];
5931     pup = boards[currentMove][toY][toX];
5932
5933     /* [HGM] If move started in holdings, it means a drop */
5934     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5935          if( pup != EmptySquare ) return ImpossibleMove;
5936          if(appData.testLegality) {
5937              /* it would be more logical if LegalityTest() also figured out
5938               * which drops are legal. For now we forbid pawns on back rank.
5939               * Shogi is on its own here...
5940               */
5941              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5942                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5943                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5944          }
5945          return WhiteDrop; /* Not needed to specify white or black yet */
5946     }
5947
5948     /* [HGM] always test for legality, to get promotion info */
5949     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5950                                          fromY, fromX, toY, toX, promoChar);
5951     /* [HGM] but possibly ignore an IllegalMove result */
5952     if (appData.testLegality) {
5953         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5954             DisplayMoveError(_("Illegal move"));
5955             return ImpossibleMove;
5956         }
5957     }
5958
5959     return moveType;
5960     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5961        function is made into one that returns an OK move type if FinishMove
5962        should be called. This to give the calling driver routine the
5963        opportunity to finish the userMove input with a promotion popup,
5964        without bothering the user with this for invalid or illegal moves */
5965
5966 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5967 }
5968
5969 /* Common tail of UserMoveEvent and DropMenuEvent */
5970 int
5971 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5972      ChessMove moveType;
5973      int fromX, fromY, toX, toY;
5974      /*char*/int promoChar;
5975 {
5976     char *bookHit = 0;
5977
5978     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5979         // [HGM] superchess: suppress promotions to non-available piece
5980         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5981         if(WhiteOnMove(currentMove)) {
5982             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5983         } else {
5984             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5985         }
5986     }
5987
5988     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5989        move type in caller when we know the move is a legal promotion */
5990     if(moveType == NormalMove && promoChar)
5991         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5992
5993     /* [HGM] convert drag-and-drop piece drops to standard form */
5994     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5995          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5996            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5997                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5998            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5999            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6000            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6001            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6002          fromY = DROP_RANK;
6003     }
6004
6005     /* [HGM] <popupFix> The following if has been moved here from
6006        UserMoveEvent(). Because it seemed to belong here (why not allow
6007        piece drops in training games?), and because it can only be
6008        performed after it is known to what we promote. */
6009     if (gameMode == Training) {
6010       /* compare the move played on the board to the next move in the
6011        * game. If they match, display the move and the opponent's response. 
6012        * If they don't match, display an error message.
6013        */
6014       int saveAnimate;
6015       Board testBoard;
6016       CopyBoard(testBoard, boards[currentMove]);
6017       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6018
6019       if (CompareBoards(testBoard, boards[currentMove+1])) {
6020         ForwardInner(currentMove+1);
6021
6022         /* Autoplay the opponent's response.
6023          * if appData.animate was TRUE when Training mode was entered,
6024          * the response will be animated.
6025          */
6026         saveAnimate = appData.animate;
6027         appData.animate = animateTraining;
6028         ForwardInner(currentMove+1);
6029         appData.animate = saveAnimate;
6030
6031         /* check for the end of the game */
6032         if (currentMove >= forwardMostMove) {
6033           gameMode = PlayFromGameFile;
6034           ModeHighlight();
6035           SetTrainingModeOff();
6036           DisplayInformation(_("End of game"));
6037         }
6038       } else {
6039         DisplayError(_("Incorrect move"), 0);
6040       }
6041       return 1;
6042     }
6043
6044   /* Ok, now we know that the move is good, so we can kill
6045      the previous line in Analysis Mode */
6046   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6047                                 && currentMove < forwardMostMove) {
6048     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6049   }
6050
6051   /* If we need the chess program but it's dead, restart it */
6052   ResurrectChessProgram();
6053
6054   /* A user move restarts a paused game*/
6055   if (pausing)
6056     PauseEvent();
6057
6058   thinkOutput[0] = NULLCHAR;
6059
6060   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6061
6062   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6063
6064   if (gameMode == BeginningOfGame) {
6065     if (appData.noChessProgram) {
6066       gameMode = EditGame;
6067       SetGameInfo();
6068     } else {
6069       char buf[MSG_SIZ];
6070       gameMode = MachinePlaysBlack;
6071       StartClocks();
6072       SetGameInfo();
6073       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6074       DisplayTitle(buf);
6075       if (first.sendName) {
6076         sprintf(buf, "name %s\n", gameInfo.white);
6077         SendToProgram(buf, &first);
6078       }
6079       StartClocks();
6080     }
6081     ModeHighlight();
6082   }
6083
6084   /* Relay move to ICS or chess engine */
6085   if (appData.icsActive) {
6086     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6087         gameMode == IcsExamining) {
6088       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6089         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6090         SendToICS("draw ");
6091         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6092       }
6093       // also send plain move, in case ICS does not understand atomic claims
6094       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6095       ics_user_moved = 1;
6096     }
6097   } else {
6098     if (first.sendTime && (gameMode == BeginningOfGame ||
6099                            gameMode == MachinePlaysWhite ||
6100                            gameMode == MachinePlaysBlack)) {
6101       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6102     }
6103     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6104          // [HGM] book: if program might be playing, let it use book
6105         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6106         first.maybeThinking = TRUE;
6107     } else SendMoveToProgram(forwardMostMove-1, &first);
6108     if (currentMove == cmailOldMove + 1) {
6109       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6110     }
6111   }
6112
6113   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6114
6115   switch (gameMode) {
6116   case EditGame:
6117     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6118     case MT_NONE:
6119     case MT_CHECK:
6120       break;
6121     case MT_CHECKMATE:
6122     case MT_STAINMATE:
6123       if (WhiteOnMove(currentMove)) {
6124         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6125       } else {
6126         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6127       }
6128       break;
6129     case MT_STALEMATE:
6130       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6131       break;
6132     }
6133     break;
6134     
6135   case MachinePlaysBlack:
6136   case MachinePlaysWhite:
6137     /* disable certain menu options while machine is thinking */
6138     SetMachineThinkingEnables();
6139     break;
6140
6141   default:
6142     break;
6143   }
6144
6145   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6146         
6147   if(bookHit) { // [HGM] book: simulate book reply
6148         static char bookMove[MSG_SIZ]; // a bit generous?
6149
6150         programStats.nodes = programStats.depth = programStats.time = 
6151         programStats.score = programStats.got_only_move = 0;
6152         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6153
6154         strcpy(bookMove, "move ");
6155         strcat(bookMove, bookHit);
6156         HandleMachineMove(bookMove, &first);
6157   }
6158   return 1;
6159 }
6160
6161 void
6162 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6163      int fromX, fromY, toX, toY;
6164      int promoChar;
6165 {
6166     /* [HGM] This routine was added to allow calling of its two logical
6167        parts from other modules in the old way. Before, UserMoveEvent()
6168        automatically called FinishMove() if the move was OK, and returned
6169        otherwise. I separated the two, in order to make it possible to
6170        slip a promotion popup in between. But that it always needs two
6171        calls, to the first part, (now called UserMoveTest() ), and to
6172        FinishMove if the first part succeeded. Calls that do not need
6173        to do anything in between, can call this routine the old way. 
6174     */
6175     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6176 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6177     if(moveType == AmbiguousMove)
6178         DrawPosition(FALSE, boards[currentMove]);
6179     else if(moveType != ImpossibleMove && moveType != Comment)
6180         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6181 }
6182
6183 void
6184 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6185      Board board;
6186      int flags;
6187      ChessMove kind;
6188      int rf, ff, rt, ft;
6189      VOIDSTAR closure;
6190 {
6191     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6192     Markers *m = (Markers *) closure;
6193     if(rf == fromY && ff == fromX)
6194         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6195                          || kind == WhiteCapturesEnPassant
6196                          || kind == BlackCapturesEnPassant);
6197     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6198 }
6199
6200 void
6201 MarkTargetSquares(int clear)
6202 {
6203   int x, y;
6204   if(!appData.markers || !appData.highlightDragging || 
6205      !appData.testLegality || gameMode == EditPosition) return;
6206   if(clear) {
6207     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6208   } else {
6209     int capt = 0;
6210     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6211     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6212       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6213       if(capt)
6214       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6215     }
6216   }
6217   DrawPosition(TRUE, NULL);
6218 }
6219
6220 void LeftClick(ClickType clickType, int xPix, int yPix)
6221 {
6222     int x, y;
6223     Boolean saveAnimate;
6224     static int second = 0, promotionChoice = 0;
6225     char promoChoice = NULLCHAR;
6226
6227     if(appData.seekGraph && appData.icsActive && loggedOn &&
6228         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6229         SeekGraphClick(clickType, xPix, yPix, 0);
6230         return;
6231     }
6232
6233     if (clickType == Press) ErrorPopDown();
6234     MarkTargetSquares(1);
6235
6236     x = EventToSquare(xPix, BOARD_WIDTH);
6237     y = EventToSquare(yPix, BOARD_HEIGHT);
6238     if (!flipView && y >= 0) {
6239         y = BOARD_HEIGHT - 1 - y;
6240     }
6241     if (flipView && x >= 0) {
6242         x = BOARD_WIDTH - 1 - x;
6243     }
6244
6245     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6246         if(clickType == Release) return; // ignore upclick of click-click destination
6247         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6248         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6249         if(gameInfo.holdingsWidth && 
6250                 (WhiteOnMove(currentMove) 
6251                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6252                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6253             // click in right holdings, for determining promotion piece
6254             ChessSquare p = boards[currentMove][y][x];
6255             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6256             if(p != EmptySquare) {
6257                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6258                 fromX = fromY = -1;
6259                 return;
6260             }
6261         }
6262         DrawPosition(FALSE, boards[currentMove]);
6263         return;
6264     }
6265
6266     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6267     if(clickType == Press
6268             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6269               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6270               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6271         return;
6272
6273     autoQueen = appData.alwaysPromoteToQueen;
6274
6275     if (fromX == -1) {
6276       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6277         if (clickType == Press) {
6278             /* First square */
6279             if (OKToStartUserMove(x, y)) {
6280                 fromX = x;
6281                 fromY = y;
6282                 second = 0;
6283                 MarkTargetSquares(0);
6284                 DragPieceBegin(xPix, yPix);
6285                 if (appData.highlightDragging) {
6286                     SetHighlights(x, y, -1, -1);
6287                 }
6288             }
6289         }
6290         return;
6291       }
6292     }
6293
6294     /* fromX != -1 */
6295     if (clickType == Press && gameMode != EditPosition) {
6296         ChessSquare fromP;
6297         ChessSquare toP;
6298         int frc;
6299
6300         // ignore off-board to clicks
6301         if(y < 0 || x < 0) return;
6302
6303         /* Check if clicking again on the same color piece */
6304         fromP = boards[currentMove][fromY][fromX];
6305         toP = boards[currentMove][y][x];
6306         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6307         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6308              WhitePawn <= toP && toP <= WhiteKing &&
6309              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6310              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6311             (BlackPawn <= fromP && fromP <= BlackKing && 
6312              BlackPawn <= toP && toP <= BlackKing &&
6313              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6314              !(fromP == BlackKing && toP == BlackRook && frc))) {
6315             /* Clicked again on same color piece -- changed his mind */
6316             second = (x == fromX && y == fromY);
6317            if(!second || !OnlyMove(&x, &y, TRUE)) {
6318             if (appData.highlightDragging) {
6319                 SetHighlights(x, y, -1, -1);
6320             } else {
6321                 ClearHighlights();
6322             }
6323             if (OKToStartUserMove(x, y)) {
6324                 fromX = x;
6325                 fromY = y;
6326                 MarkTargetSquares(0);
6327                 DragPieceBegin(xPix, yPix);
6328             }
6329             return;
6330            }
6331         }
6332         // ignore clicks on holdings
6333         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6334     }
6335
6336     if (clickType == Release && x == fromX && y == fromY) {
6337         DragPieceEnd(xPix, yPix);
6338         if (appData.animateDragging) {
6339             /* Undo animation damage if any */
6340             DrawPosition(FALSE, NULL);
6341         }
6342         if (second) {
6343             /* Second up/down in same square; just abort move */
6344             second = 0;
6345             fromX = fromY = -1;
6346             ClearHighlights();
6347             gotPremove = 0;
6348             ClearPremoveHighlights();
6349         } else {
6350             /* First upclick in same square; start click-click mode */
6351             SetHighlights(x, y, -1, -1);
6352         }
6353         return;
6354     }
6355
6356     /* we now have a different from- and (possibly off-board) to-square */
6357     /* Completed move */
6358     toX = x;
6359     toY = y;
6360     saveAnimate = appData.animate;
6361     if (clickType == Press) {
6362         /* Finish clickclick move */
6363         if (appData.animate || appData.highlightLastMove) {
6364             SetHighlights(fromX, fromY, toX, toY);
6365         } else {
6366             ClearHighlights();
6367         }
6368     } else {
6369         /* Finish drag move */
6370         if (appData.highlightLastMove) {
6371             SetHighlights(fromX, fromY, toX, toY);
6372         } else {
6373             ClearHighlights();
6374         }
6375         DragPieceEnd(xPix, yPix);
6376         /* Don't animate move and drag both */
6377         appData.animate = FALSE;
6378     }
6379
6380     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6381     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6382         ChessSquare piece = boards[currentMove][fromY][fromX];
6383         if(gameMode == EditPosition && piece != EmptySquare &&
6384            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6385             int n;
6386              
6387             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6388                 n = PieceToNumber(piece - (int)BlackPawn);
6389                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6390                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6391                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6392             } else
6393             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6394                 n = PieceToNumber(piece);
6395                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6396                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6397                 boards[currentMove][n][BOARD_WIDTH-2]++;
6398             }
6399             boards[currentMove][fromY][fromX] = EmptySquare;
6400         }
6401         ClearHighlights();
6402         fromX = fromY = -1;
6403         DrawPosition(TRUE, boards[currentMove]);
6404         return;
6405     }
6406
6407     // off-board moves should not be highlighted
6408     if(x < 0 || x < 0) ClearHighlights();
6409
6410     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6411         SetHighlights(fromX, fromY, toX, toY);
6412         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6413             // [HGM] super: promotion to captured piece selected from holdings
6414             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6415             promotionChoice = TRUE;
6416             // kludge follows to temporarily execute move on display, without promoting yet
6417             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6418             boards[currentMove][toY][toX] = p;
6419             DrawPosition(FALSE, boards[currentMove]);
6420             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6421             boards[currentMove][toY][toX] = q;
6422             DisplayMessage("Click in holdings to choose piece", "");
6423             return;
6424         }
6425         PromotionPopUp();
6426     } else {
6427         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6428         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6429         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6430         fromX = fromY = -1;
6431     }
6432     appData.animate = saveAnimate;
6433     if (appData.animate || appData.animateDragging) {
6434         /* Undo animation damage if needed */
6435         DrawPosition(FALSE, NULL);
6436     }
6437 }
6438
6439 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6440 {   // front-end-free part taken out of PieceMenuPopup
6441     int whichMenu; int xSqr, ySqr;
6442
6443     if(seekGraphUp) { // [HGM] seekgraph
6444         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6445         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6446         return -2;
6447     }
6448
6449     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6450          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6451         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6452         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6453         if(action == Press)   {
6454             originalFlip = flipView;
6455             flipView = !flipView; // temporarily flip board to see game from partners perspective
6456             DrawPosition(TRUE, partnerBoard);
6457             DisplayMessage(partnerStatus, "");
6458             partnerUp = TRUE;
6459         } else if(action == Release) {
6460             flipView = originalFlip;
6461             DrawPosition(TRUE, boards[currentMove]);
6462             partnerUp = FALSE;
6463         }
6464         return -2;
6465     }
6466
6467     xSqr = EventToSquare(x, BOARD_WIDTH);
6468     ySqr = EventToSquare(y, BOARD_HEIGHT);
6469     if (action == Release) UnLoadPV(); // [HGM] pv
6470     if (action != Press) return -2; // return code to be ignored
6471     switch (gameMode) {
6472       case IcsExamining:
6473         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6474       case EditPosition:
6475         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6476         if (xSqr < 0 || ySqr < 0) return -1;\r
6477         whichMenu = 0; // edit-position menu
6478         break;
6479       case IcsObserving:
6480         if(!appData.icsEngineAnalyze) return -1;
6481       case IcsPlayingWhite:
6482       case IcsPlayingBlack:
6483         if(!appData.zippyPlay) goto noZip;
6484       case AnalyzeMode:
6485       case AnalyzeFile:
6486       case MachinePlaysWhite:
6487       case MachinePlaysBlack:
6488       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6489         if (!appData.dropMenu) {
6490           LoadPV(x, y);
6491           return 2; // flag front-end to grab mouse events
6492         }
6493         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6494            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6495       case EditGame:
6496       noZip:
6497         if (xSqr < 0 || ySqr < 0) return -1;
6498         if (!appData.dropMenu || appData.testLegality &&
6499             gameInfo.variant != VariantBughouse &&
6500             gameInfo.variant != VariantCrazyhouse) return -1;
6501         whichMenu = 1; // drop menu
6502         break;
6503       default:
6504         return -1;
6505     }
6506
6507     if (((*fromX = xSqr) < 0) ||
6508         ((*fromY = ySqr) < 0)) {
6509         *fromX = *fromY = -1;
6510         return -1;
6511     }
6512     if (flipView)
6513       *fromX = BOARD_WIDTH - 1 - *fromX;
6514     else
6515       *fromY = BOARD_HEIGHT - 1 - *fromY;
6516
6517     return whichMenu;
6518 }
6519
6520 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6521 {
6522 //    char * hint = lastHint;
6523     FrontEndProgramStats stats;
6524
6525     stats.which = cps == &first ? 0 : 1;
6526     stats.depth = cpstats->depth;
6527     stats.nodes = cpstats->nodes;
6528     stats.score = cpstats->score;
6529     stats.time = cpstats->time;
6530     stats.pv = cpstats->movelist;
6531     stats.hint = lastHint;
6532     stats.an_move_index = 0;
6533     stats.an_move_count = 0;
6534
6535     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6536         stats.hint = cpstats->move_name;
6537         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6538         stats.an_move_count = cpstats->nr_moves;
6539     }
6540
6541     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6542
6543     SetProgramStats( &stats );
6544 }
6545
6546 int
6547 Adjudicate(ChessProgramState *cps)
6548 {       // [HGM] some adjudications useful with buggy engines
6549         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6550         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6551         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6552         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6553         int k, count = 0; static int bare = 1;
6554         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6555         Boolean canAdjudicate = !appData.icsActive;
6556
6557         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6558         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6559             if( appData.testLegality )
6560             {   /* [HGM] Some more adjudications for obstinate engines */
6561                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6562                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6563                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6564                 static int moveCount = 6;
6565                 ChessMove result;
6566                 char *reason = NULL;
6567
6568                 /* Count what is on board. */
6569                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6570                 {   ChessSquare p = boards[forwardMostMove][i][j];
6571                     int m=i;
6572
6573                     switch((int) p)
6574                     {   /* count B,N,R and other of each side */
6575                         case WhiteKing:
6576                         case BlackKing:
6577                              NrK++; break; // [HGM] atomic: count Kings
6578                         case WhiteKnight:
6579                              NrWN++; break;
6580                         case WhiteBishop:
6581                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6582                              bishopsColor |= 1 << ((i^j)&1);
6583                              NrWB++; break;
6584                         case BlackKnight:
6585                              NrBN++; break;
6586                         case BlackBishop:
6587                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6588                              bishopsColor |= 1 << ((i^j)&1);
6589                              NrBB++; break;
6590                         case WhiteRook:
6591                              NrWR++; break;
6592                         case BlackRook:
6593                              NrBR++; break;
6594                         case WhiteQueen:
6595                              NrWQ++; break;
6596                         case BlackQueen:
6597                              NrBQ++; break;
6598                         case EmptySquare: 
6599                              break;
6600                         case BlackPawn:
6601                              m = 7-i;
6602                         case WhitePawn:
6603                              PawnAdvance += m; NrPawns++;
6604                     }
6605                     NrPieces += (p != EmptySquare);
6606                     NrW += ((int)p < (int)BlackPawn);
6607                     if(gameInfo.variant == VariantXiangqi && 
6608                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6609                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6610                         NrW -= ((int)p < (int)BlackPawn);
6611                     }
6612                 }
6613
6614                 /* Some material-based adjudications that have to be made before stalemate test */
6615                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6616                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6617                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6618                      if(canAdjudicate && appData.checkMates) {
6619                          if(engineOpponent)
6620                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6621                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6623                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6624                          return 1;
6625                      }
6626                 }
6627
6628                 /* Bare King in Shatranj (loses) or Losers (wins) */
6629                 if( NrW == 1 || NrPieces - NrW == 1) {
6630                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6631                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6632                      if(canAdjudicate && appData.checkMates) {
6633                          if(engineOpponent)
6634                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6635                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6637                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6638                          return 1;
6639                      }
6640                   } else
6641                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6642                   {    /* bare King */
6643                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6644                         if(canAdjudicate && appData.checkMates) {
6645                             /* but only adjudicate if adjudication enabled */
6646                             if(engineOpponent)
6647                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6648                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6650                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6651                             return 1;
6652                         }
6653                   }
6654                 } else bare = 1;
6655
6656
6657             // don't wait for engine to announce game end if we can judge ourselves
6658             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6659               case MT_CHECK:
6660                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6661                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6662                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6663                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6664                             checkCnt++;
6665                         if(checkCnt >= 2) {
6666                             reason = "Xboard adjudication: 3rd check";
6667                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6668                             break;
6669                         }
6670                     }
6671                 }
6672               case MT_NONE:
6673               default:
6674                 break;
6675               case MT_STALEMATE:
6676               case MT_STAINMATE:
6677                 reason = "Xboard adjudication: Stalemate";
6678                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6679                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6680                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6681                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6682                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6683                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6684                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6685                                                                         EP_CHECKMATE : EP_WINS);
6686                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6687                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6688                 }
6689                 break;
6690               case MT_CHECKMATE:
6691                 reason = "Xboard adjudication: Checkmate";
6692                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6693                 break;
6694             }
6695
6696                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6697                     case EP_STALEMATE:
6698                         result = GameIsDrawn; break;
6699                     case EP_CHECKMATE:
6700                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6701                     case EP_WINS:
6702                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6703                     default:
6704                         result = (ChessMove) 0;
6705                 }
6706                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6707                     if(engineOpponent)
6708                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6709                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710                     GameEnds( result, reason, GE_XBOARD );
6711                     return 1;
6712                 }
6713
6714                 /* Next absolutely insufficient mating material. */
6715                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6716                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6717                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6718                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6719                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6720
6721                      /* always flag draws, for judging claims */
6722                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6723
6724                      if(canAdjudicate && appData.materialDraws) {
6725                          /* but only adjudicate them if adjudication enabled */
6726                          if(engineOpponent) {
6727                            SendToProgram("force\n", engineOpponent); // suppress reply
6728                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6729                          }
6730                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6731                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6732                          return 1;
6733                      }
6734                 }
6735
6736                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6737                 if(NrPieces == 4 && 
6738                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6739                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6740                    || NrWN==2 || NrBN==2     /* KNNK */
6741                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6742                   ) ) {
6743                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6744                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6745                           if(engineOpponent) {
6746                             SendToProgram("force\n", engineOpponent); // suppress reply
6747                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6748                           }
6749                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6751                           return 1;
6752                      }
6753                 } else moveCount = 6;
6754             }
6755         }
6756           
6757         if (appData.debugMode) { int i;
6758             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6759                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6760                     appData.drawRepeats);
6761             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6762               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6763             
6764         }
6765
6766         // Repetition draws and 50-move rule can be applied independently of legality testing
6767
6768                 /* Check for rep-draws */
6769                 count = 0;
6770                 for(k = forwardMostMove-2;
6771                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6772                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6773                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6774                     k-=2)
6775                 {   int rights=0;
6776                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6777                         /* compare castling rights */
6778                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6779                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6780                                 rights++; /* King lost rights, while rook still had them */
6781                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6782                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6783                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6784                                    rights++; /* but at least one rook lost them */
6785                         }
6786                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6787                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6788                                 rights++; 
6789                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6790                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6791                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6792                                    rights++;
6793                         }
6794                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6795                             && appData.drawRepeats > 1) {
6796                              /* adjudicate after user-specified nr of repeats */
6797                              if(engineOpponent) {
6798                                SendToProgram("force\n", engineOpponent); // suppress reply
6799                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6800                              }
6801                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6802                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6803                                 // [HGM] xiangqi: check for forbidden perpetuals
6804                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6805                                 for(m=forwardMostMove; m>k; m-=2) {
6806                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6807                                         ourPerpetual = 0; // the current mover did not always check
6808                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6809                                         hisPerpetual = 0; // the opponent did not always check
6810                                 }
6811                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6812                                                                         ourPerpetual, hisPerpetual);
6813                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6814                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6815                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6816                                     return 1;
6817                                 }
6818                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6819                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6820                                 // Now check for perpetual chases
6821                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6822                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6823                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6824                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6825                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6826                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6827                                         return 1;
6828                                     }
6829                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6830                                         break; // Abort repetition-checking loop.
6831                                 }
6832                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6833                              }
6834                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6835                              return 1;
6836                         }
6837                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6838                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6839                     }
6840                 }
6841
6842                 /* Now we test for 50-move draws. Determine ply count */
6843                 count = forwardMostMove;
6844                 /* look for last irreversble move */
6845                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6846                     count--;
6847                 /* if we hit starting position, add initial plies */
6848                 if( count == backwardMostMove )
6849                     count -= initialRulePlies;
6850                 count = forwardMostMove - count; 
6851                 if( count >= 100)
6852                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6853                          /* this is used to judge if draw claims are legal */
6854                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6855                          if(engineOpponent) {
6856                            SendToProgram("force\n", engineOpponent); // suppress reply
6857                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6858                          }
6859                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6860                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6861                          return 1;
6862                 }
6863
6864                 /* if draw offer is pending, treat it as a draw claim
6865                  * when draw condition present, to allow engines a way to
6866                  * claim draws before making their move to avoid a race
6867                  * condition occurring after their move
6868                  */
6869                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6870                          char *p = NULL;
6871                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6872                              p = "Draw claim: 50-move rule";
6873                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6874                              p = "Draw claim: 3-fold repetition";
6875                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6876                              p = "Draw claim: insufficient mating material";
6877                          if( p != NULL && canAdjudicate) {
6878                              if(engineOpponent) {
6879                                SendToProgram("force\n", engineOpponent); // suppress reply
6880                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6881                              }
6882                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6883                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6884                              return 1;
6885                          }
6886                 }
6887
6888                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6889                     if(engineOpponent) {
6890                       SendToProgram("force\n", engineOpponent); // suppress reply
6891                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6892                     }
6893                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6894                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6895                     return 1;
6896                 }
6897         return 0;
6898 }
6899
6900 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6901 {   // [HGM] book: this routine intercepts moves to simulate book replies
6902     char *bookHit = NULL;
6903
6904     //first determine if the incoming move brings opponent into his book
6905     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6906         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6907     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6908     if(bookHit != NULL && !cps->bookSuspend) {
6909         // make sure opponent is not going to reply after receiving move to book position
6910         SendToProgram("force\n", cps);
6911         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6912     }
6913     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6914     // now arrange restart after book miss
6915     if(bookHit) {
6916         // after a book hit we never send 'go', and the code after the call to this routine
6917         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6918         char buf[MSG_SIZ];
6919         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6920         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6921         SendToProgram(buf, cps);
6922         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6923     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6924         SendToProgram("go\n", cps);
6925         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6926     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6927         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6928             SendToProgram("go\n", cps); 
6929         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6930     }
6931     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6932 }
6933
6934 char *savedMessage;
6935 ChessProgramState *savedState;
6936 void DeferredBookMove(void)
6937 {
6938         if(savedState->lastPing != savedState->lastPong)
6939                     ScheduleDelayedEvent(DeferredBookMove, 10);
6940         else
6941         HandleMachineMove(savedMessage, savedState);
6942 }
6943
6944 void
6945 HandleMachineMove(message, cps)
6946      char *message;
6947      ChessProgramState *cps;
6948 {
6949     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6950     char realname[MSG_SIZ];
6951     int fromX, fromY, toX, toY;
6952     ChessMove moveType;
6953     char promoChar;
6954     char *p;
6955     int machineWhite;
6956     char *bookHit;
6957
6958     cps->userError = 0;
6959
6960 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6961     /*
6962      * Kludge to ignore BEL characters
6963      */
6964     while (*message == '\007') message++;
6965
6966     /*
6967      * [HGM] engine debug message: ignore lines starting with '#' character
6968      */
6969     if(cps->debug && *message == '#') return;
6970
6971     /*
6972      * Look for book output
6973      */
6974     if (cps == &first && bookRequested) {
6975         if (message[0] == '\t' || message[0] == ' ') {
6976             /* Part of the book output is here; append it */
6977             strcat(bookOutput, message);
6978             strcat(bookOutput, "  \n");
6979             return;
6980         } else if (bookOutput[0] != NULLCHAR) {
6981             /* All of book output has arrived; display it */
6982             char *p = bookOutput;
6983             while (*p != NULLCHAR) {
6984                 if (*p == '\t') *p = ' ';
6985                 p++;
6986             }
6987             DisplayInformation(bookOutput);
6988             bookRequested = FALSE;
6989             /* Fall through to parse the current output */
6990         }
6991     }
6992
6993     /*
6994      * Look for machine move.
6995      */
6996     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6997         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6998     {
6999         /* This method is only useful on engines that support ping */
7000         if (cps->lastPing != cps->lastPong) {
7001           if (gameMode == BeginningOfGame) {
7002             /* Extra move from before last new; ignore */
7003             if (appData.debugMode) {
7004                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7005             }
7006           } else {
7007             if (appData.debugMode) {
7008                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7009                         cps->which, gameMode);
7010             }
7011
7012             SendToProgram("undo\n", cps);
7013           }
7014           return;
7015         }
7016
7017         switch (gameMode) {
7018           case BeginningOfGame:
7019             /* Extra move from before last reset; ignore */
7020             if (appData.debugMode) {
7021                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7022             }
7023             return;
7024
7025           case EndOfGame:
7026           case IcsIdle:
7027           default:
7028             /* Extra move after we tried to stop.  The mode test is
7029                not a reliable way of detecting this problem, but it's
7030                the best we can do on engines that don't support ping.
7031             */
7032             if (appData.debugMode) {
7033                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7034                         cps->which, gameMode);
7035             }
7036             SendToProgram("undo\n", cps);
7037             return;
7038
7039           case MachinePlaysWhite:
7040           case IcsPlayingWhite:
7041             machineWhite = TRUE;
7042             break;
7043
7044           case MachinePlaysBlack:
7045           case IcsPlayingBlack:
7046             machineWhite = FALSE;
7047             break;
7048
7049           case TwoMachinesPlay:
7050             machineWhite = (cps->twoMachinesColor[0] == 'w');
7051             break;
7052         }
7053         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7054             if (appData.debugMode) {
7055                 fprintf(debugFP,
7056                         "Ignoring move out of turn by %s, gameMode %d"
7057                         ", forwardMost %d\n",
7058                         cps->which, gameMode, forwardMostMove);
7059             }
7060             return;
7061         }
7062
7063     if (appData.debugMode) { int f = forwardMostMove;
7064         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7065                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7066                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7067     }
7068         if(cps->alphaRank) AlphaRank(machineMove, 4);
7069         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7070                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7071             /* Machine move could not be parsed; ignore it. */
7072             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7073                     machineMove, cps->which);
7074             DisplayError(buf1, 0);
7075             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7076                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7077             if (gameMode == TwoMachinesPlay) {
7078               GameEnds(machineWhite ? BlackWins : WhiteWins,
7079                        buf1, GE_XBOARD);
7080             }
7081             return;
7082         }
7083
7084         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7085         /* So we have to redo legality test with true e.p. status here,  */
7086         /* to make sure an illegal e.p. capture does not slip through,   */
7087         /* to cause a forfeit on a justified illegal-move complaint      */
7088         /* of the opponent.                                              */
7089         if( gameMode==TwoMachinesPlay && appData.testLegality
7090             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7091                                                               ) {
7092            ChessMove moveType;
7093            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7094                              fromY, fromX, toY, toX, promoChar);
7095             if (appData.debugMode) {
7096                 int i;
7097                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7098                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7099                 fprintf(debugFP, "castling rights\n");
7100             }
7101             if(moveType == IllegalMove) {
7102                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7103                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7104                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7105                            buf1, GE_XBOARD);
7106                 return;
7107            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7108            /* [HGM] Kludge to handle engines that send FRC-style castling
7109               when they shouldn't (like TSCP-Gothic) */
7110            switch(moveType) {
7111              case WhiteASideCastleFR:
7112              case BlackASideCastleFR:
7113                toX+=2;
7114                currentMoveString[2]++;
7115                break;
7116              case WhiteHSideCastleFR:
7117              case BlackHSideCastleFR:
7118                toX--;
7119                currentMoveString[2]--;
7120                break;
7121              default: ; // nothing to do, but suppresses warning of pedantic compilers
7122            }
7123         }
7124         hintRequested = FALSE;
7125         lastHint[0] = NULLCHAR;
7126         bookRequested = FALSE;
7127         /* Program may be pondering now */
7128         cps->maybeThinking = TRUE;
7129         if (cps->sendTime == 2) cps->sendTime = 1;
7130         if (cps->offeredDraw) cps->offeredDraw--;
7131
7132         /* currentMoveString is set as a side-effect of ParseOneMove */
7133         strcpy(machineMove, currentMoveString);
7134         strcat(machineMove, "\n");
7135         strcpy(moveList[forwardMostMove], machineMove);
7136
7137         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7138
7139         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7140         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7141             int count = 0;
7142
7143             while( count < adjudicateLossPlies ) {
7144                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7145
7146                 if( count & 1 ) {
7147                     score = -score; /* Flip score for winning side */
7148                 }
7149
7150                 if( score > adjudicateLossThreshold ) {
7151                     break;
7152                 }
7153
7154                 count++;
7155             }
7156
7157             if( count >= adjudicateLossPlies ) {
7158                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7159
7160                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7161                     "Xboard adjudication", 
7162                     GE_XBOARD );
7163
7164                 return;
7165             }
7166         }
7167
7168         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7169
7170 #if ZIPPY
7171         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7172             first.initDone) {
7173           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7174                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7175                 SendToICS("draw ");
7176                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7177           }
7178           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7179           ics_user_moved = 1;
7180           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7181                 char buf[3*MSG_SIZ];
7182
7183                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7184                         programStats.score / 100.,
7185                         programStats.depth,
7186                         programStats.time / 100.,
7187                         (unsigned int)programStats.nodes,
7188                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7189                         programStats.movelist);
7190                 SendToICS(buf);
7191 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7192           }
7193         }
7194 #endif
7195
7196         /* [AS] Save move info and clear stats for next move */
7197         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7198         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7199         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7200         ClearProgramStats();
7201         thinkOutput[0] = NULLCHAR;
7202         hiddenThinkOutputState = 0;
7203
7204         bookHit = NULL;
7205         if (gameMode == TwoMachinesPlay) {
7206             /* [HGM] relaying draw offers moved to after reception of move */
7207             /* and interpreting offer as claim if it brings draw condition */
7208             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7209                 SendToProgram("draw\n", cps->other);
7210             }
7211             if (cps->other->sendTime) {
7212                 SendTimeRemaining(cps->other,
7213                                   cps->other->twoMachinesColor[0] == 'w');
7214             }
7215             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7216             if (firstMove && !bookHit) {
7217                 firstMove = FALSE;
7218                 if (cps->other->useColors) {
7219                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7220                 }
7221                 SendToProgram("go\n", cps->other);
7222             }
7223             cps->other->maybeThinking = TRUE;
7224         }
7225
7226         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7227         
7228         if (!pausing && appData.ringBellAfterMoves) {
7229             RingBell();
7230         }
7231
7232         /* 
7233          * Reenable menu items that were disabled while
7234          * machine was thinking
7235          */
7236         if (gameMode != TwoMachinesPlay)
7237             SetUserThinkingEnables();
7238
7239         // [HGM] book: after book hit opponent has received move and is now in force mode
7240         // force the book reply into it, and then fake that it outputted this move by jumping
7241         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7242         if(bookHit) {
7243                 static char bookMove[MSG_SIZ]; // a bit generous?
7244
7245                 strcpy(bookMove, "move ");
7246                 strcat(bookMove, bookHit);
7247                 message = bookMove;
7248                 cps = cps->other;
7249                 programStats.nodes = programStats.depth = programStats.time = 
7250                 programStats.score = programStats.got_only_move = 0;
7251                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7252
7253                 if(cps->lastPing != cps->lastPong) {
7254                     savedMessage = message; // args for deferred call
7255                     savedState = cps;
7256                     ScheduleDelayedEvent(DeferredBookMove, 10);
7257                     return;
7258                 }
7259                 goto FakeBookMove;
7260         }
7261
7262         return;
7263     }
7264
7265     /* Set special modes for chess engines.  Later something general
7266      *  could be added here; for now there is just one kludge feature,
7267      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7268      *  when "xboard" is given as an interactive command.
7269      */
7270     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7271         cps->useSigint = FALSE;
7272         cps->useSigterm = FALSE;
7273     }
7274     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7275       ParseFeatures(message+8, cps);
7276       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7277     }
7278
7279     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7280      * want this, I was asked to put it in, and obliged.
7281      */
7282     if (!strncmp(message, "setboard ", 9)) {
7283         Board initial_position;
7284
7285         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7286
7287         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7288             DisplayError(_("Bad FEN received from engine"), 0);
7289             return ;
7290         } else {
7291            Reset(TRUE, FALSE);
7292            CopyBoard(boards[0], initial_position);
7293            initialRulePlies = FENrulePlies;
7294            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7295            else gameMode = MachinePlaysBlack;                 
7296            DrawPosition(FALSE, boards[currentMove]);
7297         }
7298         return;
7299     }
7300
7301     /*
7302      * Look for communication commands
7303      */
7304     if (!strncmp(message, "telluser ", 9)) {
7305         DisplayNote(message + 9);
7306         return;
7307     }
7308     if (!strncmp(message, "tellusererror ", 14)) {
7309         cps->userError = 1;
7310         DisplayError(message + 14, 0);
7311         return;
7312     }
7313     if (!strncmp(message, "tellopponent ", 13)) {
7314       if (appData.icsActive) {
7315         if (loggedOn) {
7316           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7317           SendToICS(buf1);
7318         }
7319       } else {
7320         DisplayNote(message + 13);
7321       }
7322       return;
7323     }
7324     if (!strncmp(message, "tellothers ", 11)) {
7325       if (appData.icsActive) {
7326         if (loggedOn) {
7327           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7328           SendToICS(buf1);
7329         }
7330       }
7331       return;
7332     }
7333     if (!strncmp(message, "tellall ", 8)) {
7334       if (appData.icsActive) {
7335         if (loggedOn) {
7336           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7337           SendToICS(buf1);
7338         }
7339       } else {
7340         DisplayNote(message + 8);
7341       }
7342       return;
7343     }
7344     if (strncmp(message, "warning", 7) == 0) {
7345         /* Undocumented feature, use tellusererror in new code */
7346         DisplayError(message, 0);
7347         return;
7348     }
7349     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7350         strcpy(realname, cps->tidy);
7351         strcat(realname, " query");
7352         AskQuestion(realname, buf2, buf1, cps->pr);
7353         return;
7354     }
7355     /* Commands from the engine directly to ICS.  We don't allow these to be 
7356      *  sent until we are logged on. Crafty kibitzes have been known to 
7357      *  interfere with the login process.
7358      */
7359     if (loggedOn) {
7360         if (!strncmp(message, "tellics ", 8)) {
7361             SendToICS(message + 8);
7362             SendToICS("\n");
7363             return;
7364         }
7365         if (!strncmp(message, "tellicsnoalias ", 15)) {
7366             SendToICS(ics_prefix);
7367             SendToICS(message + 15);
7368             SendToICS("\n");
7369             return;
7370         }
7371         /* The following are for backward compatibility only */
7372         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7373             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7374             SendToICS(ics_prefix);
7375             SendToICS(message);
7376             SendToICS("\n");
7377             return;
7378         }
7379     }
7380     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7381         return;
7382     }
7383     /*
7384      * If the move is illegal, cancel it and redraw the board.
7385      * Also deal with other error cases.  Matching is rather loose
7386      * here to accommodate engines written before the spec.
7387      */
7388     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7389         strncmp(message, "Error", 5) == 0) {
7390         if (StrStr(message, "name") || 
7391             StrStr(message, "rating") || StrStr(message, "?") ||
7392             StrStr(message, "result") || StrStr(message, "board") ||
7393             StrStr(message, "bk") || StrStr(message, "computer") ||
7394             StrStr(message, "variant") || StrStr(message, "hint") ||
7395             StrStr(message, "random") || StrStr(message, "depth") ||
7396             StrStr(message, "accepted")) {
7397             return;
7398         }
7399         if (StrStr(message, "protover")) {
7400           /* Program is responding to input, so it's apparently done
7401              initializing, and this error message indicates it is
7402              protocol version 1.  So we don't need to wait any longer
7403              for it to initialize and send feature commands. */
7404           FeatureDone(cps, 1);
7405           cps->protocolVersion = 1;
7406           return;
7407         }
7408         cps->maybeThinking = FALSE;
7409
7410         if (StrStr(message, "draw")) {
7411             /* Program doesn't have "draw" command */
7412             cps->sendDrawOffers = 0;
7413             return;
7414         }
7415         if (cps->sendTime != 1 &&
7416             (StrStr(message, "time") || StrStr(message, "otim"))) {
7417           /* Program apparently doesn't have "time" or "otim" command */
7418           cps->sendTime = 0;
7419           return;
7420         }
7421         if (StrStr(message, "analyze")) {
7422             cps->analysisSupport = FALSE;
7423             cps->analyzing = FALSE;
7424             Reset(FALSE, TRUE);
7425             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7426             DisplayError(buf2, 0);
7427             return;
7428         }
7429         if (StrStr(message, "(no matching move)st")) {
7430           /* Special kludge for GNU Chess 4 only */
7431           cps->stKludge = TRUE;
7432           SendTimeControl(cps, movesPerSession, timeControl,
7433                           timeIncrement, appData.searchDepth,
7434                           searchTime);
7435           return;
7436         }
7437         if (StrStr(message, "(no matching move)sd")) {
7438           /* Special kludge for GNU Chess 4 only */
7439           cps->sdKludge = TRUE;
7440           SendTimeControl(cps, movesPerSession, timeControl,
7441                           timeIncrement, appData.searchDepth,
7442                           searchTime);
7443           return;
7444         }
7445         if (!StrStr(message, "llegal")) {
7446             return;
7447         }
7448         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7449             gameMode == IcsIdle) return;
7450         if (forwardMostMove <= backwardMostMove) return;
7451         if (pausing) PauseEvent();
7452       if(appData.forceIllegal) {
7453             // [HGM] illegal: machine refused move; force position after move into it
7454           SendToProgram("force\n", cps);
7455           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7456                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7457                 // when black is to move, while there might be nothing on a2 or black
7458                 // might already have the move. So send the board as if white has the move.
7459                 // But first we must change the stm of the engine, as it refused the last move
7460                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7461                 if(WhiteOnMove(forwardMostMove)) {
7462                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7463                     SendBoard(cps, forwardMostMove); // kludgeless board
7464                 } else {
7465                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7466                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7467                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7468                 }
7469           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7470             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7471                  gameMode == TwoMachinesPlay)
7472               SendToProgram("go\n", cps);
7473             return;
7474       } else
7475         if (gameMode == PlayFromGameFile) {
7476             /* Stop reading this game file */
7477             gameMode = EditGame;
7478             ModeHighlight();
7479         }
7480         currentMove = forwardMostMove-1;
7481         DisplayMove(currentMove-1); /* before DisplayMoveError */
7482         SwitchClocks(forwardMostMove-1); // [HGM] race
7483         DisplayBothClocks();
7484         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7485                 parseList[currentMove], cps->which);
7486         DisplayMoveError(buf1);
7487         DrawPosition(FALSE, boards[currentMove]);
7488
7489         /* [HGM] illegal-move claim should forfeit game when Xboard */
7490         /* only passes fully legal moves                            */
7491         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7492             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7493                                 "False illegal-move claim", GE_XBOARD );
7494         }
7495         return;
7496     }
7497     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7498         /* Program has a broken "time" command that
7499            outputs a string not ending in newline.
7500            Don't use it. */
7501         cps->sendTime = 0;
7502     }
7503     
7504     /*
7505      * If chess program startup fails, exit with an error message.
7506      * Attempts to recover here are futile.
7507      */
7508     if ((StrStr(message, "unknown host") != NULL)
7509         || (StrStr(message, "No remote directory") != NULL)
7510         || (StrStr(message, "not found") != NULL)
7511         || (StrStr(message, "No such file") != NULL)
7512         || (StrStr(message, "can't alloc") != NULL)
7513         || (StrStr(message, "Permission denied") != NULL)) {
7514
7515         cps->maybeThinking = FALSE;
7516         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7517                 cps->which, cps->program, cps->host, message);
7518         RemoveInputSource(cps->isr);
7519         DisplayFatalError(buf1, 0, 1);
7520         return;
7521     }
7522     
7523     /* 
7524      * Look for hint output
7525      */
7526     if (sscanf(message, "Hint: %s", buf1) == 1) {
7527         if (cps == &first && hintRequested) {
7528             hintRequested = FALSE;
7529             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7530                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7531                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7532                                     PosFlags(forwardMostMove),
7533                                     fromY, fromX, toY, toX, promoChar, buf1);
7534                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7535                 DisplayInformation(buf2);
7536             } else {
7537                 /* Hint move could not be parsed!? */
7538               snprintf(buf2, sizeof(buf2),
7539                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7540                         buf1, cps->which);
7541                 DisplayError(buf2, 0);
7542             }
7543         } else {
7544             strcpy(lastHint, buf1);
7545         }
7546         return;
7547     }
7548
7549     /*
7550      * Ignore other messages if game is not in progress
7551      */
7552     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7553         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7554
7555     /*
7556      * look for win, lose, draw, or draw offer
7557      */
7558     if (strncmp(message, "1-0", 3) == 0) {
7559         char *p, *q, *r = "";
7560         p = strchr(message, '{');
7561         if (p) {
7562             q = strchr(p, '}');
7563             if (q) {
7564                 *q = NULLCHAR;
7565                 r = p + 1;
7566             }
7567         }
7568         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7569         return;
7570     } else if (strncmp(message, "0-1", 3) == 0) {
7571         char *p, *q, *r = "";
7572         p = strchr(message, '{');
7573         if (p) {
7574             q = strchr(p, '}');
7575             if (q) {
7576                 *q = NULLCHAR;
7577                 r = p + 1;
7578             }
7579         }
7580         /* Kludge for Arasan 4.1 bug */
7581         if (strcmp(r, "Black resigns") == 0) {
7582             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7583             return;
7584         }
7585         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7586         return;
7587     } else if (strncmp(message, "1/2", 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             
7598         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7599         return;
7600
7601     } else if (strncmp(message, "White resign", 12) == 0) {
7602         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7603         return;
7604     } else if (strncmp(message, "Black resign", 12) == 0) {
7605         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7606         return;
7607     } else if (strncmp(message, "White matches", 13) == 0 ||
7608                strncmp(message, "Black matches", 13) == 0   ) {
7609         /* [HGM] ignore GNUShogi noises */
7610         return;
7611     } else if (strncmp(message, "White", 5) == 0 &&
7612                message[5] != '(' &&
7613                StrStr(message, "Black") == NULL) {
7614         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7615         return;
7616     } else if (strncmp(message, "Black", 5) == 0 &&
7617                message[5] != '(') {
7618         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7619         return;
7620     } else if (strcmp(message, "resign") == 0 ||
7621                strcmp(message, "computer resigns") == 0) {
7622         switch (gameMode) {
7623           case MachinePlaysBlack:
7624           case IcsPlayingBlack:
7625             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7626             break;
7627           case MachinePlaysWhite:
7628           case IcsPlayingWhite:
7629             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7630             break;
7631           case TwoMachinesPlay:
7632             if (cps->twoMachinesColor[0] == 'w')
7633               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7634             else
7635               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7636             break;
7637           default:
7638             /* can't happen */
7639             break;
7640         }
7641         return;
7642     } else if (strncmp(message, "opponent mates", 14) == 0) {
7643         switch (gameMode) {
7644           case MachinePlaysBlack:
7645           case IcsPlayingBlack:
7646             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7647             break;
7648           case MachinePlaysWhite:
7649           case IcsPlayingWhite:
7650             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7651             break;
7652           case TwoMachinesPlay:
7653             if (cps->twoMachinesColor[0] == 'w')
7654               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7655             else
7656               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7657             break;
7658           default:
7659             /* can't happen */
7660             break;
7661         }
7662         return;
7663     } else if (strncmp(message, "computer mates", 14) == 0) {
7664         switch (gameMode) {
7665           case MachinePlaysBlack:
7666           case IcsPlayingBlack:
7667             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7668             break;
7669           case MachinePlaysWhite:
7670           case IcsPlayingWhite:
7671             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7672             break;
7673           case TwoMachinesPlay:
7674             if (cps->twoMachinesColor[0] == 'w')
7675               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7676             else
7677               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7678             break;
7679           default:
7680             /* can't happen */
7681             break;
7682         }
7683         return;
7684     } else if (strncmp(message, "checkmate", 9) == 0) {
7685         if (WhiteOnMove(forwardMostMove)) {
7686             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7687         } else {
7688             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7689         }
7690         return;
7691     } else if (strstr(message, "Draw") != NULL ||
7692                strstr(message, "game is a draw") != NULL) {
7693         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7694         return;
7695     } else if (strstr(message, "offer") != NULL &&
7696                strstr(message, "draw") != NULL) {
7697 #if ZIPPY
7698         if (appData.zippyPlay && first.initDone) {
7699             /* Relay offer to ICS */
7700             SendToICS(ics_prefix);
7701             SendToICS("draw\n");
7702         }
7703 #endif
7704         cps->offeredDraw = 2; /* valid until this engine moves twice */
7705         if (gameMode == TwoMachinesPlay) {
7706             if (cps->other->offeredDraw) {
7707                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7708             /* [HGM] in two-machine mode we delay relaying draw offer      */
7709             /* until after we also have move, to see if it is really claim */
7710             }
7711         } else if (gameMode == MachinePlaysWhite ||
7712                    gameMode == MachinePlaysBlack) {
7713           if (userOfferedDraw) {
7714             DisplayInformation(_("Machine accepts your draw offer"));
7715             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7716           } else {
7717             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7718           }
7719         }
7720     }
7721
7722     
7723     /*
7724      * Look for thinking output
7725      */
7726     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7727           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7728                                 ) {
7729         int plylev, mvleft, mvtot, curscore, time;
7730         char mvname[MOVE_LEN];
7731         u64 nodes; // [DM]
7732         char plyext;
7733         int ignore = FALSE;
7734         int prefixHint = FALSE;
7735         mvname[0] = NULLCHAR;
7736
7737         switch (gameMode) {
7738           case MachinePlaysBlack:
7739           case IcsPlayingBlack:
7740             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7741             break;
7742           case MachinePlaysWhite:
7743           case IcsPlayingWhite:
7744             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7745             break;
7746           case AnalyzeMode:
7747           case AnalyzeFile:
7748             break;
7749           case IcsObserving: /* [DM] icsEngineAnalyze */
7750             if (!appData.icsEngineAnalyze) ignore = TRUE;
7751             break;
7752           case TwoMachinesPlay:
7753             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7754                 ignore = TRUE;
7755             }
7756             break;
7757           default:
7758             ignore = TRUE;
7759             break;
7760         }
7761
7762         if (!ignore) {
7763             buf1[0] = NULLCHAR;
7764             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7765                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7766
7767                 if (plyext != ' ' && plyext != '\t') {
7768                     time *= 100;
7769                 }
7770
7771                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7772                 if( cps->scoreIsAbsolute && 
7773                     ( gameMode == MachinePlaysBlack ||
7774                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7775                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7776                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7777                      !WhiteOnMove(currentMove)
7778                     ) )
7779                 {
7780                     curscore = -curscore;
7781                 }
7782
7783
7784                 programStats.depth = plylev;
7785                 programStats.nodes = nodes;
7786                 programStats.time = time;
7787                 programStats.score = curscore;
7788                 programStats.got_only_move = 0;
7789
7790                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7791                         int ticklen;
7792
7793                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7794                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7795                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7796                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7797                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7798                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7799                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7800                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7801                 }
7802
7803                 /* Buffer overflow protection */
7804                 if (buf1[0] != NULLCHAR) {
7805                     if (strlen(buf1) >= sizeof(programStats.movelist)
7806                         && appData.debugMode) {
7807                         fprintf(debugFP,
7808                                 "PV is too long; using the first %u bytes.\n",
7809                                 (unsigned) sizeof(programStats.movelist) - 1);
7810                     }
7811
7812                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7813                 } else {
7814                     sprintf(programStats.movelist, " no PV\n");
7815                 }
7816
7817                 if (programStats.seen_stat) {
7818                     programStats.ok_to_send = 1;
7819                 }
7820
7821                 if (strchr(programStats.movelist, '(') != NULL) {
7822                     programStats.line_is_book = 1;
7823                     programStats.nr_moves = 0;
7824                     programStats.moves_left = 0;
7825                 } else {
7826                     programStats.line_is_book = 0;
7827                 }
7828
7829                 SendProgramStatsToFrontend( cps, &programStats );
7830
7831                 /* 
7832                     [AS] Protect the thinkOutput buffer from overflow... this
7833                     is only useful if buf1 hasn't overflowed first!
7834                 */
7835                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7836                         plylev, 
7837                         (gameMode == TwoMachinesPlay ?
7838                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7839                         ((double) curscore) / 100.0,
7840                         prefixHint ? lastHint : "",
7841                         prefixHint ? " " : "" );
7842
7843                 if( buf1[0] != NULLCHAR ) {
7844                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7845
7846                     if( strlen(buf1) > max_len ) {
7847                         if( appData.debugMode) {
7848                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7849                         }
7850                         buf1[max_len+1] = '\0';
7851                     }
7852
7853                     strcat( thinkOutput, buf1 );
7854                 }
7855
7856                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7857                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7858                     DisplayMove(currentMove - 1);
7859                 }
7860                 return;
7861
7862             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7863                 /* crafty (9.25+) says "(only move) <move>"
7864                  * if there is only 1 legal move
7865                  */
7866                 sscanf(p, "(only move) %s", buf1);
7867                 sprintf(thinkOutput, "%s (only move)", buf1);
7868                 sprintf(programStats.movelist, "%s (only move)", buf1);
7869                 programStats.depth = 1;
7870                 programStats.nr_moves = 1;
7871                 programStats.moves_left = 1;
7872                 programStats.nodes = 1;
7873                 programStats.time = 1;
7874                 programStats.got_only_move = 1;
7875
7876                 /* Not really, but we also use this member to
7877                    mean "line isn't going to change" (Crafty
7878                    isn't searching, so stats won't change) */
7879                 programStats.line_is_book = 1;
7880
7881                 SendProgramStatsToFrontend( cps, &programStats );
7882                 
7883                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7884                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7885                     DisplayMove(currentMove - 1);
7886                 }
7887                 return;
7888             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7889                               &time, &nodes, &plylev, &mvleft,
7890                               &mvtot, mvname) >= 5) {
7891                 /* The stat01: line is from Crafty (9.29+) in response
7892                    to the "." command */
7893                 programStats.seen_stat = 1;
7894                 cps->maybeThinking = TRUE;
7895
7896                 if (programStats.got_only_move || !appData.periodicUpdates)
7897                   return;
7898
7899                 programStats.depth = plylev;
7900                 programStats.time = time;
7901                 programStats.nodes = nodes;
7902                 programStats.moves_left = mvleft;
7903                 programStats.nr_moves = mvtot;
7904                 strcpy(programStats.move_name, mvname);
7905                 programStats.ok_to_send = 1;
7906                 programStats.movelist[0] = '\0';
7907
7908                 SendProgramStatsToFrontend( cps, &programStats );
7909
7910                 return;
7911
7912             } else if (strncmp(message,"++",2) == 0) {
7913                 /* Crafty 9.29+ outputs this */
7914                 programStats.got_fail = 2;
7915                 return;
7916
7917             } else if (strncmp(message,"--",2) == 0) {
7918                 /* Crafty 9.29+ outputs this */
7919                 programStats.got_fail = 1;
7920                 return;
7921
7922             } else if (thinkOutput[0] != NULLCHAR &&
7923                        strncmp(message, "    ", 4) == 0) {
7924                 unsigned message_len;
7925
7926                 p = message;
7927                 while (*p && *p == ' ') p++;
7928
7929                 message_len = strlen( p );
7930
7931                 /* [AS] Avoid buffer overflow */
7932                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7933                     strcat(thinkOutput, " ");
7934                     strcat(thinkOutput, p);
7935                 }
7936
7937                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7938                     strcat(programStats.movelist, " ");
7939                     strcat(programStats.movelist, p);
7940                 }
7941
7942                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7943                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7944                     DisplayMove(currentMove - 1);
7945                 }
7946                 return;
7947             }
7948         }
7949         else {
7950             buf1[0] = NULLCHAR;
7951
7952             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7953                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7954             {
7955                 ChessProgramStats cpstats;
7956
7957                 if (plyext != ' ' && plyext != '\t') {
7958                     time *= 100;
7959                 }
7960
7961                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7962                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7963                     curscore = -curscore;
7964                 }
7965
7966                 cpstats.depth = plylev;
7967                 cpstats.nodes = nodes;
7968                 cpstats.time = time;
7969                 cpstats.score = curscore;
7970                 cpstats.got_only_move = 0;
7971                 cpstats.movelist[0] = '\0';
7972
7973                 if (buf1[0] != NULLCHAR) {
7974                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7975                 }
7976
7977                 cpstats.ok_to_send = 0;
7978                 cpstats.line_is_book = 0;
7979                 cpstats.nr_moves = 0;
7980                 cpstats.moves_left = 0;
7981
7982                 SendProgramStatsToFrontend( cps, &cpstats );
7983             }
7984         }
7985     }
7986 }
7987
7988
7989 /* Parse a game score from the character string "game", and
7990    record it as the history of the current game.  The game
7991    score is NOT assumed to start from the standard position. 
7992    The display is not updated in any way.
7993    */
7994 void
7995 ParseGameHistory(game)
7996      char *game;
7997 {
7998     ChessMove moveType;
7999     int fromX, fromY, toX, toY, boardIndex;
8000     char promoChar;
8001     char *p, *q;
8002     char buf[MSG_SIZ];
8003
8004     if (appData.debugMode)
8005       fprintf(debugFP, "Parsing game history: %s\n", game);
8006
8007     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8008     gameInfo.site = StrSave(appData.icsHost);
8009     gameInfo.date = PGNDate();
8010     gameInfo.round = StrSave("-");
8011
8012     /* Parse out names of players */
8013     while (*game == ' ') game++;
8014     p = buf;
8015     while (*game != ' ') *p++ = *game++;
8016     *p = NULLCHAR;
8017     gameInfo.white = StrSave(buf);
8018     while (*game == ' ') game++;
8019     p = buf;
8020     while (*game != ' ' && *game != '\n') *p++ = *game++;
8021     *p = NULLCHAR;
8022     gameInfo.black = StrSave(buf);
8023
8024     /* Parse moves */
8025     boardIndex = blackPlaysFirst ? 1 : 0;
8026     yynewstr(game);
8027     for (;;) {
8028         yyboardindex = boardIndex;
8029         moveType = (ChessMove) yylex();
8030         switch (moveType) {
8031           case IllegalMove:             /* maybe suicide chess, etc. */
8032   if (appData.debugMode) {
8033     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8034     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8035     setbuf(debugFP, NULL);
8036   }
8037           case WhitePromotionChancellor:
8038           case BlackPromotionChancellor:
8039           case WhitePromotionArchbishop:
8040           case BlackPromotionArchbishop:
8041           case WhitePromotionQueen:
8042           case BlackPromotionQueen:
8043           case WhitePromotionRook:
8044           case BlackPromotionRook:
8045           case WhitePromotionBishop:
8046           case BlackPromotionBishop:
8047           case WhitePromotionKnight:
8048           case BlackPromotionKnight:
8049           case WhitePromotionKing:
8050           case BlackPromotionKing:
8051           case NormalMove:
8052           case WhiteCapturesEnPassant:
8053           case BlackCapturesEnPassant:
8054           case WhiteKingSideCastle:
8055           case WhiteQueenSideCastle:
8056           case BlackKingSideCastle:
8057           case BlackQueenSideCastle:
8058           case WhiteKingSideCastleWild:
8059           case WhiteQueenSideCastleWild:
8060           case BlackKingSideCastleWild:
8061           case BlackQueenSideCastleWild:
8062           /* PUSH Fabien */
8063           case WhiteHSideCastleFR:
8064           case WhiteASideCastleFR:
8065           case BlackHSideCastleFR:
8066           case BlackASideCastleFR:
8067           /* POP Fabien */
8068             fromX = currentMoveString[0] - AAA;
8069             fromY = currentMoveString[1] - ONE;
8070             toX = currentMoveString[2] - AAA;
8071             toY = currentMoveString[3] - ONE;
8072             promoChar = currentMoveString[4];
8073             break;
8074           case WhiteDrop:
8075           case BlackDrop:
8076             fromX = moveType == WhiteDrop ?
8077               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8078             (int) CharToPiece(ToLower(currentMoveString[0]));
8079             fromY = DROP_RANK;
8080             toX = currentMoveString[2] - AAA;
8081             toY = currentMoveString[3] - ONE;
8082             promoChar = NULLCHAR;
8083             break;
8084           case AmbiguousMove:
8085             /* bug? */
8086             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8087   if (appData.debugMode) {
8088     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8089     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8090     setbuf(debugFP, NULL);
8091   }
8092             DisplayError(buf, 0);
8093             return;
8094           case ImpossibleMove:
8095             /* bug? */
8096             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8097   if (appData.debugMode) {
8098     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8099     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8100     setbuf(debugFP, NULL);
8101   }
8102             DisplayError(buf, 0);
8103             return;
8104           case (ChessMove) 0:   /* end of file */
8105             if (boardIndex < backwardMostMove) {
8106                 /* Oops, gap.  How did that happen? */
8107                 DisplayError(_("Gap in move list"), 0);
8108                 return;
8109             }
8110             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8111             if (boardIndex > forwardMostMove) {
8112                 forwardMostMove = boardIndex;
8113             }
8114             return;
8115           case ElapsedTime:
8116             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8117                 strcat(parseList[boardIndex-1], " ");
8118                 strcat(parseList[boardIndex-1], yy_text);
8119             }
8120             continue;
8121           case Comment:
8122           case PGNTag:
8123           case NAG:
8124           default:
8125             /* ignore */
8126             continue;
8127           case WhiteWins:
8128           case BlackWins:
8129           case GameIsDrawn:
8130           case GameUnfinished:
8131             if (gameMode == IcsExamining) {
8132                 if (boardIndex < backwardMostMove) {
8133                     /* Oops, gap.  How did that happen? */
8134                     return;
8135                 }
8136                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8137                 return;
8138             }
8139             gameInfo.result = moveType;
8140             p = strchr(yy_text, '{');
8141             if (p == NULL) p = strchr(yy_text, '(');
8142             if (p == NULL) {
8143                 p = yy_text;
8144                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8145             } else {
8146                 q = strchr(p, *p == '{' ? '}' : ')');
8147                 if (q != NULL) *q = NULLCHAR;
8148                 p++;
8149             }
8150             gameInfo.resultDetails = StrSave(p);
8151             continue;
8152         }
8153         if (boardIndex >= forwardMostMove &&
8154             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8155             backwardMostMove = blackPlaysFirst ? 1 : 0;
8156             return;
8157         }
8158         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8159                                  fromY, fromX, toY, toX, promoChar,
8160                                  parseList[boardIndex]);
8161         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8162         /* currentMoveString is set as a side-effect of yylex */
8163         strcpy(moveList[boardIndex], currentMoveString);
8164         strcat(moveList[boardIndex], "\n");
8165         boardIndex++;
8166         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8167         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8168           case MT_NONE:
8169           case MT_STALEMATE:
8170           default:
8171             break;
8172           case MT_CHECK:
8173             if(gameInfo.variant != VariantShogi)
8174                 strcat(parseList[boardIndex - 1], "+");
8175             break;
8176           case MT_CHECKMATE:
8177           case MT_STAINMATE:
8178             strcat(parseList[boardIndex - 1], "#");
8179             break;
8180         }
8181     }
8182 }
8183
8184
8185 /* Apply a move to the given board  */
8186 void
8187 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8188      int fromX, fromY, toX, toY;
8189      int promoChar;
8190      Board board;
8191 {
8192   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8193   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8194
8195     /* [HGM] compute & store e.p. status and castling rights for new position */
8196     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8197     { int i;
8198
8199       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8200       oldEP = (signed char)board[EP_STATUS];
8201       board[EP_STATUS] = EP_NONE;
8202
8203       if( board[toY][toX] != EmptySquare ) 
8204            board[EP_STATUS] = EP_CAPTURE;  
8205
8206       if( board[fromY][fromX] == WhitePawn ) {
8207            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8208                board[EP_STATUS] = EP_PAWN_MOVE;
8209            if( toY-fromY==2) {
8210                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8211                         gameInfo.variant != VariantBerolina || toX < fromX)
8212                       board[EP_STATUS] = toX | berolina;
8213                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8214                         gameInfo.variant != VariantBerolina || toX > fromX) 
8215                       board[EP_STATUS] = toX;
8216            }
8217       } else 
8218       if( board[fromY][fromX] == BlackPawn ) {
8219            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8220                board[EP_STATUS] = EP_PAWN_MOVE; 
8221            if( toY-fromY== -2) {
8222                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8223                         gameInfo.variant != VariantBerolina || toX < fromX)
8224                       board[EP_STATUS] = toX | berolina;
8225                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8226                         gameInfo.variant != VariantBerolina || toX > fromX) 
8227                       board[EP_STATUS] = toX;
8228            }
8229        }
8230
8231        for(i=0; i<nrCastlingRights; i++) {
8232            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8233               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8234              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8235        }
8236
8237     }
8238
8239   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8240   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8241        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8242          
8243   if (fromX == toX && fromY == toY) return;
8244
8245   if (fromY == DROP_RANK) {
8246         /* must be first */
8247         piece = board[toY][toX] = (ChessSquare) fromX;
8248   } else {
8249      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8250      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8251      if(gameInfo.variant == VariantKnightmate)
8252          king += (int) WhiteUnicorn - (int) WhiteKing;
8253
8254     /* Code added by Tord: */
8255     /* FRC castling assumed when king captures friendly rook. */
8256     if (board[fromY][fromX] == WhiteKing &&
8257              board[toY][toX] == WhiteRook) {
8258       board[fromY][fromX] = EmptySquare;
8259       board[toY][toX] = EmptySquare;
8260       if(toX > fromX) {
8261         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8262       } else {
8263         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8264       }
8265     } else if (board[fromY][fromX] == BlackKing &&
8266                board[toY][toX] == BlackRook) {
8267       board[fromY][fromX] = EmptySquare;
8268       board[toY][toX] = EmptySquare;
8269       if(toX > fromX) {
8270         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8271       } else {
8272         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8273       }
8274     /* End of code added by Tord */
8275
8276     } else if (board[fromY][fromX] == king
8277         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8278         && toY == fromY && toX > fromX+1) {
8279         board[fromY][fromX] = EmptySquare;
8280         board[toY][toX] = king;
8281         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8282         board[fromY][BOARD_RGHT-1] = EmptySquare;
8283     } else if (board[fromY][fromX] == king
8284         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8285                && toY == fromY && toX < fromX-1) {
8286         board[fromY][fromX] = EmptySquare;
8287         board[toY][toX] = king;
8288         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8289         board[fromY][BOARD_LEFT] = EmptySquare;
8290     } else if (board[fromY][fromX] == WhitePawn
8291                && toY >= BOARD_HEIGHT-promoRank
8292                && gameInfo.variant != VariantXiangqi
8293                ) {
8294         /* white pawn promotion */
8295         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8296         if (board[toY][toX] == EmptySquare) {
8297             board[toY][toX] = WhiteQueen;
8298         }
8299         if(gameInfo.variant==VariantBughouse ||
8300            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8301             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8302         board[fromY][fromX] = EmptySquare;
8303     } else if ((fromY == BOARD_HEIGHT-4)
8304                && (toX != fromX)
8305                && gameInfo.variant != VariantXiangqi
8306                && gameInfo.variant != VariantBerolina
8307                && (board[fromY][fromX] == WhitePawn)
8308                && (board[toY][toX] == EmptySquare)) {
8309         board[fromY][fromX] = EmptySquare;
8310         board[toY][toX] = WhitePawn;
8311         captured = board[toY - 1][toX];
8312         board[toY - 1][toX] = EmptySquare;
8313     } else if ((fromY == BOARD_HEIGHT-4)
8314                && (toX == fromX)
8315                && gameInfo.variant == VariantBerolina
8316                && (board[fromY][fromX] == WhitePawn)
8317                && (board[toY][toX] == EmptySquare)) {
8318         board[fromY][fromX] = EmptySquare;
8319         board[toY][toX] = WhitePawn;
8320         if(oldEP & EP_BEROLIN_A) {
8321                 captured = board[fromY][fromX-1];
8322                 board[fromY][fromX-1] = EmptySquare;
8323         }else{  captured = board[fromY][fromX+1];
8324                 board[fromY][fromX+1] = EmptySquare;
8325         }
8326     } else if (board[fromY][fromX] == king
8327         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8328                && toY == fromY && toX > fromX+1) {
8329         board[fromY][fromX] = EmptySquare;
8330         board[toY][toX] = king;
8331         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8332         board[fromY][BOARD_RGHT-1] = EmptySquare;
8333     } else if (board[fromY][fromX] == king
8334         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8335                && toY == fromY && toX < fromX-1) {
8336         board[fromY][fromX] = EmptySquare;
8337         board[toY][toX] = king;
8338         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8339         board[fromY][BOARD_LEFT] = EmptySquare;
8340     } else if (fromY == 7 && fromX == 3
8341                && board[fromY][fromX] == BlackKing
8342                && toY == 7 && toX == 5) {
8343         board[fromY][fromX] = EmptySquare;
8344         board[toY][toX] = BlackKing;
8345         board[fromY][7] = EmptySquare;
8346         board[toY][4] = BlackRook;
8347     } else if (fromY == 7 && fromX == 3
8348                && board[fromY][fromX] == BlackKing
8349                && toY == 7 && toX == 1) {
8350         board[fromY][fromX] = EmptySquare;
8351         board[toY][toX] = BlackKing;
8352         board[fromY][0] = EmptySquare;
8353         board[toY][2] = BlackRook;
8354     } else if (board[fromY][fromX] == BlackPawn
8355                && toY < promoRank
8356                && gameInfo.variant != VariantXiangqi
8357                ) {
8358         /* black pawn promotion */
8359         board[toY][toX] = CharToPiece(ToLower(promoChar));
8360         if (board[toY][toX] == EmptySquare) {
8361             board[toY][toX] = BlackQueen;
8362         }
8363         if(gameInfo.variant==VariantBughouse ||
8364            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8365             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8366         board[fromY][fromX] = EmptySquare;
8367     } else if ((fromY == 3)
8368                && (toX != fromX)
8369                && gameInfo.variant != VariantXiangqi
8370                && gameInfo.variant != VariantBerolina
8371                && (board[fromY][fromX] == BlackPawn)
8372                && (board[toY][toX] == EmptySquare)) {
8373         board[fromY][fromX] = EmptySquare;
8374         board[toY][toX] = BlackPawn;
8375         captured = board[toY + 1][toX];
8376         board[toY + 1][toX] = EmptySquare;
8377     } else if ((fromY == 3)
8378                && (toX == fromX)
8379                && gameInfo.variant == VariantBerolina
8380                && (board[fromY][fromX] == BlackPawn)
8381                && (board[toY][toX] == EmptySquare)) {
8382         board[fromY][fromX] = EmptySquare;
8383         board[toY][toX] = BlackPawn;
8384         if(oldEP & EP_BEROLIN_A) {
8385                 captured = board[fromY][fromX-1];
8386                 board[fromY][fromX-1] = EmptySquare;
8387         }else{  captured = board[fromY][fromX+1];
8388                 board[fromY][fromX+1] = EmptySquare;
8389         }
8390     } else {
8391         board[toY][toX] = board[fromY][fromX];
8392         board[fromY][fromX] = EmptySquare;
8393     }
8394
8395     /* [HGM] now we promote for Shogi, if needed */
8396     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8397         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8398   }
8399
8400     if (gameInfo.holdingsWidth != 0) {
8401
8402       /* !!A lot more code needs to be written to support holdings  */
8403       /* [HGM] OK, so I have written it. Holdings are stored in the */
8404       /* penultimate board files, so they are automaticlly stored   */
8405       /* in the game history.                                       */
8406       if (fromY == DROP_RANK) {
8407         /* Delete from holdings, by decreasing count */
8408         /* and erasing image if necessary            */
8409         p = (int) fromX;
8410         if(p < (int) BlackPawn) { /* white drop */
8411              p -= (int)WhitePawn;
8412                  p = PieceToNumber((ChessSquare)p);
8413              if(p >= gameInfo.holdingsSize) p = 0;
8414              if(--board[p][BOARD_WIDTH-2] <= 0)
8415                   board[p][BOARD_WIDTH-1] = EmptySquare;
8416              if((int)board[p][BOARD_WIDTH-2] < 0)
8417                         board[p][BOARD_WIDTH-2] = 0;
8418         } else {                  /* black drop */
8419              p -= (int)BlackPawn;
8420                  p = PieceToNumber((ChessSquare)p);
8421              if(p >= gameInfo.holdingsSize) p = 0;
8422              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8423                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8424              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8425                         board[BOARD_HEIGHT-1-p][1] = 0;
8426         }
8427       }
8428       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8429           && gameInfo.variant != VariantBughouse        ) {
8430         /* [HGM] holdings: Add to holdings, if holdings exist */
8431         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8432                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8433                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8434         }
8435         p = (int) captured;
8436         if (p >= (int) BlackPawn) {
8437           p -= (int)BlackPawn;
8438           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8439                   /* in Shogi restore piece to its original  first */
8440                   captured = (ChessSquare) (DEMOTED captured);
8441                   p = DEMOTED p;
8442           }
8443           p = PieceToNumber((ChessSquare)p);
8444           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8445           board[p][BOARD_WIDTH-2]++;
8446           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8447         } else {
8448           p -= (int)WhitePawn;
8449           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8450                   captured = (ChessSquare) (DEMOTED captured);
8451                   p = DEMOTED p;
8452           }
8453           p = PieceToNumber((ChessSquare)p);
8454           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8455           board[BOARD_HEIGHT-1-p][1]++;
8456           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8457         }
8458       }
8459     } else if (gameInfo.variant == VariantAtomic) {
8460       if (captured != EmptySquare) {
8461         int y, x;
8462         for (y = toY-1; y <= toY+1; y++) {
8463           for (x = toX-1; x <= toX+1; x++) {
8464             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8465                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8466               board[y][x] = EmptySquare;
8467             }
8468           }
8469         }
8470         board[toY][toX] = EmptySquare;
8471       }
8472     }
8473     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8474         /* [HGM] Shogi promotions */
8475         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8476     }
8477
8478     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8479                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8480         // [HGM] superchess: take promotion piece out of holdings
8481         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8482         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8483             if(!--board[k][BOARD_WIDTH-2])
8484                 board[k][BOARD_WIDTH-1] = EmptySquare;
8485         } else {
8486             if(!--board[BOARD_HEIGHT-1-k][1])
8487                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8488         }
8489     }
8490
8491 }
8492
8493 /* Updates forwardMostMove */
8494 void
8495 MakeMove(fromX, fromY, toX, toY, promoChar)
8496      int fromX, fromY, toX, toY;
8497      int promoChar;
8498 {
8499 //    forwardMostMove++; // [HGM] bare: moved downstream
8500
8501     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8502         int timeLeft; static int lastLoadFlag=0; int king, piece;
8503         piece = boards[forwardMostMove][fromY][fromX];
8504         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8505         if(gameInfo.variant == VariantKnightmate)
8506             king += (int) WhiteUnicorn - (int) WhiteKing;
8507         if(forwardMostMove == 0) {
8508             if(blackPlaysFirst) 
8509                 fprintf(serverMoves, "%s;", second.tidy);
8510             fprintf(serverMoves, "%s;", first.tidy);
8511             if(!blackPlaysFirst) 
8512                 fprintf(serverMoves, "%s;", second.tidy);
8513         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8514         lastLoadFlag = loadFlag;
8515         // print base move
8516         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8517         // print castling suffix
8518         if( toY == fromY && piece == king ) {
8519             if(toX-fromX > 1)
8520                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8521             if(fromX-toX >1)
8522                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8523         }
8524         // e.p. suffix
8525         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8526              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8527              boards[forwardMostMove][toY][toX] == EmptySquare
8528              && fromX != toX )
8529                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8530         // promotion suffix
8531         if(promoChar != NULLCHAR)
8532                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8533         if(!loadFlag) {
8534             fprintf(serverMoves, "/%d/%d",
8535                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8536             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8537             else                      timeLeft = blackTimeRemaining/1000;
8538             fprintf(serverMoves, "/%d", timeLeft);
8539         }
8540         fflush(serverMoves);
8541     }
8542
8543     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8544       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8545                         0, 1);
8546       return;
8547     }
8548     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8549     if (commentList[forwardMostMove+1] != NULL) {
8550         free(commentList[forwardMostMove+1]);
8551         commentList[forwardMostMove+1] = NULL;
8552     }
8553     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8554     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8555     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8556     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8557     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8558     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8559     gameInfo.result = GameUnfinished;
8560     if (gameInfo.resultDetails != NULL) {
8561         free(gameInfo.resultDetails);
8562         gameInfo.resultDetails = NULL;
8563     }
8564     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8565                               moveList[forwardMostMove - 1]);
8566     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8567                              PosFlags(forwardMostMove - 1),
8568                              fromY, fromX, toY, toX, promoChar,
8569                              parseList[forwardMostMove - 1]);
8570     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8571       case MT_NONE:
8572       case MT_STALEMATE:
8573       default:
8574         break;
8575       case MT_CHECK:
8576         if(gameInfo.variant != VariantShogi)
8577             strcat(parseList[forwardMostMove - 1], "+");
8578         break;
8579       case MT_CHECKMATE:
8580       case MT_STAINMATE:
8581         strcat(parseList[forwardMostMove - 1], "#");
8582         break;
8583     }
8584     if (appData.debugMode) {
8585         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8586     }
8587
8588 }
8589
8590 /* Updates currentMove if not pausing */
8591 void
8592 ShowMove(fromX, fromY, toX, toY)
8593 {
8594     int instant = (gameMode == PlayFromGameFile) ?
8595         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8596     if(appData.noGUI) return;
8597     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8598         if (!instant) {
8599             if (forwardMostMove == currentMove + 1) {
8600                 AnimateMove(boards[forwardMostMove - 1],
8601                             fromX, fromY, toX, toY);
8602             }
8603             if (appData.highlightLastMove) {
8604                 SetHighlights(fromX, fromY, toX, toY);
8605             }
8606         }
8607         currentMove = forwardMostMove;
8608     }
8609
8610     if (instant) return;
8611
8612     DisplayMove(currentMove - 1);
8613     DrawPosition(FALSE, boards[currentMove]);
8614     DisplayBothClocks();
8615     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8616 }
8617
8618 void SendEgtPath(ChessProgramState *cps)
8619 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8620         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8621
8622         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8623
8624         while(*p) {
8625             char c, *q = name+1, *r, *s;
8626
8627             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8628             while(*p && *p != ',') *q++ = *p++;
8629             *q++ = ':'; *q = 0;
8630             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8631                 strcmp(name, ",nalimov:") == 0 ) {
8632                 // take nalimov path from the menu-changeable option first, if it is defined
8633                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8634                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8635             } else
8636             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8637                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8638                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8639                 s = r = StrStr(s, ":") + 1; // beginning of path info
8640                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8641                 c = *r; *r = 0;             // temporarily null-terminate path info
8642                     *--q = 0;               // strip of trailig ':' from name
8643                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8644                 *r = c;
8645                 SendToProgram(buf,cps);     // send egtbpath command for this format
8646             }
8647             if(*p == ',') p++; // read away comma to position for next format name
8648         }
8649 }
8650
8651 void
8652 InitChessProgram(cps, setup)
8653      ChessProgramState *cps;
8654      int setup; /* [HGM] needed to setup FRC opening position */
8655 {
8656     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8657     if (appData.noChessProgram) return;
8658     hintRequested = FALSE;
8659     bookRequested = FALSE;
8660
8661     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8662     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8663     if(cps->memSize) { /* [HGM] memory */
8664         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8665         SendToProgram(buf, cps);
8666     }
8667     SendEgtPath(cps); /* [HGM] EGT */
8668     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8669         sprintf(buf, "cores %d\n", appData.smpCores);
8670         SendToProgram(buf, cps);
8671     }
8672
8673     SendToProgram(cps->initString, cps);
8674     if (gameInfo.variant != VariantNormal &&
8675         gameInfo.variant != VariantLoadable
8676         /* [HGM] also send variant if board size non-standard */
8677         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8678                                             ) {
8679       char *v = VariantName(gameInfo.variant);
8680       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8681         /* [HGM] in protocol 1 we have to assume all variants valid */
8682         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8683         DisplayFatalError(buf, 0, 1);
8684         return;
8685       }
8686
8687       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8688       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8689       if( gameInfo.variant == VariantXiangqi )
8690            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8691       if( gameInfo.variant == VariantShogi )
8692            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8693       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8694            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8695       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8696                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8697            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8698       if( gameInfo.variant == VariantCourier )
8699            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8700       if( gameInfo.variant == VariantSuper )
8701            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8702       if( gameInfo.variant == VariantGreat )
8703            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8704
8705       if(overruled) {
8706            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8707                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8708            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8709            if(StrStr(cps->variants, b) == NULL) { 
8710                // specific sized variant not known, check if general sizing allowed
8711                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8712                    if(StrStr(cps->variants, "boardsize") == NULL) {
8713                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8714                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8715                        DisplayFatalError(buf, 0, 1);
8716                        return;
8717                    }
8718                    /* [HGM] here we really should compare with the maximum supported board size */
8719                }
8720            }
8721       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8722       sprintf(buf, "variant %s\n", b);
8723       SendToProgram(buf, cps);
8724     }
8725     currentlyInitializedVariant = gameInfo.variant;
8726
8727     /* [HGM] send opening position in FRC to first engine */
8728     if(setup) {
8729           SendToProgram("force\n", cps);
8730           SendBoard(cps, 0);
8731           /* engine is now in force mode! Set flag to wake it up after first move. */
8732           setboardSpoiledMachineBlack = 1;
8733     }
8734
8735     if (cps->sendICS) {
8736       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8737       SendToProgram(buf, cps);
8738     }
8739     cps->maybeThinking = FALSE;
8740     cps->offeredDraw = 0;
8741     if (!appData.icsActive) {
8742         SendTimeControl(cps, movesPerSession, timeControl,
8743                         timeIncrement, appData.searchDepth,
8744                         searchTime);
8745     }
8746     if (appData.showThinking 
8747         // [HGM] thinking: four options require thinking output to be sent
8748         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8749                                 ) {
8750         SendToProgram("post\n", cps);
8751     }
8752     SendToProgram("hard\n", cps);
8753     if (!appData.ponderNextMove) {
8754         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8755            it without being sure what state we are in first.  "hard"
8756            is not a toggle, so that one is OK.
8757          */
8758         SendToProgram("easy\n", cps);
8759     }
8760     if (cps->usePing) {
8761       sprintf(buf, "ping %d\n", ++cps->lastPing);
8762       SendToProgram(buf, cps);
8763     }
8764     cps->initDone = TRUE;
8765 }   
8766
8767
8768 void
8769 StartChessProgram(cps)
8770      ChessProgramState *cps;
8771 {
8772     char buf[MSG_SIZ];
8773     int err;
8774
8775     if (appData.noChessProgram) return;
8776     cps->initDone = FALSE;
8777
8778     if (strcmp(cps->host, "localhost") == 0) {
8779         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8780     } else if (*appData.remoteShell == NULLCHAR) {
8781         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8782     } else {
8783         if (*appData.remoteUser == NULLCHAR) {
8784           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8785                     cps->program);
8786         } else {
8787           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8788                     cps->host, appData.remoteUser, cps->program);
8789         }
8790         err = StartChildProcess(buf, "", &cps->pr);
8791     }
8792     
8793     if (err != 0) {
8794         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8795         DisplayFatalError(buf, err, 1);
8796         cps->pr = NoProc;
8797         cps->isr = NULL;
8798         return;
8799     }
8800     
8801     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8802     if (cps->protocolVersion > 1) {
8803       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8804       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8805       cps->comboCnt = 0;  //                and values of combo boxes
8806       SendToProgram(buf, cps);
8807     } else {
8808       SendToProgram("xboard\n", cps);
8809     }
8810 }
8811
8812
8813 void
8814 TwoMachinesEventIfReady P((void))
8815 {
8816   if (first.lastPing != first.lastPong) {
8817     DisplayMessage("", _("Waiting for first chess program"));
8818     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8819     return;
8820   }
8821   if (second.lastPing != second.lastPong) {
8822     DisplayMessage("", _("Waiting for second chess program"));
8823     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8824     return;
8825   }
8826   ThawUI();
8827   TwoMachinesEvent();
8828 }
8829
8830 void
8831 NextMatchGame P((void))
8832 {
8833     int index; /* [HGM] autoinc: step load index during match */
8834     Reset(FALSE, TRUE);
8835     if (*appData.loadGameFile != NULLCHAR) {
8836         index = appData.loadGameIndex;
8837         if(index < 0) { // [HGM] autoinc
8838             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8839             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8840         } 
8841         LoadGameFromFile(appData.loadGameFile,
8842                          index,
8843                          appData.loadGameFile, FALSE);
8844     } else if (*appData.loadPositionFile != NULLCHAR) {
8845         index = appData.loadPositionIndex;
8846         if(index < 0) { // [HGM] autoinc
8847             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8848             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8849         } 
8850         LoadPositionFromFile(appData.loadPositionFile,
8851                              index,
8852                              appData.loadPositionFile);
8853     }
8854     TwoMachinesEventIfReady();
8855 }
8856
8857 void UserAdjudicationEvent( int result )
8858 {
8859     ChessMove gameResult = GameIsDrawn;
8860
8861     if( result > 0 ) {
8862         gameResult = WhiteWins;
8863     }
8864     else if( result < 0 ) {
8865         gameResult = BlackWins;
8866     }
8867
8868     if( gameMode == TwoMachinesPlay ) {
8869         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8870     }
8871 }
8872
8873
8874 // [HGM] save: calculate checksum of game to make games easily identifiable
8875 int StringCheckSum(char *s)
8876 {
8877         int i = 0;
8878         if(s==NULL) return 0;
8879         while(*s) i = i*259 + *s++;
8880         return i;
8881 }
8882
8883 int GameCheckSum()
8884 {
8885         int i, sum=0;
8886         for(i=backwardMostMove; i<forwardMostMove; i++) {
8887                 sum += pvInfoList[i].depth;
8888                 sum += StringCheckSum(parseList[i]);
8889                 sum += StringCheckSum(commentList[i]);
8890                 sum *= 261;
8891         }
8892         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8893         return sum + StringCheckSum(commentList[i]);
8894 } // end of save patch
8895
8896 void
8897 GameEnds(result, resultDetails, whosays)
8898      ChessMove result;
8899      char *resultDetails;
8900      int whosays;
8901 {
8902     GameMode nextGameMode;
8903     int isIcsGame;
8904     char buf[MSG_SIZ];
8905
8906     if(endingGame) return; /* [HGM] crash: forbid recursion */
8907     endingGame = 1;
8908     if(twoBoards) { // [HGM] dual: switch back to one board
8909         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8910         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8911     }
8912     if (appData.debugMode) {
8913       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8914               result, resultDetails ? resultDetails : "(null)", whosays);
8915     }
8916
8917     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8918
8919     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8920         /* If we are playing on ICS, the server decides when the
8921            game is over, but the engine can offer to draw, claim 
8922            a draw, or resign. 
8923          */
8924 #if ZIPPY
8925         if (appData.zippyPlay && first.initDone) {
8926             if (result == GameIsDrawn) {
8927                 /* In case draw still needs to be claimed */
8928                 SendToICS(ics_prefix);
8929                 SendToICS("draw\n");
8930             } else if (StrCaseStr(resultDetails, "resign")) {
8931                 SendToICS(ics_prefix);
8932                 SendToICS("resign\n");
8933             }
8934         }
8935 #endif
8936         endingGame = 0; /* [HGM] crash */
8937         return;
8938     }
8939
8940     /* If we're loading the game from a file, stop */
8941     if (whosays == GE_FILE) {
8942       (void) StopLoadGameTimer();
8943       gameFileFP = NULL;
8944     }
8945
8946     /* Cancel draw offers */
8947     first.offeredDraw = second.offeredDraw = 0;
8948
8949     /* If this is an ICS game, only ICS can really say it's done;
8950        if not, anyone can. */
8951     isIcsGame = (gameMode == IcsPlayingWhite || 
8952                  gameMode == IcsPlayingBlack || 
8953                  gameMode == IcsObserving    || 
8954                  gameMode == IcsExamining);
8955
8956     if (!isIcsGame || whosays == GE_ICS) {
8957         /* OK -- not an ICS game, or ICS said it was done */
8958         StopClocks();
8959         if (!isIcsGame && !appData.noChessProgram) 
8960           SetUserThinkingEnables();
8961     
8962         /* [HGM] if a machine claims the game end we verify this claim */
8963         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8964             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8965                 char claimer;
8966                 ChessMove trueResult = (ChessMove) -1;
8967
8968                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8969                                             first.twoMachinesColor[0] :
8970                                             second.twoMachinesColor[0] ;
8971
8972                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8973                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8974                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8975                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8976                 } else
8977                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8978                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8979                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8980                 } else
8981                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8982                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8983                 }
8984
8985                 // now verify win claims, but not in drop games, as we don't understand those yet
8986                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8987                                                  || gameInfo.variant == VariantGreat) &&
8988                     (result == WhiteWins && claimer == 'w' ||
8989                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8990                       if (appData.debugMode) {
8991                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8992                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8993                       }
8994                       if(result != trueResult) {
8995                               sprintf(buf, "False win claim: '%s'", resultDetails);
8996                               result = claimer == 'w' ? BlackWins : WhiteWins;
8997                               resultDetails = buf;
8998                       }
8999                 } else
9000                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9001                     && (forwardMostMove <= backwardMostMove ||
9002                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9003                         (claimer=='b')==(forwardMostMove&1))
9004                                                                                   ) {
9005                       /* [HGM] verify: draws that were not flagged are false claims */
9006                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9007                       result = claimer == 'w' ? BlackWins : WhiteWins;
9008                       resultDetails = buf;
9009                 }
9010                 /* (Claiming a loss is accepted no questions asked!) */
9011             }
9012             /* [HGM] bare: don't allow bare King to win */
9013             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9014                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9015                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9016                && result != GameIsDrawn)
9017             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9018                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9019                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9020                         if(p >= 0 && p <= (int)WhiteKing) k++;
9021                 }
9022                 if (appData.debugMode) {
9023                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9024                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9025                 }
9026                 if(k <= 1) {
9027                         result = GameIsDrawn;
9028                         sprintf(buf, "%s but bare king", resultDetails);
9029                         resultDetails = buf;
9030                 }
9031             }
9032         }
9033
9034
9035         if(serverMoves != NULL && !loadFlag) { char c = '=';
9036             if(result==WhiteWins) c = '+';
9037             if(result==BlackWins) c = '-';
9038             if(resultDetails != NULL)
9039                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9040         }
9041         if (resultDetails != NULL) {
9042             gameInfo.result = result;
9043             gameInfo.resultDetails = StrSave(resultDetails);
9044
9045             /* display last move only if game was not loaded from file */
9046             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9047                 DisplayMove(currentMove - 1);
9048     
9049             if (forwardMostMove != 0) {
9050                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9051                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9052                                                                 ) {
9053                     if (*appData.saveGameFile != NULLCHAR) {
9054                         SaveGameToFile(appData.saveGameFile, TRUE);
9055                     } else if (appData.autoSaveGames) {
9056                         AutoSaveGame();
9057                     }
9058                     if (*appData.savePositionFile != NULLCHAR) {
9059                         SavePositionToFile(appData.savePositionFile);
9060                     }
9061                 }
9062             }
9063
9064             /* Tell program how game ended in case it is learning */
9065             /* [HGM] Moved this to after saving the PGN, just in case */
9066             /* engine died and we got here through time loss. In that */
9067             /* case we will get a fatal error writing the pipe, which */
9068             /* would otherwise lose us the PGN.                       */
9069             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9070             /* output during GameEnds should never be fatal anymore   */
9071             if (gameMode == MachinePlaysWhite ||
9072                 gameMode == MachinePlaysBlack ||
9073                 gameMode == TwoMachinesPlay ||
9074                 gameMode == IcsPlayingWhite ||
9075                 gameMode == IcsPlayingBlack ||
9076                 gameMode == BeginningOfGame) {
9077                 char buf[MSG_SIZ];
9078                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9079                         resultDetails);
9080                 if (first.pr != NoProc) {
9081                     SendToProgram(buf, &first);
9082                 }
9083                 if (second.pr != NoProc &&
9084                     gameMode == TwoMachinesPlay) {
9085                     SendToProgram(buf, &second);
9086                 }
9087             }
9088         }
9089
9090         if (appData.icsActive) {
9091             if (appData.quietPlay &&
9092                 (gameMode == IcsPlayingWhite ||
9093                  gameMode == IcsPlayingBlack)) {
9094                 SendToICS(ics_prefix);
9095                 SendToICS("set shout 1\n");
9096             }
9097             nextGameMode = IcsIdle;
9098             ics_user_moved = FALSE;
9099             /* clean up premove.  It's ugly when the game has ended and the
9100              * premove highlights are still on the board.
9101              */
9102             if (gotPremove) {
9103               gotPremove = FALSE;
9104               ClearPremoveHighlights();
9105               DrawPosition(FALSE, boards[currentMove]);
9106             }
9107             if (whosays == GE_ICS) {
9108                 switch (result) {
9109                 case WhiteWins:
9110                     if (gameMode == IcsPlayingWhite)
9111                         PlayIcsWinSound();
9112                     else if(gameMode == IcsPlayingBlack)
9113                         PlayIcsLossSound();
9114                     break;
9115                 case BlackWins:
9116                     if (gameMode == IcsPlayingBlack)
9117                         PlayIcsWinSound();
9118                     else if(gameMode == IcsPlayingWhite)
9119                         PlayIcsLossSound();
9120                     break;
9121                 case GameIsDrawn:
9122                     PlayIcsDrawSound();
9123                     break;
9124                 default:
9125                     PlayIcsUnfinishedSound();
9126                 }
9127             }
9128         } else if (gameMode == EditGame ||
9129                    gameMode == PlayFromGameFile || 
9130                    gameMode == AnalyzeMode || 
9131                    gameMode == AnalyzeFile) {
9132             nextGameMode = gameMode;
9133         } else {
9134             nextGameMode = EndOfGame;
9135         }
9136         pausing = FALSE;
9137         ModeHighlight();
9138     } else {
9139         nextGameMode = gameMode;
9140     }
9141
9142     if (appData.noChessProgram) {
9143         gameMode = nextGameMode;
9144         ModeHighlight();
9145         endingGame = 0; /* [HGM] crash */
9146         return;
9147     }
9148
9149     if (first.reuse) {
9150         /* Put first chess program into idle state */
9151         if (first.pr != NoProc &&
9152             (gameMode == MachinePlaysWhite ||
9153              gameMode == MachinePlaysBlack ||
9154              gameMode == TwoMachinesPlay ||
9155              gameMode == IcsPlayingWhite ||
9156              gameMode == IcsPlayingBlack ||
9157              gameMode == BeginningOfGame)) {
9158             SendToProgram("force\n", &first);
9159             if (first.usePing) {
9160               char buf[MSG_SIZ];
9161               sprintf(buf, "ping %d\n", ++first.lastPing);
9162               SendToProgram(buf, &first);
9163             }
9164         }
9165     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9166         /* Kill off first chess program */
9167         if (first.isr != NULL)
9168           RemoveInputSource(first.isr);
9169         first.isr = NULL;
9170     
9171         if (first.pr != NoProc) {
9172             ExitAnalyzeMode();
9173             DoSleep( appData.delayBeforeQuit );
9174             SendToProgram("quit\n", &first);
9175             DoSleep( appData.delayAfterQuit );
9176             DestroyChildProcess(first.pr, first.useSigterm);
9177         }
9178         first.pr = NoProc;
9179     }
9180     if (second.reuse) {
9181         /* Put second chess program into idle state */
9182         if (second.pr != NoProc &&
9183             gameMode == TwoMachinesPlay) {
9184             SendToProgram("force\n", &second);
9185             if (second.usePing) {
9186               char buf[MSG_SIZ];
9187               sprintf(buf, "ping %d\n", ++second.lastPing);
9188               SendToProgram(buf, &second);
9189             }
9190         }
9191     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9192         /* Kill off second chess program */
9193         if (second.isr != NULL)
9194           RemoveInputSource(second.isr);
9195         second.isr = NULL;
9196     
9197         if (second.pr != NoProc) {
9198             DoSleep( appData.delayBeforeQuit );
9199             SendToProgram("quit\n", &second);
9200             DoSleep( appData.delayAfterQuit );
9201             DestroyChildProcess(second.pr, second.useSigterm);
9202         }
9203         second.pr = NoProc;
9204     }
9205
9206     if (matchMode && gameMode == TwoMachinesPlay) {
9207         switch (result) {
9208         case WhiteWins:
9209           if (first.twoMachinesColor[0] == 'w') {
9210             first.matchWins++;
9211           } else {
9212             second.matchWins++;
9213           }
9214           break;
9215         case BlackWins:
9216           if (first.twoMachinesColor[0] == 'b') {
9217             first.matchWins++;
9218           } else {
9219             second.matchWins++;
9220           }
9221           break;
9222         default:
9223           break;
9224         }
9225         if (matchGame < appData.matchGames) {
9226             char *tmp;
9227             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9228                 tmp = first.twoMachinesColor;
9229                 first.twoMachinesColor = second.twoMachinesColor;
9230                 second.twoMachinesColor = tmp;
9231             }
9232             gameMode = nextGameMode;
9233             matchGame++;
9234             if(appData.matchPause>10000 || appData.matchPause<10)
9235                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9236             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9237             endingGame = 0; /* [HGM] crash */
9238             return;
9239         } else {
9240             char buf[MSG_SIZ];
9241             gameMode = nextGameMode;
9242             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9243                     first.tidy, second.tidy,
9244                     first.matchWins, second.matchWins,
9245                     appData.matchGames - (first.matchWins + second.matchWins));
9246             DisplayFatalError(buf, 0, 0);
9247         }
9248     }
9249     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9250         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9251       ExitAnalyzeMode();
9252     gameMode = nextGameMode;
9253     ModeHighlight();
9254     endingGame = 0;  /* [HGM] crash */
9255 }
9256
9257 /* Assumes program was just initialized (initString sent).
9258    Leaves program in force mode. */
9259 void
9260 FeedMovesToProgram(cps, upto) 
9261      ChessProgramState *cps;
9262      int upto;
9263 {
9264     int i;
9265     
9266     if (appData.debugMode)
9267       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9268               startedFromSetupPosition ? "position and " : "",
9269               backwardMostMove, upto, cps->which);
9270     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9271         // [HGM] variantswitch: make engine aware of new variant
9272         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9273                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9274         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9275         SendToProgram(buf, cps);
9276         currentlyInitializedVariant = gameInfo.variant;
9277     }
9278     SendToProgram("force\n", cps);
9279     if (startedFromSetupPosition) {
9280         SendBoard(cps, backwardMostMove);
9281     if (appData.debugMode) {
9282         fprintf(debugFP, "feedMoves\n");
9283     }
9284     }
9285     for (i = backwardMostMove; i < upto; i++) {
9286         SendMoveToProgram(i, cps);
9287     }
9288 }
9289
9290
9291 void
9292 ResurrectChessProgram()
9293 {
9294      /* The chess program may have exited.
9295         If so, restart it and feed it all the moves made so far. */
9296
9297     if (appData.noChessProgram || first.pr != NoProc) return;
9298     
9299     StartChessProgram(&first);
9300     InitChessProgram(&first, FALSE);
9301     FeedMovesToProgram(&first, currentMove);
9302
9303     if (!first.sendTime) {
9304         /* can't tell gnuchess what its clock should read,
9305            so we bow to its notion. */
9306         ResetClocks();
9307         timeRemaining[0][currentMove] = whiteTimeRemaining;
9308         timeRemaining[1][currentMove] = blackTimeRemaining;
9309     }
9310
9311     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9312                 appData.icsEngineAnalyze) && first.analysisSupport) {
9313       SendToProgram("analyze\n", &first);
9314       first.analyzing = TRUE;
9315     }
9316 }
9317
9318 /*
9319  * Button procedures
9320  */
9321 void
9322 Reset(redraw, init)
9323      int redraw, init;
9324 {
9325     int i;
9326
9327     if (appData.debugMode) {
9328         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9329                 redraw, init, gameMode);
9330     }
9331     CleanupTail(); // [HGM] vari: delete any stored variations
9332     pausing = pauseExamInvalid = FALSE;
9333     startedFromSetupPosition = blackPlaysFirst = FALSE;
9334     firstMove = TRUE;
9335     whiteFlag = blackFlag = FALSE;
9336     userOfferedDraw = FALSE;
9337     hintRequested = bookRequested = FALSE;
9338     first.maybeThinking = FALSE;
9339     second.maybeThinking = FALSE;
9340     first.bookSuspend = FALSE; // [HGM] book
9341     second.bookSuspend = FALSE;
9342     thinkOutput[0] = NULLCHAR;
9343     lastHint[0] = NULLCHAR;
9344     ClearGameInfo(&gameInfo);
9345     gameInfo.variant = StringToVariant(appData.variant);
9346     ics_user_moved = ics_clock_paused = FALSE;
9347     ics_getting_history = H_FALSE;
9348     ics_gamenum = -1;
9349     white_holding[0] = black_holding[0] = NULLCHAR;
9350     ClearProgramStats();
9351     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9352     
9353     ResetFrontEnd();
9354     ClearHighlights();
9355     flipView = appData.flipView;
9356     ClearPremoveHighlights();
9357     gotPremove = FALSE;
9358     alarmSounded = FALSE;
9359
9360     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9361     if(appData.serverMovesName != NULL) {
9362         /* [HGM] prepare to make moves file for broadcasting */
9363         clock_t t = clock();
9364         if(serverMoves != NULL) fclose(serverMoves);
9365         serverMoves = fopen(appData.serverMovesName, "r");
9366         if(serverMoves != NULL) {
9367             fclose(serverMoves);
9368             /* delay 15 sec before overwriting, so all clients can see end */
9369             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9370         }
9371         serverMoves = fopen(appData.serverMovesName, "w");
9372     }
9373
9374     ExitAnalyzeMode();
9375     gameMode = BeginningOfGame;
9376     ModeHighlight();
9377     if(appData.icsActive) gameInfo.variant = VariantNormal;
9378     currentMove = forwardMostMove = backwardMostMove = 0;
9379     InitPosition(redraw);
9380     for (i = 0; i < MAX_MOVES; i++) {
9381         if (commentList[i] != NULL) {
9382             free(commentList[i]);
9383             commentList[i] = NULL;
9384         }
9385     }
9386     ResetClocks();
9387     timeRemaining[0][0] = whiteTimeRemaining;
9388     timeRemaining[1][0] = blackTimeRemaining;
9389     if (first.pr == NULL) {
9390         StartChessProgram(&first);
9391     }
9392     if (init) {
9393             InitChessProgram(&first, startedFromSetupPosition);
9394     }
9395     DisplayTitle("");
9396     DisplayMessage("", "");
9397     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9398     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9399 }
9400
9401 void
9402 AutoPlayGameLoop()
9403 {
9404     for (;;) {
9405         if (!AutoPlayOneMove())
9406           return;
9407         if (matchMode || appData.timeDelay == 0)
9408           continue;
9409         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9410           return;
9411         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9412         break;
9413     }
9414 }
9415
9416
9417 int
9418 AutoPlayOneMove()
9419 {
9420     int fromX, fromY, toX, toY;
9421
9422     if (appData.debugMode) {
9423       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9424     }
9425
9426     if (gameMode != PlayFromGameFile)
9427       return FALSE;
9428
9429     if (currentMove >= forwardMostMove) {
9430       gameMode = EditGame;
9431       ModeHighlight();
9432
9433       /* [AS] Clear current move marker at the end of a game */
9434       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9435
9436       return FALSE;
9437     }
9438     
9439     toX = moveList[currentMove][2] - AAA;
9440     toY = moveList[currentMove][3] - ONE;
9441
9442     if (moveList[currentMove][1] == '@') {
9443         if (appData.highlightLastMove) {
9444             SetHighlights(-1, -1, toX, toY);
9445         }
9446     } else {
9447         fromX = moveList[currentMove][0] - AAA;
9448         fromY = moveList[currentMove][1] - ONE;
9449
9450         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9451
9452         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9453
9454         if (appData.highlightLastMove) {
9455             SetHighlights(fromX, fromY, toX, toY);
9456         }
9457     }
9458     DisplayMove(currentMove);
9459     SendMoveToProgram(currentMove++, &first);
9460     DisplayBothClocks();
9461     DrawPosition(FALSE, boards[currentMove]);
9462     // [HGM] PV info: always display, routine tests if empty
9463     DisplayComment(currentMove - 1, commentList[currentMove]);
9464     return TRUE;
9465 }
9466
9467
9468 int
9469 LoadGameOneMove(readAhead)
9470      ChessMove readAhead;
9471 {
9472     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9473     char promoChar = NULLCHAR;
9474     ChessMove moveType;
9475     char move[MSG_SIZ];
9476     char *p, *q;
9477     
9478     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9479         gameMode != AnalyzeMode && gameMode != Training) {
9480         gameFileFP = NULL;
9481         return FALSE;
9482     }
9483     
9484     yyboardindex = forwardMostMove;
9485     if (readAhead != (ChessMove)0) {
9486       moveType = readAhead;
9487     } else {
9488       if (gameFileFP == NULL)
9489           return FALSE;
9490       moveType = (ChessMove) yylex();
9491     }
9492     
9493     done = FALSE;
9494     switch (moveType) {
9495       case Comment:
9496         if (appData.debugMode) 
9497           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9498         p = yy_text;
9499
9500         /* append the comment but don't display it */
9501         AppendComment(currentMove, p, FALSE);
9502         return TRUE;
9503
9504       case WhiteCapturesEnPassant:
9505       case BlackCapturesEnPassant:
9506       case WhitePromotionChancellor:
9507       case BlackPromotionChancellor:
9508       case WhitePromotionArchbishop:
9509       case BlackPromotionArchbishop:
9510       case WhitePromotionCentaur:
9511       case BlackPromotionCentaur:
9512       case WhitePromotionQueen:
9513       case BlackPromotionQueen:
9514       case WhitePromotionRook:
9515       case BlackPromotionRook:
9516       case WhitePromotionBishop:
9517       case BlackPromotionBishop:
9518       case WhitePromotionKnight:
9519       case BlackPromotionKnight:
9520       case WhitePromotionKing:
9521       case BlackPromotionKing:
9522       case NormalMove:
9523       case WhiteKingSideCastle:
9524       case WhiteQueenSideCastle:
9525       case BlackKingSideCastle:
9526       case BlackQueenSideCastle:
9527       case WhiteKingSideCastleWild:
9528       case WhiteQueenSideCastleWild:
9529       case BlackKingSideCastleWild:
9530       case BlackQueenSideCastleWild:
9531       /* PUSH Fabien */
9532       case WhiteHSideCastleFR:
9533       case WhiteASideCastleFR:
9534       case BlackHSideCastleFR:
9535       case BlackASideCastleFR:
9536       /* POP Fabien */
9537         if (appData.debugMode)
9538           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9539         fromX = currentMoveString[0] - AAA;
9540         fromY = currentMoveString[1] - ONE;
9541         toX = currentMoveString[2] - AAA;
9542         toY = currentMoveString[3] - ONE;
9543         promoChar = currentMoveString[4];
9544         break;
9545
9546       case WhiteDrop:
9547       case BlackDrop:
9548         if (appData.debugMode)
9549           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9550         fromX = moveType == WhiteDrop ?
9551           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9552         (int) CharToPiece(ToLower(currentMoveString[0]));
9553         fromY = DROP_RANK;
9554         toX = currentMoveString[2] - AAA;
9555         toY = currentMoveString[3] - ONE;
9556         break;
9557
9558       case WhiteWins:
9559       case BlackWins:
9560       case GameIsDrawn:
9561       case GameUnfinished:
9562         if (appData.debugMode)
9563           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9564         p = strchr(yy_text, '{');
9565         if (p == NULL) p = strchr(yy_text, '(');
9566         if (p == NULL) {
9567             p = yy_text;
9568             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9569         } else {
9570             q = strchr(p, *p == '{' ? '}' : ')');
9571             if (q != NULL) *q = NULLCHAR;
9572             p++;
9573         }
9574         GameEnds(moveType, p, GE_FILE);
9575         done = TRUE;
9576         if (cmailMsgLoaded) {
9577             ClearHighlights();
9578             flipView = WhiteOnMove(currentMove);
9579             if (moveType == GameUnfinished) flipView = !flipView;
9580             if (appData.debugMode)
9581               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9582         }
9583         break;
9584
9585       case (ChessMove) 0:       /* end of file */
9586         if (appData.debugMode)
9587           fprintf(debugFP, "Parser hit end of file\n");
9588         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9589           case MT_NONE:
9590           case MT_CHECK:
9591             break;
9592           case MT_CHECKMATE:
9593           case MT_STAINMATE:
9594             if (WhiteOnMove(currentMove)) {
9595                 GameEnds(BlackWins, "Black mates", GE_FILE);
9596             } else {
9597                 GameEnds(WhiteWins, "White mates", GE_FILE);
9598             }
9599             break;
9600           case MT_STALEMATE:
9601             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9602             break;
9603         }
9604         done = TRUE;
9605         break;
9606
9607       case MoveNumberOne:
9608         if (lastLoadGameStart == GNUChessGame) {
9609             /* GNUChessGames have numbers, but they aren't move numbers */
9610             if (appData.debugMode)
9611               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9612                       yy_text, (int) moveType);
9613             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9614         }
9615         /* else fall thru */
9616
9617       case XBoardGame:
9618       case GNUChessGame:
9619       case PGNTag:
9620         /* Reached start of next game in file */
9621         if (appData.debugMode)
9622           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9623         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9624           case MT_NONE:
9625           case MT_CHECK:
9626             break;
9627           case MT_CHECKMATE:
9628           case MT_STAINMATE:
9629             if (WhiteOnMove(currentMove)) {
9630                 GameEnds(BlackWins, "Black mates", GE_FILE);
9631             } else {
9632                 GameEnds(WhiteWins, "White mates", GE_FILE);
9633             }
9634             break;
9635           case MT_STALEMATE:
9636             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9637             break;
9638         }
9639         done = TRUE;
9640         break;
9641
9642       case PositionDiagram:     /* should not happen; ignore */
9643       case ElapsedTime:         /* ignore */
9644       case NAG:                 /* ignore */
9645         if (appData.debugMode)
9646           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9647                   yy_text, (int) moveType);
9648         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9649
9650       case IllegalMove:
9651         if (appData.testLegality) {
9652             if (appData.debugMode)
9653               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9654             sprintf(move, _("Illegal move: %d.%s%s"),
9655                     (forwardMostMove / 2) + 1,
9656                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9657             DisplayError(move, 0);
9658             done = TRUE;
9659         } else {
9660             if (appData.debugMode)
9661               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9662                       yy_text, currentMoveString);
9663             fromX = currentMoveString[0] - AAA;
9664             fromY = currentMoveString[1] - ONE;
9665             toX = currentMoveString[2] - AAA;
9666             toY = currentMoveString[3] - ONE;
9667             promoChar = currentMoveString[4];
9668         }
9669         break;
9670
9671       case AmbiguousMove:
9672         if (appData.debugMode)
9673           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9674         sprintf(move, _("Ambiguous move: %d.%s%s"),
9675                 (forwardMostMove / 2) + 1,
9676                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9677         DisplayError(move, 0);
9678         done = TRUE;
9679         break;
9680
9681       default:
9682       case ImpossibleMove:
9683         if (appData.debugMode)
9684           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9685         sprintf(move, _("Illegal move: %d.%s%s"),
9686                 (forwardMostMove / 2) + 1,
9687                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9688         DisplayError(move, 0);
9689         done = TRUE;
9690         break;
9691     }
9692
9693     if (done) {
9694         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9695             DrawPosition(FALSE, boards[currentMove]);
9696             DisplayBothClocks();
9697             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9698               DisplayComment(currentMove - 1, commentList[currentMove]);
9699         }
9700         (void) StopLoadGameTimer();
9701         gameFileFP = NULL;
9702         cmailOldMove = forwardMostMove;
9703         return FALSE;
9704     } else {
9705         /* currentMoveString is set as a side-effect of yylex */
9706         strcat(currentMoveString, "\n");
9707         strcpy(moveList[forwardMostMove], currentMoveString);
9708         
9709         thinkOutput[0] = NULLCHAR;
9710         MakeMove(fromX, fromY, toX, toY, promoChar);
9711         currentMove = forwardMostMove;
9712         return TRUE;
9713     }
9714 }
9715
9716 /* Load the nth game from the given file */
9717 int
9718 LoadGameFromFile(filename, n, title, useList)
9719      char *filename;
9720      int n;
9721      char *title;
9722      /*Boolean*/ int useList;
9723 {
9724     FILE *f;
9725     char buf[MSG_SIZ];
9726
9727     if (strcmp(filename, "-") == 0) {
9728         f = stdin;
9729         title = "stdin";
9730     } else {
9731         f = fopen(filename, "rb");
9732         if (f == NULL) {
9733           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9734             DisplayError(buf, errno);
9735             return FALSE;
9736         }
9737     }
9738     if (fseek(f, 0, 0) == -1) {
9739         /* f is not seekable; probably a pipe */
9740         useList = FALSE;
9741     }
9742     if (useList && n == 0) {
9743         int error = GameListBuild(f);
9744         if (error) {
9745             DisplayError(_("Cannot build game list"), error);
9746         } else if (!ListEmpty(&gameList) &&
9747                    ((ListGame *) gameList.tailPred)->number > 1) {
9748             GameListPopUp(f, title);
9749             return TRUE;
9750         }
9751         GameListDestroy();
9752         n = 1;
9753     }
9754     if (n == 0) n = 1;
9755     return LoadGame(f, n, title, FALSE);
9756 }
9757
9758
9759 void
9760 MakeRegisteredMove()
9761 {
9762     int fromX, fromY, toX, toY;
9763     char promoChar;
9764     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9765         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9766           case CMAIL_MOVE:
9767           case CMAIL_DRAW:
9768             if (appData.debugMode)
9769               fprintf(debugFP, "Restoring %s for game %d\n",
9770                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9771     
9772             thinkOutput[0] = NULLCHAR;
9773             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9774             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9775             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9776             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9777             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9778             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9779             MakeMove(fromX, fromY, toX, toY, promoChar);
9780             ShowMove(fromX, fromY, toX, toY);
9781               
9782             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9783               case MT_NONE:
9784               case MT_CHECK:
9785                 break;
9786                 
9787               case MT_CHECKMATE:
9788               case MT_STAINMATE:
9789                 if (WhiteOnMove(currentMove)) {
9790                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9791                 } else {
9792                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9793                 }
9794                 break;
9795                 
9796               case MT_STALEMATE:
9797                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9798                 break;
9799             }
9800
9801             break;
9802             
9803           case CMAIL_RESIGN:
9804             if (WhiteOnMove(currentMove)) {
9805                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9806             } else {
9807                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9808             }
9809             break;
9810             
9811           case CMAIL_ACCEPT:
9812             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9813             break;
9814               
9815           default:
9816             break;
9817         }
9818     }
9819
9820     return;
9821 }
9822
9823 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9824 int
9825 CmailLoadGame(f, gameNumber, title, useList)
9826      FILE *f;
9827      int gameNumber;
9828      char *title;
9829      int useList;
9830 {
9831     int retVal;
9832
9833     if (gameNumber > nCmailGames) {
9834         DisplayError(_("No more games in this message"), 0);
9835         return FALSE;
9836     }
9837     if (f == lastLoadGameFP) {
9838         int offset = gameNumber - lastLoadGameNumber;
9839         if (offset == 0) {
9840             cmailMsg[0] = NULLCHAR;
9841             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9842                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9843                 nCmailMovesRegistered--;
9844             }
9845             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9846             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9847                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9848             }
9849         } else {
9850             if (! RegisterMove()) return FALSE;
9851         }
9852     }
9853
9854     retVal = LoadGame(f, gameNumber, title, useList);
9855
9856     /* Make move registered during previous look at this game, if any */
9857     MakeRegisteredMove();
9858
9859     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9860         commentList[currentMove]
9861           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9862         DisplayComment(currentMove - 1, commentList[currentMove]);
9863     }
9864
9865     return retVal;
9866 }
9867
9868 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9869 int
9870 ReloadGame(offset)
9871      int offset;
9872 {
9873     int gameNumber = lastLoadGameNumber + offset;
9874     if (lastLoadGameFP == NULL) {
9875         DisplayError(_("No game has been loaded yet"), 0);
9876         return FALSE;
9877     }
9878     if (gameNumber <= 0) {
9879         DisplayError(_("Can't back up any further"), 0);
9880         return FALSE;
9881     }
9882     if (cmailMsgLoaded) {
9883         return CmailLoadGame(lastLoadGameFP, gameNumber,
9884                              lastLoadGameTitle, lastLoadGameUseList);
9885     } else {
9886         return LoadGame(lastLoadGameFP, gameNumber,
9887                         lastLoadGameTitle, lastLoadGameUseList);
9888     }
9889 }
9890
9891
9892
9893 /* Load the nth game from open file f */
9894 int
9895 LoadGame(f, gameNumber, title, useList)
9896      FILE *f;
9897      int gameNumber;
9898      char *title;
9899      int useList;
9900 {
9901     ChessMove cm;
9902     char buf[MSG_SIZ];
9903     int gn = gameNumber;
9904     ListGame *lg = NULL;
9905     int numPGNTags = 0;
9906     int err;
9907     GameMode oldGameMode;
9908     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9909
9910     if (appData.debugMode) 
9911         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9912
9913     if (gameMode == Training )
9914         SetTrainingModeOff();
9915
9916     oldGameMode = gameMode;
9917     if (gameMode != BeginningOfGame) {
9918       Reset(FALSE, TRUE);
9919     }
9920
9921     gameFileFP = f;
9922     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9923         fclose(lastLoadGameFP);
9924     }
9925
9926     if (useList) {
9927         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9928         
9929         if (lg) {
9930             fseek(f, lg->offset, 0);
9931             GameListHighlight(gameNumber);
9932             gn = 1;
9933         }
9934         else {
9935             DisplayError(_("Game number out of range"), 0);
9936             return FALSE;
9937         }
9938     } else {
9939         GameListDestroy();
9940         if (fseek(f, 0, 0) == -1) {
9941             if (f == lastLoadGameFP ?
9942                 gameNumber == lastLoadGameNumber + 1 :
9943                 gameNumber == 1) {
9944                 gn = 1;
9945             } else {
9946                 DisplayError(_("Can't seek on game file"), 0);
9947                 return FALSE;
9948             }
9949         }
9950     }
9951     lastLoadGameFP = f;
9952     lastLoadGameNumber = gameNumber;
9953     strcpy(lastLoadGameTitle, title);
9954     lastLoadGameUseList = useList;
9955
9956     yynewfile(f);
9957
9958     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9959       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9960                 lg->gameInfo.black);
9961             DisplayTitle(buf);
9962     } else if (*title != NULLCHAR) {
9963         if (gameNumber > 1) {
9964             sprintf(buf, "%s %d", title, gameNumber);
9965             DisplayTitle(buf);
9966         } else {
9967             DisplayTitle(title);
9968         }
9969     }
9970
9971     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9972         gameMode = PlayFromGameFile;
9973         ModeHighlight();
9974     }
9975
9976     currentMove = forwardMostMove = backwardMostMove = 0;
9977     CopyBoard(boards[0], initialPosition);
9978     StopClocks();
9979
9980     /*
9981      * Skip the first gn-1 games in the file.
9982      * Also skip over anything that precedes an identifiable 
9983      * start of game marker, to avoid being confused by 
9984      * garbage at the start of the file.  Currently 
9985      * recognized start of game markers are the move number "1",
9986      * the pattern "gnuchess .* game", the pattern
9987      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9988      * A game that starts with one of the latter two patterns
9989      * will also have a move number 1, possibly
9990      * following a position diagram.
9991      * 5-4-02: Let's try being more lenient and allowing a game to
9992      * start with an unnumbered move.  Does that break anything?
9993      */
9994     cm = lastLoadGameStart = (ChessMove) 0;
9995     while (gn > 0) {
9996         yyboardindex = forwardMostMove;
9997         cm = (ChessMove) yylex();
9998         switch (cm) {
9999           case (ChessMove) 0:
10000             if (cmailMsgLoaded) {
10001                 nCmailGames = CMAIL_MAX_GAMES - gn;
10002             } else {
10003                 Reset(TRUE, TRUE);
10004                 DisplayError(_("Game not found in file"), 0);
10005             }
10006             return FALSE;
10007
10008           case GNUChessGame:
10009           case XBoardGame:
10010             gn--;
10011             lastLoadGameStart = cm;
10012             break;
10013             
10014           case MoveNumberOne:
10015             switch (lastLoadGameStart) {
10016               case GNUChessGame:
10017               case XBoardGame:
10018               case PGNTag:
10019                 break;
10020               case MoveNumberOne:
10021               case (ChessMove) 0:
10022                 gn--;           /* count this game */
10023                 lastLoadGameStart = cm;
10024                 break;
10025               default:
10026                 /* impossible */
10027                 break;
10028             }
10029             break;
10030
10031           case PGNTag:
10032             switch (lastLoadGameStart) {
10033               case GNUChessGame:
10034               case PGNTag:
10035               case MoveNumberOne:
10036               case (ChessMove) 0:
10037                 gn--;           /* count this game */
10038                 lastLoadGameStart = cm;
10039                 break;
10040               case XBoardGame:
10041                 lastLoadGameStart = cm; /* game counted already */
10042                 break;
10043               default:
10044                 /* impossible */
10045                 break;
10046             }
10047             if (gn > 0) {
10048                 do {
10049                     yyboardindex = forwardMostMove;
10050                     cm = (ChessMove) yylex();
10051                 } while (cm == PGNTag || cm == Comment);
10052             }
10053             break;
10054
10055           case WhiteWins:
10056           case BlackWins:
10057           case GameIsDrawn:
10058             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10059                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10060                     != CMAIL_OLD_RESULT) {
10061                     nCmailResults ++ ;
10062                     cmailResult[  CMAIL_MAX_GAMES
10063                                 - gn - 1] = CMAIL_OLD_RESULT;
10064                 }
10065             }
10066             break;
10067
10068           case NormalMove:
10069             /* Only a NormalMove can be at the start of a game
10070              * without a position diagram. */
10071             if (lastLoadGameStart == (ChessMove) 0) {
10072               gn--;
10073               lastLoadGameStart = MoveNumberOne;
10074             }
10075             break;
10076
10077           default:
10078             break;
10079         }
10080     }
10081     
10082     if (appData.debugMode)
10083       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10084
10085     if (cm == XBoardGame) {
10086         /* Skip any header junk before position diagram and/or move 1 */
10087         for (;;) {
10088             yyboardindex = forwardMostMove;
10089             cm = (ChessMove) yylex();
10090
10091             if (cm == (ChessMove) 0 ||
10092                 cm == GNUChessGame || cm == XBoardGame) {
10093                 /* Empty game; pretend end-of-file and handle later */
10094                 cm = (ChessMove) 0;
10095                 break;
10096             }
10097
10098             if (cm == MoveNumberOne || cm == PositionDiagram ||
10099                 cm == PGNTag || cm == Comment)
10100               break;
10101         }
10102     } else if (cm == GNUChessGame) {
10103         if (gameInfo.event != NULL) {
10104             free(gameInfo.event);
10105         }
10106         gameInfo.event = StrSave(yy_text);
10107     }   
10108
10109     startedFromSetupPosition = FALSE;
10110     while (cm == PGNTag) {
10111         if (appData.debugMode) 
10112           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10113         err = ParsePGNTag(yy_text, &gameInfo);
10114         if (!err) numPGNTags++;
10115
10116         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10117         if(gameInfo.variant != oldVariant) {
10118             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10119             InitPosition(TRUE);
10120             oldVariant = gameInfo.variant;
10121             if (appData.debugMode) 
10122               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10123         }
10124
10125
10126         if (gameInfo.fen != NULL) {
10127           Board initial_position;
10128           startedFromSetupPosition = TRUE;
10129           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10130             Reset(TRUE, TRUE);
10131             DisplayError(_("Bad FEN position in file"), 0);
10132             return FALSE;
10133           }
10134           CopyBoard(boards[0], initial_position);
10135           if (blackPlaysFirst) {
10136             currentMove = forwardMostMove = backwardMostMove = 1;
10137             CopyBoard(boards[1], initial_position);
10138             strcpy(moveList[0], "");
10139             strcpy(parseList[0], "");
10140             timeRemaining[0][1] = whiteTimeRemaining;
10141             timeRemaining[1][1] = blackTimeRemaining;
10142             if (commentList[0] != NULL) {
10143               commentList[1] = commentList[0];
10144               commentList[0] = NULL;
10145             }
10146           } else {
10147             currentMove = forwardMostMove = backwardMostMove = 0;
10148           }
10149           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10150           {   int i;
10151               initialRulePlies = FENrulePlies;
10152               for( i=0; i< nrCastlingRights; i++ )
10153                   initialRights[i] = initial_position[CASTLING][i];
10154           }
10155           yyboardindex = forwardMostMove;
10156           free(gameInfo.fen);
10157           gameInfo.fen = NULL;
10158         }
10159
10160         yyboardindex = forwardMostMove;
10161         cm = (ChessMove) yylex();
10162
10163         /* Handle comments interspersed among the tags */
10164         while (cm == Comment) {
10165             char *p;
10166             if (appData.debugMode) 
10167               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10168             p = yy_text;
10169             AppendComment(currentMove, p, FALSE);
10170             yyboardindex = forwardMostMove;
10171             cm = (ChessMove) yylex();
10172         }
10173     }
10174
10175     /* don't rely on existence of Event tag since if game was
10176      * pasted from clipboard the Event tag may not exist
10177      */
10178     if (numPGNTags > 0){
10179         char *tags;
10180         if (gameInfo.variant == VariantNormal) {
10181           VariantClass v = StringToVariant(gameInfo.event);
10182           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10183           if(v < VariantShogi) gameInfo.variant = v;
10184         }
10185         if (!matchMode) {
10186           if( appData.autoDisplayTags ) {
10187             tags = PGNTags(&gameInfo);
10188             TagsPopUp(tags, CmailMsg());
10189             free(tags);
10190           }
10191         }
10192     } else {
10193         /* Make something up, but don't display it now */
10194         SetGameInfo();
10195         TagsPopDown();
10196     }
10197
10198     if (cm == PositionDiagram) {
10199         int i, j;
10200         char *p;
10201         Board initial_position;
10202
10203         if (appData.debugMode)
10204           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10205
10206         if (!startedFromSetupPosition) {
10207             p = yy_text;
10208             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10209               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10210                 switch (*p) {
10211                   case '[':
10212                   case '-':
10213                   case ' ':
10214                   case '\t':
10215                   case '\n':
10216                   case '\r':
10217                     break;
10218                   default:
10219                     initial_position[i][j++] = CharToPiece(*p);
10220                     break;
10221                 }
10222             while (*p == ' ' || *p == '\t' ||
10223                    *p == '\n' || *p == '\r') p++;
10224         
10225             if (strncmp(p, "black", strlen("black"))==0)
10226               blackPlaysFirst = TRUE;
10227             else
10228               blackPlaysFirst = FALSE;
10229             startedFromSetupPosition = TRUE;
10230         
10231             CopyBoard(boards[0], initial_position);
10232             if (blackPlaysFirst) {
10233                 currentMove = forwardMostMove = backwardMostMove = 1;
10234                 CopyBoard(boards[1], initial_position);
10235                 strcpy(moveList[0], "");
10236                 strcpy(parseList[0], "");
10237                 timeRemaining[0][1] = whiteTimeRemaining;
10238                 timeRemaining[1][1] = blackTimeRemaining;
10239                 if (commentList[0] != NULL) {
10240                     commentList[1] = commentList[0];
10241                     commentList[0] = NULL;
10242                 }
10243             } else {
10244                 currentMove = forwardMostMove = backwardMostMove = 0;
10245             }
10246         }
10247         yyboardindex = forwardMostMove;
10248         cm = (ChessMove) yylex();
10249     }
10250
10251     if (first.pr == NoProc) {
10252         StartChessProgram(&first);
10253     }
10254     InitChessProgram(&first, FALSE);
10255     SendToProgram("force\n", &first);
10256     if (startedFromSetupPosition) {
10257         SendBoard(&first, forwardMostMove);
10258     if (appData.debugMode) {
10259         fprintf(debugFP, "Load Game\n");
10260     }
10261         DisplayBothClocks();
10262     }      
10263
10264     /* [HGM] server: flag to write setup moves in broadcast file as one */
10265     loadFlag = appData.suppressLoadMoves;
10266
10267     while (cm == Comment) {
10268         char *p;
10269         if (appData.debugMode) 
10270           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10271         p = yy_text;
10272         AppendComment(currentMove, p, FALSE);
10273         yyboardindex = forwardMostMove;
10274         cm = (ChessMove) yylex();
10275     }
10276
10277     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10278         cm == WhiteWins || cm == BlackWins ||
10279         cm == GameIsDrawn || cm == GameUnfinished) {
10280         DisplayMessage("", _("No moves in game"));
10281         if (cmailMsgLoaded) {
10282             if (appData.debugMode)
10283               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10284             ClearHighlights();
10285             flipView = FALSE;
10286         }
10287         DrawPosition(FALSE, boards[currentMove]);
10288         DisplayBothClocks();
10289         gameMode = EditGame;
10290         ModeHighlight();
10291         gameFileFP = NULL;
10292         cmailOldMove = 0;
10293         return TRUE;
10294     }
10295
10296     // [HGM] PV info: routine tests if comment empty
10297     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10298         DisplayComment(currentMove - 1, commentList[currentMove]);
10299     }
10300     if (!matchMode && appData.timeDelay != 0) 
10301       DrawPosition(FALSE, boards[currentMove]);
10302
10303     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10304       programStats.ok_to_send = 1;
10305     }
10306
10307     /* if the first token after the PGN tags is a move
10308      * and not move number 1, retrieve it from the parser 
10309      */
10310     if (cm != MoveNumberOne)
10311         LoadGameOneMove(cm);
10312
10313     /* load the remaining moves from the file */
10314     while (LoadGameOneMove((ChessMove)0)) {
10315       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10316       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10317     }
10318
10319     /* rewind to the start of the game */
10320     currentMove = backwardMostMove;
10321
10322     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10323
10324     if (oldGameMode == AnalyzeFile ||
10325         oldGameMode == AnalyzeMode) {
10326       AnalyzeFileEvent();
10327     }
10328
10329     if (matchMode || appData.timeDelay == 0) {
10330       ToEndEvent();
10331       gameMode = EditGame;
10332       ModeHighlight();
10333     } else if (appData.timeDelay > 0) {
10334       AutoPlayGameLoop();
10335     }
10336
10337     if (appData.debugMode) 
10338         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10339
10340     loadFlag = 0; /* [HGM] true game starts */
10341     return TRUE;
10342 }
10343
10344 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10345 int
10346 ReloadPosition(offset)
10347      int offset;
10348 {
10349     int positionNumber = lastLoadPositionNumber + offset;
10350     if (lastLoadPositionFP == NULL) {
10351         DisplayError(_("No position has been loaded yet"), 0);
10352         return FALSE;
10353     }
10354     if (positionNumber <= 0) {
10355         DisplayError(_("Can't back up any further"), 0);
10356         return FALSE;
10357     }
10358     return LoadPosition(lastLoadPositionFP, positionNumber,
10359                         lastLoadPositionTitle);
10360 }
10361
10362 /* Load the nth position from the given file */
10363 int
10364 LoadPositionFromFile(filename, n, title)
10365      char *filename;
10366      int n;
10367      char *title;
10368 {
10369     FILE *f;
10370     char buf[MSG_SIZ];
10371
10372     if (strcmp(filename, "-") == 0) {
10373         return LoadPosition(stdin, n, "stdin");
10374     } else {
10375         f = fopen(filename, "rb");
10376         if (f == NULL) {
10377             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10378             DisplayError(buf, errno);
10379             return FALSE;
10380         } else {
10381             return LoadPosition(f, n, title);
10382         }
10383     }
10384 }
10385
10386 /* Load the nth position from the given open file, and close it */
10387 int
10388 LoadPosition(f, positionNumber, title)
10389      FILE *f;
10390      int positionNumber;
10391      char *title;
10392 {
10393     char *p, line[MSG_SIZ];
10394     Board initial_position;
10395     int i, j, fenMode, pn;
10396     
10397     if (gameMode == Training )
10398         SetTrainingModeOff();
10399
10400     if (gameMode != BeginningOfGame) {
10401         Reset(FALSE, TRUE);
10402     }
10403     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10404         fclose(lastLoadPositionFP);
10405     }
10406     if (positionNumber == 0) positionNumber = 1;
10407     lastLoadPositionFP = f;
10408     lastLoadPositionNumber = positionNumber;
10409     strcpy(lastLoadPositionTitle, title);
10410     if (first.pr == NoProc) {
10411       StartChessProgram(&first);
10412       InitChessProgram(&first, FALSE);
10413     }    
10414     pn = positionNumber;
10415     if (positionNumber < 0) {
10416         /* Negative position number means to seek to that byte offset */
10417         if (fseek(f, -positionNumber, 0) == -1) {
10418             DisplayError(_("Can't seek on position file"), 0);
10419             return FALSE;
10420         };
10421         pn = 1;
10422     } else {
10423         if (fseek(f, 0, 0) == -1) {
10424             if (f == lastLoadPositionFP ?
10425                 positionNumber == lastLoadPositionNumber + 1 :
10426                 positionNumber == 1) {
10427                 pn = 1;
10428             } else {
10429                 DisplayError(_("Can't seek on position file"), 0);
10430                 return FALSE;
10431             }
10432         }
10433     }
10434     /* See if this file is FEN or old-style xboard */
10435     if (fgets(line, MSG_SIZ, f) == NULL) {
10436         DisplayError(_("Position not found in file"), 0);
10437         return FALSE;
10438     }
10439     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10440     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10441
10442     if (pn >= 2) {
10443         if (fenMode || line[0] == '#') pn--;
10444         while (pn > 0) {
10445             /* skip positions before number pn */
10446             if (fgets(line, MSG_SIZ, f) == NULL) {
10447                 Reset(TRUE, TRUE);
10448                 DisplayError(_("Position not found in file"), 0);
10449                 return FALSE;
10450             }
10451             if (fenMode || line[0] == '#') pn--;
10452         }
10453     }
10454
10455     if (fenMode) {
10456         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10457             DisplayError(_("Bad FEN position in file"), 0);
10458             return FALSE;
10459         }
10460     } else {
10461         (void) fgets(line, MSG_SIZ, f);
10462         (void) fgets(line, MSG_SIZ, f);
10463     
10464         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10465             (void) fgets(line, MSG_SIZ, f);
10466             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10467                 if (*p == ' ')
10468                   continue;
10469                 initial_position[i][j++] = CharToPiece(*p);
10470             }
10471         }
10472     
10473         blackPlaysFirst = FALSE;
10474         if (!feof(f)) {
10475             (void) fgets(line, MSG_SIZ, f);
10476             if (strncmp(line, "black", strlen("black"))==0)
10477               blackPlaysFirst = TRUE;
10478         }
10479     }
10480     startedFromSetupPosition = TRUE;
10481     
10482     SendToProgram("force\n", &first);
10483     CopyBoard(boards[0], initial_position);
10484     if (blackPlaysFirst) {
10485         currentMove = forwardMostMove = backwardMostMove = 1;
10486         strcpy(moveList[0], "");
10487         strcpy(parseList[0], "");
10488         CopyBoard(boards[1], initial_position);
10489         DisplayMessage("", _("Black to play"));
10490     } else {
10491         currentMove = forwardMostMove = backwardMostMove = 0;
10492         DisplayMessage("", _("White to play"));
10493     }
10494     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10495     SendBoard(&first, forwardMostMove);
10496     if (appData.debugMode) {
10497 int i, j;
10498   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10499   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10500         fprintf(debugFP, "Load Position\n");
10501     }
10502
10503     if (positionNumber > 1) {
10504         sprintf(line, "%s %d", title, positionNumber);
10505         DisplayTitle(line);
10506     } else {
10507         DisplayTitle(title);
10508     }
10509     gameMode = EditGame;
10510     ModeHighlight();
10511     ResetClocks();
10512     timeRemaining[0][1] = whiteTimeRemaining;
10513     timeRemaining[1][1] = blackTimeRemaining;
10514     DrawPosition(FALSE, boards[currentMove]);
10515    
10516     return TRUE;
10517 }
10518
10519
10520 void
10521 CopyPlayerNameIntoFileName(dest, src)
10522      char **dest, *src;
10523 {
10524     while (*src != NULLCHAR && *src != ',') {
10525         if (*src == ' ') {
10526             *(*dest)++ = '_';
10527             src++;
10528         } else {
10529             *(*dest)++ = *src++;
10530         }
10531     }
10532 }
10533
10534 char *DefaultFileName(ext)
10535      char *ext;
10536 {
10537     static char def[MSG_SIZ];
10538     char *p;
10539
10540     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10541         p = def;
10542         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10543         *p++ = '-';
10544         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10545         *p++ = '.';
10546         strcpy(p, ext);
10547     } else {
10548         def[0] = NULLCHAR;
10549     }
10550     return def;
10551 }
10552
10553 /* Save the current game to the given file */
10554 int
10555 SaveGameToFile(filename, append)
10556      char *filename;
10557      int append;
10558 {
10559     FILE *f;
10560     char buf[MSG_SIZ];
10561
10562     if (strcmp(filename, "-") == 0) {
10563         return SaveGame(stdout, 0, NULL);
10564     } else {
10565         f = fopen(filename, append ? "a" : "w");
10566         if (f == NULL) {
10567             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10568             DisplayError(buf, errno);
10569             return FALSE;
10570         } else {
10571             return SaveGame(f, 0, NULL);
10572         }
10573     }
10574 }
10575
10576 char *
10577 SavePart(str)
10578      char *str;
10579 {
10580     static char buf[MSG_SIZ];
10581     char *p;
10582     
10583     p = strchr(str, ' ');
10584     if (p == NULL) return str;
10585     strncpy(buf, str, p - str);
10586     buf[p - str] = NULLCHAR;
10587     return buf;
10588 }
10589
10590 #define PGN_MAX_LINE 75
10591
10592 #define PGN_SIDE_WHITE  0
10593 #define PGN_SIDE_BLACK  1
10594
10595 /* [AS] */
10596 static int FindFirstMoveOutOfBook( int side )
10597 {
10598     int result = -1;
10599
10600     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10601         int index = backwardMostMove;
10602         int has_book_hit = 0;
10603
10604         if( (index % 2) != side ) {
10605             index++;
10606         }
10607
10608         while( index < forwardMostMove ) {
10609             /* Check to see if engine is in book */
10610             int depth = pvInfoList[index].depth;
10611             int score = pvInfoList[index].score;
10612             int in_book = 0;
10613
10614             if( depth <= 2 ) {
10615                 in_book = 1;
10616             }
10617             else if( score == 0 && depth == 63 ) {
10618                 in_book = 1; /* Zappa */
10619             }
10620             else if( score == 2 && depth == 99 ) {
10621                 in_book = 1; /* Abrok */
10622             }
10623
10624             has_book_hit += in_book;
10625
10626             if( ! in_book ) {
10627                 result = index;
10628
10629                 break;
10630             }
10631
10632             index += 2;
10633         }
10634     }
10635
10636     return result;
10637 }
10638
10639 /* [AS] */
10640 void GetOutOfBookInfo( char * buf )
10641 {
10642     int oob[2];
10643     int i;
10644     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10645
10646     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10647     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10648
10649     *buf = '\0';
10650
10651     if( oob[0] >= 0 || oob[1] >= 0 ) {
10652         for( i=0; i<2; i++ ) {
10653             int idx = oob[i];
10654
10655             if( idx >= 0 ) {
10656                 if( i > 0 && oob[0] >= 0 ) {
10657                     strcat( buf, "   " );
10658                 }
10659
10660                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10661                 sprintf( buf+strlen(buf), "%s%.2f", 
10662                     pvInfoList[idx].score >= 0 ? "+" : "",
10663                     pvInfoList[idx].score / 100.0 );
10664             }
10665         }
10666     }
10667 }
10668
10669 /* Save game in PGN style and close the file */
10670 int
10671 SaveGamePGN(f)
10672      FILE *f;
10673 {
10674     int i, offset, linelen, newblock;
10675     time_t tm;
10676 //    char *movetext;
10677     char numtext[32];
10678     int movelen, numlen, blank;
10679     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10680
10681     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10682     
10683     tm = time((time_t *) NULL);
10684     
10685     PrintPGNTags(f, &gameInfo);
10686     
10687     if (backwardMostMove > 0 || startedFromSetupPosition) {
10688         char *fen = PositionToFEN(backwardMostMove, NULL);
10689         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10690         fprintf(f, "\n{--------------\n");
10691         PrintPosition(f, backwardMostMove);
10692         fprintf(f, "--------------}\n");
10693         free(fen);
10694     }
10695     else {
10696         /* [AS] Out of book annotation */
10697         if( appData.saveOutOfBookInfo ) {
10698             char buf[64];
10699
10700             GetOutOfBookInfo( buf );
10701
10702             if( buf[0] != '\0' ) {
10703                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10704             }
10705         }
10706
10707         fprintf(f, "\n");
10708     }
10709
10710     i = backwardMostMove;
10711     linelen = 0;
10712     newblock = TRUE;
10713
10714     while (i < forwardMostMove) {
10715         /* Print comments preceding this move */
10716         if (commentList[i] != NULL) {
10717             if (linelen > 0) fprintf(f, "\n");
10718             fprintf(f, "%s", commentList[i]);
10719             linelen = 0;
10720             newblock = TRUE;
10721         }
10722
10723         /* Format move number */
10724         if ((i % 2) == 0) {
10725             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10726         } else {
10727             if (newblock) {
10728                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10729             } else {
10730                 numtext[0] = NULLCHAR;
10731             }
10732         }
10733         numlen = strlen(numtext);
10734         newblock = FALSE;
10735
10736         /* Print move number */
10737         blank = linelen > 0 && numlen > 0;
10738         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10739             fprintf(f, "\n");
10740             linelen = 0;
10741             blank = 0;
10742         }
10743         if (blank) {
10744             fprintf(f, " ");
10745             linelen++;
10746         }
10747         fprintf(f, "%s", numtext);
10748         linelen += numlen;
10749
10750         /* Get move */
10751         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10752         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10753
10754         /* Print move */
10755         blank = linelen > 0 && movelen > 0;
10756         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10757             fprintf(f, "\n");
10758             linelen = 0;
10759             blank = 0;
10760         }
10761         if (blank) {
10762             fprintf(f, " ");
10763             linelen++;
10764         }
10765         fprintf(f, "%s", move_buffer);
10766         linelen += movelen;
10767
10768         /* [AS] Add PV info if present */
10769         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10770             /* [HGM] add time */
10771             char buf[MSG_SIZ]; int seconds;
10772
10773             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10774
10775             if( seconds <= 0) buf[0] = 0; else
10776             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10777                 seconds = (seconds + 4)/10; // round to full seconds
10778                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10779                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10780             }
10781
10782             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10783                 pvInfoList[i].score >= 0 ? "+" : "",
10784                 pvInfoList[i].score / 100.0,
10785                 pvInfoList[i].depth,
10786                 buf );
10787
10788             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10789
10790             /* Print score/depth */
10791             blank = linelen > 0 && movelen > 0;
10792             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10793                 fprintf(f, "\n");
10794                 linelen = 0;
10795                 blank = 0;
10796             }
10797             if (blank) {
10798                 fprintf(f, " ");
10799                 linelen++;
10800             }
10801             fprintf(f, "%s", move_buffer);
10802             linelen += movelen;
10803         }
10804
10805         i++;
10806     }
10807     
10808     /* Start a new line */
10809     if (linelen > 0) fprintf(f, "\n");
10810
10811     /* Print comments after last move */
10812     if (commentList[i] != NULL) {
10813         fprintf(f, "%s\n", commentList[i]);
10814     }
10815
10816     /* Print result */
10817     if (gameInfo.resultDetails != NULL &&
10818         gameInfo.resultDetails[0] != NULLCHAR) {
10819         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10820                 PGNResult(gameInfo.result));
10821     } else {
10822         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10823     }
10824
10825     fclose(f);
10826     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10827     return TRUE;
10828 }
10829
10830 /* Save game in old style and close the file */
10831 int
10832 SaveGameOldStyle(f)
10833      FILE *f;
10834 {
10835     int i, offset;
10836     time_t tm;
10837     
10838     tm = time((time_t *) NULL);
10839     
10840     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10841     PrintOpponents(f);
10842     
10843     if (backwardMostMove > 0 || startedFromSetupPosition) {
10844         fprintf(f, "\n[--------------\n");
10845         PrintPosition(f, backwardMostMove);
10846         fprintf(f, "--------------]\n");
10847     } else {
10848         fprintf(f, "\n");
10849     }
10850
10851     i = backwardMostMove;
10852     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10853
10854     while (i < forwardMostMove) {
10855         if (commentList[i] != NULL) {
10856             fprintf(f, "[%s]\n", commentList[i]);
10857         }
10858
10859         if ((i % 2) == 1) {
10860             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10861             i++;
10862         } else {
10863             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10864             i++;
10865             if (commentList[i] != NULL) {
10866                 fprintf(f, "\n");
10867                 continue;
10868             }
10869             if (i >= forwardMostMove) {
10870                 fprintf(f, "\n");
10871                 break;
10872             }
10873             fprintf(f, "%s\n", parseList[i]);
10874             i++;
10875         }
10876     }
10877     
10878     if (commentList[i] != NULL) {
10879         fprintf(f, "[%s]\n", commentList[i]);
10880     }
10881
10882     /* This isn't really the old style, but it's close enough */
10883     if (gameInfo.resultDetails != NULL &&
10884         gameInfo.resultDetails[0] != NULLCHAR) {
10885         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10886                 gameInfo.resultDetails);
10887     } else {
10888         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10889     }
10890
10891     fclose(f);
10892     return TRUE;
10893 }
10894
10895 /* Save the current game to open file f and close the file */
10896 int
10897 SaveGame(f, dummy, dummy2)
10898      FILE *f;
10899      int dummy;
10900      char *dummy2;
10901 {
10902     if (gameMode == EditPosition) EditPositionDone(TRUE);
10903     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10904     if (appData.oldSaveStyle)
10905       return SaveGameOldStyle(f);
10906     else
10907       return SaveGamePGN(f);
10908 }
10909
10910 /* Save the current position to the given file */
10911 int
10912 SavePositionToFile(filename)
10913      char *filename;
10914 {
10915     FILE *f;
10916     char buf[MSG_SIZ];
10917
10918     if (strcmp(filename, "-") == 0) {
10919         return SavePosition(stdout, 0, NULL);
10920     } else {
10921         f = fopen(filename, "a");
10922         if (f == NULL) {
10923             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10924             DisplayError(buf, errno);
10925             return FALSE;
10926         } else {
10927             SavePosition(f, 0, NULL);
10928             return TRUE;
10929         }
10930     }
10931 }
10932
10933 /* Save the current position to the given open file and close the file */
10934 int
10935 SavePosition(f, dummy, dummy2)
10936      FILE *f;
10937      int dummy;
10938      char *dummy2;
10939 {
10940     time_t tm;
10941     char *fen;
10942     
10943     if (gameMode == EditPosition) EditPositionDone(TRUE);
10944     if (appData.oldSaveStyle) {
10945         tm = time((time_t *) NULL);
10946     
10947         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10948         PrintOpponents(f);
10949         fprintf(f, "[--------------\n");
10950         PrintPosition(f, currentMove);
10951         fprintf(f, "--------------]\n");
10952     } else {
10953         fen = PositionToFEN(currentMove, NULL);
10954         fprintf(f, "%s\n", fen);
10955         free(fen);
10956     }
10957     fclose(f);
10958     return TRUE;
10959 }
10960
10961 void
10962 ReloadCmailMsgEvent(unregister)
10963      int unregister;
10964 {
10965 #if !WIN32
10966     static char *inFilename = NULL;
10967     static char *outFilename;
10968     int i;
10969     struct stat inbuf, outbuf;
10970     int status;
10971     
10972     /* Any registered moves are unregistered if unregister is set, */
10973     /* i.e. invoked by the signal handler */
10974     if (unregister) {
10975         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10976             cmailMoveRegistered[i] = FALSE;
10977             if (cmailCommentList[i] != NULL) {
10978                 free(cmailCommentList[i]);
10979                 cmailCommentList[i] = NULL;
10980             }
10981         }
10982         nCmailMovesRegistered = 0;
10983     }
10984
10985     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10986         cmailResult[i] = CMAIL_NOT_RESULT;
10987     }
10988     nCmailResults = 0;
10989
10990     if (inFilename == NULL) {
10991         /* Because the filenames are static they only get malloced once  */
10992         /* and they never get freed                                      */
10993         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10994         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10995
10996         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10997         sprintf(outFilename, "%s.out", appData.cmailGameName);
10998     }
10999     
11000     status = stat(outFilename, &outbuf);
11001     if (status < 0) {
11002         cmailMailedMove = FALSE;
11003     } else {
11004         status = stat(inFilename, &inbuf);
11005         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11006     }
11007     
11008     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11009        counts the games, notes how each one terminated, etc.
11010        
11011        It would be nice to remove this kludge and instead gather all
11012        the information while building the game list.  (And to keep it
11013        in the game list nodes instead of having a bunch of fixed-size
11014        parallel arrays.)  Note this will require getting each game's
11015        termination from the PGN tags, as the game list builder does
11016        not process the game moves.  --mann
11017        */
11018     cmailMsgLoaded = TRUE;
11019     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11020     
11021     /* Load first game in the file or popup game menu */
11022     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11023
11024 #endif /* !WIN32 */
11025     return;
11026 }
11027
11028 int
11029 RegisterMove()
11030 {
11031     FILE *f;
11032     char string[MSG_SIZ];
11033
11034     if (   cmailMailedMove
11035         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11036         return TRUE;            /* Allow free viewing  */
11037     }
11038
11039     /* Unregister move to ensure that we don't leave RegisterMove        */
11040     /* with the move registered when the conditions for registering no   */
11041     /* longer hold                                                       */
11042     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11043         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11044         nCmailMovesRegistered --;
11045
11046         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11047           {
11048               free(cmailCommentList[lastLoadGameNumber - 1]);
11049               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11050           }
11051     }
11052
11053     if (cmailOldMove == -1) {
11054         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11055         return FALSE;
11056     }
11057
11058     if (currentMove > cmailOldMove + 1) {
11059         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11060         return FALSE;
11061     }
11062
11063     if (currentMove < cmailOldMove) {
11064         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11065         return FALSE;
11066     }
11067
11068     if (forwardMostMove > currentMove) {
11069         /* Silently truncate extra moves */
11070         TruncateGame();
11071     }
11072
11073     if (   (currentMove == cmailOldMove + 1)
11074         || (   (currentMove == cmailOldMove)
11075             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11076                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11077         if (gameInfo.result != GameUnfinished) {
11078             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11079         }
11080
11081         if (commentList[currentMove] != NULL) {
11082             cmailCommentList[lastLoadGameNumber - 1]
11083               = StrSave(commentList[currentMove]);
11084         }
11085         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11086
11087         if (appData.debugMode)
11088           fprintf(debugFP, "Saving %s for game %d\n",
11089                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11090
11091         sprintf(string,
11092                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11093         
11094         f = fopen(string, "w");
11095         if (appData.oldSaveStyle) {
11096             SaveGameOldStyle(f); /* also closes the file */
11097             
11098             sprintf(string, "%s.pos.out", appData.cmailGameName);
11099             f = fopen(string, "w");
11100             SavePosition(f, 0, NULL); /* also closes the file */
11101         } else {
11102             fprintf(f, "{--------------\n");
11103             PrintPosition(f, currentMove);
11104             fprintf(f, "--------------}\n\n");
11105             
11106             SaveGame(f, 0, NULL); /* also closes the file*/
11107         }
11108         
11109         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11110         nCmailMovesRegistered ++;
11111     } else if (nCmailGames == 1) {
11112         DisplayError(_("You have not made a move yet"), 0);
11113         return FALSE;
11114     }
11115
11116     return TRUE;
11117 }
11118
11119 void
11120 MailMoveEvent()
11121 {
11122 #if !WIN32
11123     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11124     FILE *commandOutput;
11125     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11126     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11127     int nBuffers;
11128     int i;
11129     int archived;
11130     char *arcDir;
11131
11132     if (! cmailMsgLoaded) {
11133         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11134         return;
11135     }
11136
11137     if (nCmailGames == nCmailResults) {
11138         DisplayError(_("No unfinished games"), 0);
11139         return;
11140     }
11141
11142 #if CMAIL_PROHIBIT_REMAIL
11143     if (cmailMailedMove) {
11144         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);
11145         DisplayError(msg, 0);
11146         return;
11147     }
11148 #endif
11149
11150     if (! (cmailMailedMove || RegisterMove())) return;
11151     
11152     if (   cmailMailedMove
11153         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11154         sprintf(string, partCommandString,
11155                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11156         commandOutput = popen(string, "r");
11157
11158         if (commandOutput == NULL) {
11159             DisplayError(_("Failed to invoke cmail"), 0);
11160         } else {
11161             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11162                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11163             }
11164             if (nBuffers > 1) {
11165                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11166                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11167                 nBytes = MSG_SIZ - 1;
11168             } else {
11169                 (void) memcpy(msg, buffer, nBytes);
11170             }
11171             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11172
11173             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11174                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11175
11176                 archived = TRUE;
11177                 for (i = 0; i < nCmailGames; i ++) {
11178                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11179                         archived = FALSE;
11180                     }
11181                 }
11182                 if (   archived
11183                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11184                         != NULL)) {
11185                     sprintf(buffer, "%s/%s.%s.archive",
11186                             arcDir,
11187                             appData.cmailGameName,
11188                             gameInfo.date);
11189                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11190                     cmailMsgLoaded = FALSE;
11191                 }
11192             }
11193
11194             DisplayInformation(msg);
11195             pclose(commandOutput);
11196         }
11197     } else {
11198         if ((*cmailMsg) != '\0') {
11199             DisplayInformation(cmailMsg);
11200         }
11201     }
11202
11203     return;
11204 #endif /* !WIN32 */
11205 }
11206
11207 char *
11208 CmailMsg()
11209 {
11210 #if WIN32
11211     return NULL;
11212 #else
11213     int  prependComma = 0;
11214     char number[5];
11215     char string[MSG_SIZ];       /* Space for game-list */
11216     int  i;
11217     
11218     if (!cmailMsgLoaded) return "";
11219
11220     if (cmailMailedMove) {
11221         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11222     } else {
11223         /* Create a list of games left */
11224         sprintf(string, "[");
11225         for (i = 0; i < nCmailGames; i ++) {
11226             if (! (   cmailMoveRegistered[i]
11227                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11228                 if (prependComma) {
11229                     sprintf(number, ",%d", i + 1);
11230                 } else {
11231                     sprintf(number, "%d", i + 1);
11232                     prependComma = 1;
11233                 }
11234                 
11235                 strcat(string, number);
11236             }
11237         }
11238         strcat(string, "]");
11239
11240         if (nCmailMovesRegistered + nCmailResults == 0) {
11241             switch (nCmailGames) {
11242               case 1:
11243                 sprintf(cmailMsg,
11244                         _("Still need to make move for game\n"));
11245                 break;
11246                 
11247               case 2:
11248                 sprintf(cmailMsg,
11249                         _("Still need to make moves for both games\n"));
11250                 break;
11251                 
11252               default:
11253                 sprintf(cmailMsg,
11254                         _("Still need to make moves for all %d games\n"),
11255                         nCmailGames);
11256                 break;
11257             }
11258         } else {
11259             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11260               case 1:
11261                 sprintf(cmailMsg,
11262                         _("Still need to make a move for game %s\n"),
11263                         string);
11264                 break;
11265                 
11266               case 0:
11267                 if (nCmailResults == nCmailGames) {
11268                     sprintf(cmailMsg, _("No unfinished games\n"));
11269                 } else {
11270                     sprintf(cmailMsg, _("Ready to send mail\n"));
11271                 }
11272                 break;
11273                 
11274               default:
11275                 sprintf(cmailMsg,
11276                         _("Still need to make moves for games %s\n"),
11277                         string);
11278             }
11279         }
11280     }
11281     return cmailMsg;
11282 #endif /* WIN32 */
11283 }
11284
11285 void
11286 ResetGameEvent()
11287 {
11288     if (gameMode == Training)
11289       SetTrainingModeOff();
11290
11291     Reset(TRUE, TRUE);
11292     cmailMsgLoaded = FALSE;
11293     if (appData.icsActive) {
11294       SendToICS(ics_prefix);
11295       SendToICS("refresh\n");
11296     }
11297 }
11298
11299 void
11300 ExitEvent(status)
11301      int status;
11302 {
11303     exiting++;
11304     if (exiting > 2) {
11305       /* Give up on clean exit */
11306       exit(status);
11307     }
11308     if (exiting > 1) {
11309       /* Keep trying for clean exit */
11310       return;
11311     }
11312
11313     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11314
11315     if (telnetISR != NULL) {
11316       RemoveInputSource(telnetISR);
11317     }
11318     if (icsPR != NoProc) {
11319       DestroyChildProcess(icsPR, TRUE);
11320     }
11321
11322     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11323     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11324
11325     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11326     /* make sure this other one finishes before killing it!                  */
11327     if(endingGame) { int count = 0;
11328         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11329         while(endingGame && count++ < 10) DoSleep(1);
11330         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11331     }
11332
11333     /* Kill off chess programs */
11334     if (first.pr != NoProc) {
11335         ExitAnalyzeMode();
11336         
11337         DoSleep( appData.delayBeforeQuit );
11338         SendToProgram("quit\n", &first);
11339         DoSleep( appData.delayAfterQuit );
11340         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11341     }
11342     if (second.pr != NoProc) {
11343         DoSleep( appData.delayBeforeQuit );
11344         SendToProgram("quit\n", &second);
11345         DoSleep( appData.delayAfterQuit );
11346         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11347     }
11348     if (first.isr != NULL) {
11349         RemoveInputSource(first.isr);
11350     }
11351     if (second.isr != NULL) {
11352         RemoveInputSource(second.isr);
11353     }
11354
11355     ShutDownFrontEnd();
11356     exit(status);
11357 }
11358
11359 void
11360 PauseEvent()
11361 {
11362     if (appData.debugMode)
11363         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11364     if (pausing) {
11365         pausing = FALSE;
11366         ModeHighlight();
11367         if (gameMode == MachinePlaysWhite ||
11368             gameMode == MachinePlaysBlack) {
11369             StartClocks();
11370         } else {
11371             DisplayBothClocks();
11372         }
11373         if (gameMode == PlayFromGameFile) {
11374             if (appData.timeDelay >= 0) 
11375                 AutoPlayGameLoop();
11376         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11377             Reset(FALSE, TRUE);
11378             SendToICS(ics_prefix);
11379             SendToICS("refresh\n");
11380         } else if (currentMove < forwardMostMove) {
11381             ForwardInner(forwardMostMove);
11382         }
11383         pauseExamInvalid = FALSE;
11384     } else {
11385         switch (gameMode) {
11386           default:
11387             return;
11388           case IcsExamining:
11389             pauseExamForwardMostMove = forwardMostMove;
11390             pauseExamInvalid = FALSE;
11391             /* fall through */
11392           case IcsObserving:
11393           case IcsPlayingWhite:
11394           case IcsPlayingBlack:
11395             pausing = TRUE;
11396             ModeHighlight();
11397             return;
11398           case PlayFromGameFile:
11399             (void) StopLoadGameTimer();
11400             pausing = TRUE;
11401             ModeHighlight();
11402             break;
11403           case BeginningOfGame:
11404             if (appData.icsActive) return;
11405             /* else fall through */
11406           case MachinePlaysWhite:
11407           case MachinePlaysBlack:
11408           case TwoMachinesPlay:
11409             if (forwardMostMove == 0)
11410               return;           /* don't pause if no one has moved */
11411             if ((gameMode == MachinePlaysWhite &&
11412                  !WhiteOnMove(forwardMostMove)) ||
11413                 (gameMode == MachinePlaysBlack &&
11414                  WhiteOnMove(forwardMostMove))) {
11415                 StopClocks();
11416             }
11417             pausing = TRUE;
11418             ModeHighlight();
11419             break;
11420         }
11421     }
11422 }
11423
11424 void
11425 EditCommentEvent()
11426 {
11427     char title[MSG_SIZ];
11428
11429     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11430         strcpy(title, _("Edit comment"));
11431     } else {
11432         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11433                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11434                 parseList[currentMove - 1]);
11435     }
11436
11437     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11438 }
11439
11440
11441 void
11442 EditTagsEvent()
11443 {
11444     char *tags = PGNTags(&gameInfo);
11445     EditTagsPopUp(tags);
11446     free(tags);
11447 }
11448
11449 void
11450 AnalyzeModeEvent()
11451 {
11452     if (appData.noChessProgram || gameMode == AnalyzeMode)
11453       return;
11454
11455     if (gameMode != AnalyzeFile) {
11456         if (!appData.icsEngineAnalyze) {
11457                EditGameEvent();
11458                if (gameMode != EditGame) return;
11459         }
11460         ResurrectChessProgram();
11461         SendToProgram("analyze\n", &first);
11462         first.analyzing = TRUE;
11463         /*first.maybeThinking = TRUE;*/
11464         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11465         EngineOutputPopUp();
11466     }
11467     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11468     pausing = FALSE;
11469     ModeHighlight();
11470     SetGameInfo();
11471
11472     StartAnalysisClock();
11473     GetTimeMark(&lastNodeCountTime);
11474     lastNodeCount = 0;
11475 }
11476
11477 void
11478 AnalyzeFileEvent()
11479 {
11480     if (appData.noChessProgram || gameMode == AnalyzeFile)
11481       return;
11482
11483     if (gameMode != AnalyzeMode) {
11484         EditGameEvent();
11485         if (gameMode != EditGame) return;
11486         ResurrectChessProgram();
11487         SendToProgram("analyze\n", &first);
11488         first.analyzing = TRUE;
11489         /*first.maybeThinking = TRUE;*/
11490         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11491         EngineOutputPopUp();
11492     }
11493     gameMode = AnalyzeFile;
11494     pausing = FALSE;
11495     ModeHighlight();
11496     SetGameInfo();
11497
11498     StartAnalysisClock();
11499     GetTimeMark(&lastNodeCountTime);
11500     lastNodeCount = 0;
11501 }
11502
11503 void
11504 MachineWhiteEvent()
11505 {
11506     char buf[MSG_SIZ];
11507     char *bookHit = NULL;
11508
11509     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11510       return;
11511
11512
11513     if (gameMode == PlayFromGameFile || 
11514         gameMode == TwoMachinesPlay  || 
11515         gameMode == Training         || 
11516         gameMode == AnalyzeMode      || 
11517         gameMode == EndOfGame)
11518         EditGameEvent();
11519
11520     if (gameMode == EditPosition) 
11521         EditPositionDone(TRUE);
11522
11523     if (!WhiteOnMove(currentMove)) {
11524         DisplayError(_("It is not White's turn"), 0);
11525         return;
11526     }
11527   
11528     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11529       ExitAnalyzeMode();
11530
11531     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11532         gameMode == AnalyzeFile)
11533         TruncateGame();
11534
11535     ResurrectChessProgram();    /* in case it isn't running */
11536     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11537         gameMode = MachinePlaysWhite;
11538         ResetClocks();
11539     } else
11540     gameMode = MachinePlaysWhite;
11541     pausing = FALSE;
11542     ModeHighlight();
11543     SetGameInfo();
11544     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11545     DisplayTitle(buf);
11546     if (first.sendName) {
11547       sprintf(buf, "name %s\n", gameInfo.black);
11548       SendToProgram(buf, &first);
11549     }
11550     if (first.sendTime) {
11551       if (first.useColors) {
11552         SendToProgram("black\n", &first); /*gnu kludge*/
11553       }
11554       SendTimeRemaining(&first, TRUE);
11555     }
11556     if (first.useColors) {
11557       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11558     }
11559     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11560     SetMachineThinkingEnables();
11561     first.maybeThinking = TRUE;
11562     StartClocks();
11563     firstMove = FALSE;
11564
11565     if (appData.autoFlipView && !flipView) {
11566       flipView = !flipView;
11567       DrawPosition(FALSE, NULL);
11568       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11569     }
11570
11571     if(bookHit) { // [HGM] book: simulate book reply
11572         static char bookMove[MSG_SIZ]; // a bit generous?
11573
11574         programStats.nodes = programStats.depth = programStats.time = 
11575         programStats.score = programStats.got_only_move = 0;
11576         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11577
11578         strcpy(bookMove, "move ");
11579         strcat(bookMove, bookHit);
11580         HandleMachineMove(bookMove, &first);
11581     }
11582 }
11583
11584 void
11585 MachineBlackEvent()
11586 {
11587     char buf[MSG_SIZ];
11588    char *bookHit = NULL;
11589
11590     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11591         return;
11592
11593
11594     if (gameMode == PlayFromGameFile || 
11595         gameMode == TwoMachinesPlay  || 
11596         gameMode == Training         || 
11597         gameMode == AnalyzeMode      || 
11598         gameMode == EndOfGame)
11599         EditGameEvent();
11600
11601     if (gameMode == EditPosition) 
11602         EditPositionDone(TRUE);
11603
11604     if (WhiteOnMove(currentMove)) {
11605         DisplayError(_("It is not Black's turn"), 0);
11606         return;
11607     }
11608     
11609     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11610       ExitAnalyzeMode();
11611
11612     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11613         gameMode == AnalyzeFile)
11614         TruncateGame();
11615
11616     ResurrectChessProgram();    /* in case it isn't running */
11617     gameMode = MachinePlaysBlack;
11618     pausing = FALSE;
11619     ModeHighlight();
11620     SetGameInfo();
11621     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11622     DisplayTitle(buf);
11623     if (first.sendName) {
11624       sprintf(buf, "name %s\n", gameInfo.white);
11625       SendToProgram(buf, &first);
11626     }
11627     if (first.sendTime) {
11628       if (first.useColors) {
11629         SendToProgram("white\n", &first); /*gnu kludge*/
11630       }
11631       SendTimeRemaining(&first, FALSE);
11632     }
11633     if (first.useColors) {
11634       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11635     }
11636     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11637     SetMachineThinkingEnables();
11638     first.maybeThinking = TRUE;
11639     StartClocks();
11640
11641     if (appData.autoFlipView && flipView) {
11642       flipView = !flipView;
11643       DrawPosition(FALSE, NULL);
11644       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11645     }
11646     if(bookHit) { // [HGM] book: simulate book reply
11647         static char bookMove[MSG_SIZ]; // a bit generous?
11648
11649         programStats.nodes = programStats.depth = programStats.time = 
11650         programStats.score = programStats.got_only_move = 0;
11651         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11652
11653         strcpy(bookMove, "move ");
11654         strcat(bookMove, bookHit);
11655         HandleMachineMove(bookMove, &first);
11656     }
11657 }
11658
11659
11660 void
11661 DisplayTwoMachinesTitle()
11662 {
11663     char buf[MSG_SIZ];
11664     if (appData.matchGames > 0) {
11665         if (first.twoMachinesColor[0] == 'w') {
11666             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11667                     gameInfo.white, gameInfo.black,
11668                     first.matchWins, second.matchWins,
11669                     matchGame - 1 - (first.matchWins + second.matchWins));
11670         } else {
11671             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11672                     gameInfo.white, gameInfo.black,
11673                     second.matchWins, first.matchWins,
11674                     matchGame - 1 - (first.matchWins + second.matchWins));
11675         }
11676     } else {
11677         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11678     }
11679     DisplayTitle(buf);
11680 }
11681
11682 void
11683 TwoMachinesEvent P((void))
11684 {
11685     int i;
11686     char buf[MSG_SIZ];
11687     ChessProgramState *onmove;
11688     char *bookHit = NULL;
11689     
11690     if (appData.noChessProgram) return;
11691
11692     switch (gameMode) {
11693       case TwoMachinesPlay:
11694         return;
11695       case MachinePlaysWhite:
11696       case MachinePlaysBlack:
11697         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11698             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11699             return;
11700         }
11701         /* fall through */
11702       case BeginningOfGame:
11703       case PlayFromGameFile:
11704       case EndOfGame:
11705         EditGameEvent();
11706         if (gameMode != EditGame) return;
11707         break;
11708       case EditPosition:
11709         EditPositionDone(TRUE);
11710         break;
11711       case AnalyzeMode:
11712       case AnalyzeFile:
11713         ExitAnalyzeMode();
11714         break;
11715       case EditGame:
11716       default:
11717         break;
11718     }
11719
11720 //    forwardMostMove = currentMove;
11721     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11722     ResurrectChessProgram();    /* in case first program isn't running */
11723
11724     if (second.pr == NULL) {
11725         StartChessProgram(&second);
11726         if (second.protocolVersion == 1) {
11727           TwoMachinesEventIfReady();
11728         } else {
11729           /* kludge: allow timeout for initial "feature" command */
11730           FreezeUI();
11731           DisplayMessage("", _("Starting second chess program"));
11732           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11733         }
11734         return;
11735     }
11736     DisplayMessage("", "");
11737     InitChessProgram(&second, FALSE);
11738     SendToProgram("force\n", &second);
11739     if (startedFromSetupPosition) {
11740         SendBoard(&second, backwardMostMove);
11741     if (appData.debugMode) {
11742         fprintf(debugFP, "Two Machines\n");
11743     }
11744     }
11745     for (i = backwardMostMove; i < forwardMostMove; i++) {
11746         SendMoveToProgram(i, &second);
11747     }
11748
11749     gameMode = TwoMachinesPlay;
11750     pausing = FALSE;
11751     ModeHighlight();
11752     SetGameInfo();
11753     DisplayTwoMachinesTitle();
11754     firstMove = TRUE;
11755     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11756         onmove = &first;
11757     } else {
11758         onmove = &second;
11759     }
11760
11761     SendToProgram(first.computerString, &first);
11762     if (first.sendName) {
11763       sprintf(buf, "name %s\n", second.tidy);
11764       SendToProgram(buf, &first);
11765     }
11766     SendToProgram(second.computerString, &second);
11767     if (second.sendName) {
11768       sprintf(buf, "name %s\n", first.tidy);
11769       SendToProgram(buf, &second);
11770     }
11771
11772     ResetClocks();
11773     if (!first.sendTime || !second.sendTime) {
11774         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11775         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11776     }
11777     if (onmove->sendTime) {
11778       if (onmove->useColors) {
11779         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11780       }
11781       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11782     }
11783     if (onmove->useColors) {
11784       SendToProgram(onmove->twoMachinesColor, onmove);
11785     }
11786     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11787 //    SendToProgram("go\n", onmove);
11788     onmove->maybeThinking = TRUE;
11789     SetMachineThinkingEnables();
11790
11791     StartClocks();
11792
11793     if(bookHit) { // [HGM] book: simulate book reply
11794         static char bookMove[MSG_SIZ]; // a bit generous?
11795
11796         programStats.nodes = programStats.depth = programStats.time = 
11797         programStats.score = programStats.got_only_move = 0;
11798         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11799
11800         strcpy(bookMove, "move ");
11801         strcat(bookMove, bookHit);
11802         savedMessage = bookMove; // args for deferred call
11803         savedState = onmove;
11804         ScheduleDelayedEvent(DeferredBookMove, 1);
11805     }
11806 }
11807
11808 void
11809 TrainingEvent()
11810 {
11811     if (gameMode == Training) {
11812       SetTrainingModeOff();
11813       gameMode = PlayFromGameFile;
11814       DisplayMessage("", _("Training mode off"));
11815     } else {
11816       gameMode = Training;
11817       animateTraining = appData.animate;
11818
11819       /* make sure we are not already at the end of the game */
11820       if (currentMove < forwardMostMove) {
11821         SetTrainingModeOn();
11822         DisplayMessage("", _("Training mode on"));
11823       } else {
11824         gameMode = PlayFromGameFile;
11825         DisplayError(_("Already at end of game"), 0);
11826       }
11827     }
11828     ModeHighlight();
11829 }
11830
11831 void
11832 IcsClientEvent()
11833 {
11834     if (!appData.icsActive) return;
11835     switch (gameMode) {
11836       case IcsPlayingWhite:
11837       case IcsPlayingBlack:
11838       case IcsObserving:
11839       case IcsIdle:
11840       case BeginningOfGame:
11841       case IcsExamining:
11842         return;
11843
11844       case EditGame:
11845         break;
11846
11847       case EditPosition:
11848         EditPositionDone(TRUE);
11849         break;
11850
11851       case AnalyzeMode:
11852       case AnalyzeFile:
11853         ExitAnalyzeMode();
11854         break;
11855         
11856       default:
11857         EditGameEvent();
11858         break;
11859     }
11860
11861     gameMode = IcsIdle;
11862     ModeHighlight();
11863     return;
11864 }
11865
11866
11867 void
11868 EditGameEvent()
11869 {
11870     int i;
11871
11872     switch (gameMode) {
11873       case Training:
11874         SetTrainingModeOff();
11875         break;
11876       case MachinePlaysWhite:
11877       case MachinePlaysBlack:
11878       case BeginningOfGame:
11879         SendToProgram("force\n", &first);
11880         SetUserThinkingEnables();
11881         break;
11882       case PlayFromGameFile:
11883         (void) StopLoadGameTimer();
11884         if (gameFileFP != NULL) {
11885             gameFileFP = NULL;
11886         }
11887         break;
11888       case EditPosition:
11889         EditPositionDone(TRUE);
11890         break;
11891       case AnalyzeMode:
11892       case AnalyzeFile:
11893         ExitAnalyzeMode();
11894         SendToProgram("force\n", &first);
11895         break;
11896       case TwoMachinesPlay:
11897         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11898         ResurrectChessProgram();
11899         SetUserThinkingEnables();
11900         break;
11901       case EndOfGame:
11902         ResurrectChessProgram();
11903         break;
11904       case IcsPlayingBlack:
11905       case IcsPlayingWhite:
11906         DisplayError(_("Warning: You are still playing a game"), 0);
11907         break;
11908       case IcsObserving:
11909         DisplayError(_("Warning: You are still observing a game"), 0);
11910         break;
11911       case IcsExamining:
11912         DisplayError(_("Warning: You are still examining a game"), 0);
11913         break;
11914       case IcsIdle:
11915         break;
11916       case EditGame:
11917       default:
11918         return;
11919     }
11920     
11921     pausing = FALSE;
11922     StopClocks();
11923     first.offeredDraw = second.offeredDraw = 0;
11924
11925     if (gameMode == PlayFromGameFile) {
11926         whiteTimeRemaining = timeRemaining[0][currentMove];
11927         blackTimeRemaining = timeRemaining[1][currentMove];
11928         DisplayTitle("");
11929     }
11930
11931     if (gameMode == MachinePlaysWhite ||
11932         gameMode == MachinePlaysBlack ||
11933         gameMode == TwoMachinesPlay ||
11934         gameMode == EndOfGame) {
11935         i = forwardMostMove;
11936         while (i > currentMove) {
11937             SendToProgram("undo\n", &first);
11938             i--;
11939         }
11940         whiteTimeRemaining = timeRemaining[0][currentMove];
11941         blackTimeRemaining = timeRemaining[1][currentMove];
11942         DisplayBothClocks();
11943         if (whiteFlag || blackFlag) {
11944             whiteFlag = blackFlag = 0;
11945         }
11946         DisplayTitle("");
11947     }           
11948     
11949     gameMode = EditGame;
11950     ModeHighlight();
11951     SetGameInfo();
11952 }
11953
11954
11955 void
11956 EditPositionEvent()
11957 {
11958     if (gameMode == EditPosition) {
11959         EditGameEvent();
11960         return;
11961     }
11962     
11963     EditGameEvent();
11964     if (gameMode != EditGame) return;
11965     
11966     gameMode = EditPosition;
11967     ModeHighlight();
11968     SetGameInfo();
11969     if (currentMove > 0)
11970       CopyBoard(boards[0], boards[currentMove]);
11971     
11972     blackPlaysFirst = !WhiteOnMove(currentMove);
11973     ResetClocks();
11974     currentMove = forwardMostMove = backwardMostMove = 0;
11975     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11976     DisplayMove(-1);
11977 }
11978
11979 void
11980 ExitAnalyzeMode()
11981 {
11982     /* [DM] icsEngineAnalyze - possible call from other functions */
11983     if (appData.icsEngineAnalyze) {
11984         appData.icsEngineAnalyze = FALSE;
11985
11986         DisplayMessage("",_("Close ICS engine analyze..."));
11987     }
11988     if (first.analysisSupport && first.analyzing) {
11989       SendToProgram("exit\n", &first);
11990       first.analyzing = FALSE;
11991     }
11992     thinkOutput[0] = NULLCHAR;
11993 }
11994
11995 void
11996 EditPositionDone(Boolean fakeRights)
11997 {
11998     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11999
12000     startedFromSetupPosition = TRUE;
12001     InitChessProgram(&first, FALSE);
12002     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12003       boards[0][EP_STATUS] = EP_NONE;
12004       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12005     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12006         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12007         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12008       } else boards[0][CASTLING][2] = NoRights;
12009     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12010         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12011         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12012       } else boards[0][CASTLING][5] = NoRights;
12013     }
12014     SendToProgram("force\n", &first);
12015     if (blackPlaysFirst) {
12016         strcpy(moveList[0], "");
12017         strcpy(parseList[0], "");
12018         currentMove = forwardMostMove = backwardMostMove = 1;
12019         CopyBoard(boards[1], boards[0]);
12020     } else {
12021         currentMove = forwardMostMove = backwardMostMove = 0;
12022     }
12023     SendBoard(&first, forwardMostMove);
12024     if (appData.debugMode) {
12025         fprintf(debugFP, "EditPosDone\n");
12026     }
12027     DisplayTitle("");
12028     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12029     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12030     gameMode = EditGame;
12031     ModeHighlight();
12032     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12033     ClearHighlights(); /* [AS] */
12034 }
12035
12036 /* Pause for `ms' milliseconds */
12037 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12038 void
12039 TimeDelay(ms)
12040      long ms;
12041 {
12042     TimeMark m1, m2;
12043
12044     GetTimeMark(&m1);
12045     do {
12046         GetTimeMark(&m2);
12047     } while (SubtractTimeMarks(&m2, &m1) < ms);
12048 }
12049
12050 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12051 void
12052 SendMultiLineToICS(buf)
12053      char *buf;
12054 {
12055     char temp[MSG_SIZ+1], *p;
12056     int len;
12057
12058     len = strlen(buf);
12059     if (len > MSG_SIZ)
12060       len = MSG_SIZ;
12061   
12062     strncpy(temp, buf, len);
12063     temp[len] = 0;
12064
12065     p = temp;
12066     while (*p) {
12067         if (*p == '\n' || *p == '\r')
12068           *p = ' ';
12069         ++p;
12070     }
12071
12072     strcat(temp, "\n");
12073     SendToICS(temp);
12074     SendToPlayer(temp, strlen(temp));
12075 }
12076
12077 void
12078 SetWhiteToPlayEvent()
12079 {
12080     if (gameMode == EditPosition) {
12081         blackPlaysFirst = FALSE;
12082         DisplayBothClocks();    /* works because currentMove is 0 */
12083     } else if (gameMode == IcsExamining) {
12084         SendToICS(ics_prefix);
12085         SendToICS("tomove white\n");
12086     }
12087 }
12088
12089 void
12090 SetBlackToPlayEvent()
12091 {
12092     if (gameMode == EditPosition) {
12093         blackPlaysFirst = TRUE;
12094         currentMove = 1;        /* kludge */
12095         DisplayBothClocks();
12096         currentMove = 0;
12097     } else if (gameMode == IcsExamining) {
12098         SendToICS(ics_prefix);
12099         SendToICS("tomove black\n");
12100     }
12101 }
12102
12103 void
12104 EditPositionMenuEvent(selection, x, y)
12105      ChessSquare selection;
12106      int x, y;
12107 {
12108     char buf[MSG_SIZ];
12109     ChessSquare piece = boards[0][y][x];
12110
12111     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12112
12113     switch (selection) {
12114       case ClearBoard:
12115         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12116             SendToICS(ics_prefix);
12117             SendToICS("bsetup clear\n");
12118         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12119             SendToICS(ics_prefix);
12120             SendToICS("clearboard\n");
12121         } else {
12122             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12123                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12124                 for (y = 0; y < BOARD_HEIGHT; y++) {
12125                     if (gameMode == IcsExamining) {
12126                         if (boards[currentMove][y][x] != EmptySquare) {
12127                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12128                                     AAA + x, ONE + y);
12129                             SendToICS(buf);
12130                         }
12131                     } else {
12132                         boards[0][y][x] = p;
12133                     }
12134                 }
12135             }
12136         }
12137         if (gameMode == EditPosition) {
12138             DrawPosition(FALSE, boards[0]);
12139         }
12140         break;
12141
12142       case WhitePlay:
12143         SetWhiteToPlayEvent();
12144         break;
12145
12146       case BlackPlay:
12147         SetBlackToPlayEvent();
12148         break;
12149
12150       case EmptySquare:
12151         if (gameMode == IcsExamining) {
12152             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12153             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12154             SendToICS(buf);
12155         } else {
12156             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12157                 if(x == BOARD_LEFT-2) {
12158                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12159                     boards[0][y][1] = 0;
12160                 } else
12161                 if(x == BOARD_RGHT+1) {
12162                     if(y >= gameInfo.holdingsSize) break;
12163                     boards[0][y][BOARD_WIDTH-2] = 0;
12164                 } else break;
12165             }
12166             boards[0][y][x] = EmptySquare;
12167             DrawPosition(FALSE, boards[0]);
12168         }
12169         break;
12170
12171       case PromotePiece:
12172         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12173            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12174             selection = (ChessSquare) (PROMOTED piece);
12175         } else if(piece == EmptySquare) selection = WhiteSilver;
12176         else selection = (ChessSquare)((int)piece - 1);
12177         goto defaultlabel;
12178
12179       case DemotePiece:
12180         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12181            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12182             selection = (ChessSquare) (DEMOTED piece);
12183         } else if(piece == EmptySquare) selection = BlackSilver;
12184         else selection = (ChessSquare)((int)piece + 1);       
12185         goto defaultlabel;
12186
12187       case WhiteQueen:
12188       case BlackQueen:
12189         if(gameInfo.variant == VariantShatranj ||
12190            gameInfo.variant == VariantXiangqi  ||
12191            gameInfo.variant == VariantCourier  ||
12192            gameInfo.variant == VariantMakruk     )
12193             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12194         goto defaultlabel;
12195
12196       case WhiteKing:
12197       case BlackKing:
12198         if(gameInfo.variant == VariantXiangqi)
12199             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12200         if(gameInfo.variant == VariantKnightmate)
12201             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12202       default:
12203         defaultlabel:
12204         if (gameMode == IcsExamining) {
12205             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12206             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12207                     PieceToChar(selection), AAA + x, ONE + y);
12208             SendToICS(buf);
12209         } else {
12210             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12211                 int n;
12212                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12213                     n = PieceToNumber(selection - BlackPawn);
12214                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12215                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12216                     boards[0][BOARD_HEIGHT-1-n][1]++;
12217                 } else
12218                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12219                     n = PieceToNumber(selection);
12220                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12221                     boards[0][n][BOARD_WIDTH-1] = selection;
12222                     boards[0][n][BOARD_WIDTH-2]++;
12223                 }
12224             } else
12225             boards[0][y][x] = selection;
12226             DrawPosition(TRUE, boards[0]);
12227         }
12228         break;
12229     }
12230 }
12231
12232
12233 void
12234 DropMenuEvent(selection, x, y)
12235      ChessSquare selection;
12236      int x, y;
12237 {
12238     ChessMove moveType;
12239
12240     switch (gameMode) {
12241       case IcsPlayingWhite:
12242       case MachinePlaysBlack:
12243         if (!WhiteOnMove(currentMove)) {
12244             DisplayMoveError(_("It is Black's turn"));
12245             return;
12246         }
12247         moveType = WhiteDrop;
12248         break;
12249       case IcsPlayingBlack:
12250       case MachinePlaysWhite:
12251         if (WhiteOnMove(currentMove)) {
12252             DisplayMoveError(_("It is White's turn"));
12253             return;
12254         }
12255         moveType = BlackDrop;
12256         break;
12257       case EditGame:
12258         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12259         break;
12260       default:
12261         return;
12262     }
12263
12264     if (moveType == BlackDrop && selection < BlackPawn) {
12265       selection = (ChessSquare) ((int) selection
12266                                  + (int) BlackPawn - (int) WhitePawn);
12267     }
12268     if (boards[currentMove][y][x] != EmptySquare) {
12269         DisplayMoveError(_("That square is occupied"));
12270         return;
12271     }
12272
12273     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12274 }
12275
12276 void
12277 AcceptEvent()
12278 {
12279     /* Accept a pending offer of any kind from opponent */
12280     
12281     if (appData.icsActive) {
12282         SendToICS(ics_prefix);
12283         SendToICS("accept\n");
12284     } else if (cmailMsgLoaded) {
12285         if (currentMove == cmailOldMove &&
12286             commentList[cmailOldMove] != NULL &&
12287             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12288                    "Black offers a draw" : "White offers a draw")) {
12289             TruncateGame();
12290             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12292         } else {
12293             DisplayError(_("There is no pending offer on this move"), 0);
12294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12295         }
12296     } else {
12297         /* Not used for offers from chess program */
12298     }
12299 }
12300
12301 void
12302 DeclineEvent()
12303 {
12304     /* Decline a pending offer of any kind from opponent */
12305     
12306     if (appData.icsActive) {
12307         SendToICS(ics_prefix);
12308         SendToICS("decline\n");
12309     } else if (cmailMsgLoaded) {
12310         if (currentMove == cmailOldMove &&
12311             commentList[cmailOldMove] != NULL &&
12312             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12313                    "Black offers a draw" : "White offers a draw")) {
12314 #ifdef NOTDEF
12315             AppendComment(cmailOldMove, "Draw declined", TRUE);
12316             DisplayComment(cmailOldMove - 1, "Draw declined");
12317 #endif /*NOTDEF*/
12318         } else {
12319             DisplayError(_("There is no pending offer on this move"), 0);
12320         }
12321     } else {
12322         /* Not used for offers from chess program */
12323     }
12324 }
12325
12326 void
12327 RematchEvent()
12328 {
12329     /* Issue ICS rematch command */
12330     if (appData.icsActive) {
12331         SendToICS(ics_prefix);
12332         SendToICS("rematch\n");
12333     }
12334 }
12335
12336 void
12337 CallFlagEvent()
12338 {
12339     /* Call your opponent's flag (claim a win on time) */
12340     if (appData.icsActive) {
12341         SendToICS(ics_prefix);
12342         SendToICS("flag\n");
12343     } else {
12344         switch (gameMode) {
12345           default:
12346             return;
12347           case MachinePlaysWhite:
12348             if (whiteFlag) {
12349                 if (blackFlag)
12350                   GameEnds(GameIsDrawn, "Both players ran out of time",
12351                            GE_PLAYER);
12352                 else
12353                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12354             } else {
12355                 DisplayError(_("Your opponent is not out of time"), 0);
12356             }
12357             break;
12358           case MachinePlaysBlack:
12359             if (blackFlag) {
12360                 if (whiteFlag)
12361                   GameEnds(GameIsDrawn, "Both players ran out of time",
12362                            GE_PLAYER);
12363                 else
12364                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12365             } else {
12366                 DisplayError(_("Your opponent is not out of time"), 0);
12367             }
12368             break;
12369         }
12370     }
12371 }
12372
12373 void
12374 DrawEvent()
12375 {
12376     /* Offer draw or accept pending draw offer from opponent */
12377     
12378     if (appData.icsActive) {
12379         /* Note: tournament rules require draw offers to be
12380            made after you make your move but before you punch
12381            your clock.  Currently ICS doesn't let you do that;
12382            instead, you immediately punch your clock after making
12383            a move, but you can offer a draw at any time. */
12384         
12385         SendToICS(ics_prefix);
12386         SendToICS("draw\n");
12387         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12388     } else if (cmailMsgLoaded) {
12389         if (currentMove == cmailOldMove &&
12390             commentList[cmailOldMove] != NULL &&
12391             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12392                    "Black offers a draw" : "White offers a draw")) {
12393             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12394             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12395         } else if (currentMove == cmailOldMove + 1) {
12396             char *offer = WhiteOnMove(cmailOldMove) ?
12397               "White offers a draw" : "Black offers a draw";
12398             AppendComment(currentMove, offer, TRUE);
12399             DisplayComment(currentMove - 1, offer);
12400             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12401         } else {
12402             DisplayError(_("You must make your move before offering a draw"), 0);
12403             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12404         }
12405     } else if (first.offeredDraw) {
12406         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12407     } else {
12408         if (first.sendDrawOffers) {
12409             SendToProgram("draw\n", &first);
12410             userOfferedDraw = TRUE;
12411         }
12412     }
12413 }
12414
12415 void
12416 AdjournEvent()
12417 {
12418     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12419     
12420     if (appData.icsActive) {
12421         SendToICS(ics_prefix);
12422         SendToICS("adjourn\n");
12423     } else {
12424         /* Currently GNU Chess doesn't offer or accept Adjourns */
12425     }
12426 }
12427
12428
12429 void
12430 AbortEvent()
12431 {
12432     /* Offer Abort or accept pending Abort offer from opponent */
12433     
12434     if (appData.icsActive) {
12435         SendToICS(ics_prefix);
12436         SendToICS("abort\n");
12437     } else {
12438         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12439     }
12440 }
12441
12442 void
12443 ResignEvent()
12444 {
12445     /* Resign.  You can do this even if it's not your turn. */
12446     
12447     if (appData.icsActive) {
12448         SendToICS(ics_prefix);
12449         SendToICS("resign\n");
12450     } else {
12451         switch (gameMode) {
12452           case MachinePlaysWhite:
12453             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12454             break;
12455           case MachinePlaysBlack:
12456             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12457             break;
12458           case EditGame:
12459             if (cmailMsgLoaded) {
12460                 TruncateGame();
12461                 if (WhiteOnMove(cmailOldMove)) {
12462                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12463                 } else {
12464                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12465                 }
12466                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12467             }
12468             break;
12469           default:
12470             break;
12471         }
12472     }
12473 }
12474
12475
12476 void
12477 StopObservingEvent()
12478 {
12479     /* Stop observing current games */
12480     SendToICS(ics_prefix);
12481     SendToICS("unobserve\n");
12482 }
12483
12484 void
12485 StopExaminingEvent()
12486 {
12487     /* Stop observing current game */
12488     SendToICS(ics_prefix);
12489     SendToICS("unexamine\n");
12490 }
12491
12492 void
12493 ForwardInner(target)
12494      int target;
12495 {
12496     int limit;
12497
12498     if (appData.debugMode)
12499         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12500                 target, currentMove, forwardMostMove);
12501
12502     if (gameMode == EditPosition)
12503       return;
12504
12505     if (gameMode == PlayFromGameFile && !pausing)
12506       PauseEvent();
12507     
12508     if (gameMode == IcsExamining && pausing)
12509       limit = pauseExamForwardMostMove;
12510     else
12511       limit = forwardMostMove;
12512     
12513     if (target > limit) target = limit;
12514
12515     if (target > 0 && moveList[target - 1][0]) {
12516         int fromX, fromY, toX, toY;
12517         toX = moveList[target - 1][2] - AAA;
12518         toY = moveList[target - 1][3] - ONE;
12519         if (moveList[target - 1][1] == '@') {
12520             if (appData.highlightLastMove) {
12521                 SetHighlights(-1, -1, toX, toY);
12522             }
12523         } else {
12524             fromX = moveList[target - 1][0] - AAA;
12525             fromY = moveList[target - 1][1] - ONE;
12526             if (target == currentMove + 1) {
12527                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12528             }
12529             if (appData.highlightLastMove) {
12530                 SetHighlights(fromX, fromY, toX, toY);
12531             }
12532         }
12533     }
12534     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12535         gameMode == Training || gameMode == PlayFromGameFile || 
12536         gameMode == AnalyzeFile) {
12537         while (currentMove < target) {
12538             SendMoveToProgram(currentMove++, &first);
12539         }
12540     } else {
12541         currentMove = target;
12542     }
12543     
12544     if (gameMode == EditGame || gameMode == EndOfGame) {
12545         whiteTimeRemaining = timeRemaining[0][currentMove];
12546         blackTimeRemaining = timeRemaining[1][currentMove];
12547     }
12548     DisplayBothClocks();
12549     DisplayMove(currentMove - 1);
12550     DrawPosition(FALSE, boards[currentMove]);
12551     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12552     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12553         DisplayComment(currentMove - 1, commentList[currentMove]);
12554     }
12555 }
12556
12557
12558 void
12559 ForwardEvent()
12560 {
12561     if (gameMode == IcsExamining && !pausing) {
12562         SendToICS(ics_prefix);
12563         SendToICS("forward\n");
12564     } else {
12565         ForwardInner(currentMove + 1);
12566     }
12567 }
12568
12569 void
12570 ToEndEvent()
12571 {
12572     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12573         /* to optimze, we temporarily turn off analysis mode while we feed
12574          * the remaining moves to the engine. Otherwise we get analysis output
12575          * after each move.
12576          */ 
12577         if (first.analysisSupport) {
12578           SendToProgram("exit\nforce\n", &first);
12579           first.analyzing = FALSE;
12580         }
12581     }
12582         
12583     if (gameMode == IcsExamining && !pausing) {
12584         SendToICS(ics_prefix);
12585         SendToICS("forward 999999\n");
12586     } else {
12587         ForwardInner(forwardMostMove);
12588     }
12589
12590     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12591         /* we have fed all the moves, so reactivate analysis mode */
12592         SendToProgram("analyze\n", &first);
12593         first.analyzing = TRUE;
12594         /*first.maybeThinking = TRUE;*/
12595         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12596     }
12597 }
12598
12599 void
12600 BackwardInner(target)
12601      int target;
12602 {
12603     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12604
12605     if (appData.debugMode)
12606         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12607                 target, currentMove, forwardMostMove);
12608
12609     if (gameMode == EditPosition) return;
12610     if (currentMove <= backwardMostMove) {
12611         ClearHighlights();
12612         DrawPosition(full_redraw, boards[currentMove]);
12613         return;
12614     }
12615     if (gameMode == PlayFromGameFile && !pausing)
12616       PauseEvent();
12617     
12618     if (moveList[target][0]) {
12619         int fromX, fromY, toX, toY;
12620         toX = moveList[target][2] - AAA;
12621         toY = moveList[target][3] - ONE;
12622         if (moveList[target][1] == '@') {
12623             if (appData.highlightLastMove) {
12624                 SetHighlights(-1, -1, toX, toY);
12625             }
12626         } else {
12627             fromX = moveList[target][0] - AAA;
12628             fromY = moveList[target][1] - ONE;
12629             if (target == currentMove - 1) {
12630                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12631             }
12632             if (appData.highlightLastMove) {
12633                 SetHighlights(fromX, fromY, toX, toY);
12634             }
12635         }
12636     }
12637     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12638         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12639         while (currentMove > target) {
12640             SendToProgram("undo\n", &first);
12641             currentMove--;
12642         }
12643     } else {
12644         currentMove = target;
12645     }
12646     
12647     if (gameMode == EditGame || gameMode == EndOfGame) {
12648         whiteTimeRemaining = timeRemaining[0][currentMove];
12649         blackTimeRemaining = timeRemaining[1][currentMove];
12650     }
12651     DisplayBothClocks();
12652     DisplayMove(currentMove - 1);
12653     DrawPosition(full_redraw, boards[currentMove]);
12654     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12655     // [HGM] PV info: routine tests if comment empty
12656     DisplayComment(currentMove - 1, commentList[currentMove]);
12657 }
12658
12659 void
12660 BackwardEvent()
12661 {
12662     if (gameMode == IcsExamining && !pausing) {
12663         SendToICS(ics_prefix);
12664         SendToICS("backward\n");
12665     } else {
12666         BackwardInner(currentMove - 1);
12667     }
12668 }
12669
12670 void
12671 ToStartEvent()
12672 {
12673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12674         /* to optimize, we temporarily turn off analysis mode while we undo
12675          * all the moves. Otherwise we get analysis output after each undo.
12676          */ 
12677         if (first.analysisSupport) {
12678           SendToProgram("exit\nforce\n", &first);
12679           first.analyzing = FALSE;
12680         }
12681     }
12682
12683     if (gameMode == IcsExamining && !pausing) {
12684         SendToICS(ics_prefix);
12685         SendToICS("backward 999999\n");
12686     } else {
12687         BackwardInner(backwardMostMove);
12688     }
12689
12690     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12691         /* we have fed all the moves, so reactivate analysis mode */
12692         SendToProgram("analyze\n", &first);
12693         first.analyzing = TRUE;
12694         /*first.maybeThinking = TRUE;*/
12695         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12696     }
12697 }
12698
12699 void
12700 ToNrEvent(int to)
12701 {
12702   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12703   if (to >= forwardMostMove) to = forwardMostMove;
12704   if (to <= backwardMostMove) to = backwardMostMove;
12705   if (to < currentMove) {
12706     BackwardInner(to);
12707   } else {
12708     ForwardInner(to);
12709   }
12710 }
12711
12712 void
12713 RevertEvent(Boolean annotate)
12714 {
12715     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12716         return;
12717     }
12718     if (gameMode != IcsExamining) {
12719         DisplayError(_("You are not examining a game"), 0);
12720         return;
12721     }
12722     if (pausing) {
12723         DisplayError(_("You can't revert while pausing"), 0);
12724         return;
12725     }
12726     SendToICS(ics_prefix);
12727     SendToICS("revert\n");
12728 }
12729
12730 void
12731 RetractMoveEvent()
12732 {
12733     switch (gameMode) {
12734       case MachinePlaysWhite:
12735       case MachinePlaysBlack:
12736         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12737             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12738             return;
12739         }
12740         if (forwardMostMove < 2) return;
12741         currentMove = forwardMostMove = forwardMostMove - 2;
12742         whiteTimeRemaining = timeRemaining[0][currentMove];
12743         blackTimeRemaining = timeRemaining[1][currentMove];
12744         DisplayBothClocks();
12745         DisplayMove(currentMove - 1);
12746         ClearHighlights();/*!! could figure this out*/
12747         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12748         SendToProgram("remove\n", &first);
12749         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12750         break;
12751
12752       case BeginningOfGame:
12753       default:
12754         break;
12755
12756       case IcsPlayingWhite:
12757       case IcsPlayingBlack:
12758         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12759             SendToICS(ics_prefix);
12760             SendToICS("takeback 2\n");
12761         } else {
12762             SendToICS(ics_prefix);
12763             SendToICS("takeback 1\n");
12764         }
12765         break;
12766     }
12767 }
12768
12769 void
12770 MoveNowEvent()
12771 {
12772     ChessProgramState *cps;
12773
12774     switch (gameMode) {
12775       case MachinePlaysWhite:
12776         if (!WhiteOnMove(forwardMostMove)) {
12777             DisplayError(_("It is your turn"), 0);
12778             return;
12779         }
12780         cps = &first;
12781         break;
12782       case MachinePlaysBlack:
12783         if (WhiteOnMove(forwardMostMove)) {
12784             DisplayError(_("It is your turn"), 0);
12785             return;
12786         }
12787         cps = &first;
12788         break;
12789       case TwoMachinesPlay:
12790         if (WhiteOnMove(forwardMostMove) ==
12791             (first.twoMachinesColor[0] == 'w')) {
12792             cps = &first;
12793         } else {
12794             cps = &second;
12795         }
12796         break;
12797       case BeginningOfGame:
12798       default:
12799         return;
12800     }
12801     SendToProgram("?\n", cps);
12802 }
12803
12804 void
12805 TruncateGameEvent()
12806 {
12807     EditGameEvent();
12808     if (gameMode != EditGame) return;
12809     TruncateGame();
12810 }
12811
12812 void
12813 TruncateGame()
12814 {
12815     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12816     if (forwardMostMove > currentMove) {
12817         if (gameInfo.resultDetails != NULL) {
12818             free(gameInfo.resultDetails);
12819             gameInfo.resultDetails = NULL;
12820             gameInfo.result = GameUnfinished;
12821         }
12822         forwardMostMove = currentMove;
12823         HistorySet(parseList, backwardMostMove, forwardMostMove,
12824                    currentMove-1);
12825     }
12826 }
12827
12828 void
12829 HintEvent()
12830 {
12831     if (appData.noChessProgram) return;
12832     switch (gameMode) {
12833       case MachinePlaysWhite:
12834         if (WhiteOnMove(forwardMostMove)) {
12835             DisplayError(_("Wait until your turn"), 0);
12836             return;
12837         }
12838         break;
12839       case BeginningOfGame:
12840       case MachinePlaysBlack:
12841         if (!WhiteOnMove(forwardMostMove)) {
12842             DisplayError(_("Wait until your turn"), 0);
12843             return;
12844         }
12845         break;
12846       default:
12847         DisplayError(_("No hint available"), 0);
12848         return;
12849     }
12850     SendToProgram("hint\n", &first);
12851     hintRequested = TRUE;
12852 }
12853
12854 void
12855 BookEvent()
12856 {
12857     if (appData.noChessProgram) return;
12858     switch (gameMode) {
12859       case MachinePlaysWhite:
12860         if (WhiteOnMove(forwardMostMove)) {
12861             DisplayError(_("Wait until your turn"), 0);
12862             return;
12863         }
12864         break;
12865       case BeginningOfGame:
12866       case MachinePlaysBlack:
12867         if (!WhiteOnMove(forwardMostMove)) {
12868             DisplayError(_("Wait until your turn"), 0);
12869             return;
12870         }
12871         break;
12872       case EditPosition:
12873         EditPositionDone(TRUE);
12874         break;
12875       case TwoMachinesPlay:
12876         return;
12877       default:
12878         break;
12879     }
12880     SendToProgram("bk\n", &first);
12881     bookOutput[0] = NULLCHAR;
12882     bookRequested = TRUE;
12883 }
12884
12885 void
12886 AboutGameEvent()
12887 {
12888     char *tags = PGNTags(&gameInfo);
12889     TagsPopUp(tags, CmailMsg());
12890     free(tags);
12891 }
12892
12893 /* end button procedures */
12894
12895 void
12896 PrintPosition(fp, move)
12897      FILE *fp;
12898      int move;
12899 {
12900     int i, j;
12901     
12902     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12903         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12904             char c = PieceToChar(boards[move][i][j]);
12905             fputc(c == 'x' ? '.' : c, fp);
12906             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12907         }
12908     }
12909     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12910       fprintf(fp, "white to play\n");
12911     else
12912       fprintf(fp, "black to play\n");
12913 }
12914
12915 void
12916 PrintOpponents(fp)
12917      FILE *fp;
12918 {
12919     if (gameInfo.white != NULL) {
12920         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12921     } else {
12922         fprintf(fp, "\n");
12923     }
12924 }
12925
12926 /* Find last component of program's own name, using some heuristics */
12927 void
12928 TidyProgramName(prog, host, buf)
12929      char *prog, *host, buf[MSG_SIZ];
12930 {
12931     char *p, *q;
12932     int local = (strcmp(host, "localhost") == 0);
12933     while (!local && (p = strchr(prog, ';')) != NULL) {
12934         p++;
12935         while (*p == ' ') p++;
12936         prog = p;
12937     }
12938     if (*prog == '"' || *prog == '\'') {
12939         q = strchr(prog + 1, *prog);
12940     } else {
12941         q = strchr(prog, ' ');
12942     }
12943     if (q == NULL) q = prog + strlen(prog);
12944     p = q;
12945     while (p >= prog && *p != '/' && *p != '\\') p--;
12946     p++;
12947     if(p == prog && *p == '"') p++;
12948     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12949     memcpy(buf, p, q - p);
12950     buf[q - p] = NULLCHAR;
12951     if (!local) {
12952         strcat(buf, "@");
12953         strcat(buf, host);
12954     }
12955 }
12956
12957 char *
12958 TimeControlTagValue()
12959 {
12960     char buf[MSG_SIZ];
12961     if (!appData.clockMode) {
12962         strcpy(buf, "-");
12963     } else if (movesPerSession > 0) {
12964         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12965     } else if (timeIncrement == 0) {
12966         sprintf(buf, "%ld", timeControl/1000);
12967     } else {
12968         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12969     }
12970     return StrSave(buf);
12971 }
12972
12973 void
12974 SetGameInfo()
12975 {
12976     /* This routine is used only for certain modes */
12977     VariantClass v = gameInfo.variant;
12978     ChessMove r = GameUnfinished;
12979     char *p = NULL;
12980
12981     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12982         r = gameInfo.result; 
12983         p = gameInfo.resultDetails; 
12984         gameInfo.resultDetails = NULL;
12985     }
12986     ClearGameInfo(&gameInfo);
12987     gameInfo.variant = v;
12988
12989     switch (gameMode) {
12990       case MachinePlaysWhite:
12991         gameInfo.event = StrSave( appData.pgnEventHeader );
12992         gameInfo.site = StrSave(HostName());
12993         gameInfo.date = PGNDate();
12994         gameInfo.round = StrSave("-");
12995         gameInfo.white = StrSave(first.tidy);
12996         gameInfo.black = StrSave(UserName());
12997         gameInfo.timeControl = TimeControlTagValue();
12998         break;
12999
13000       case MachinePlaysBlack:
13001         gameInfo.event = StrSave( appData.pgnEventHeader );
13002         gameInfo.site = StrSave(HostName());
13003         gameInfo.date = PGNDate();
13004         gameInfo.round = StrSave("-");
13005         gameInfo.white = StrSave(UserName());
13006         gameInfo.black = StrSave(first.tidy);
13007         gameInfo.timeControl = TimeControlTagValue();
13008         break;
13009
13010       case TwoMachinesPlay:
13011         gameInfo.event = StrSave( appData.pgnEventHeader );
13012         gameInfo.site = StrSave(HostName());
13013         gameInfo.date = PGNDate();
13014         if (matchGame > 0) {
13015             char buf[MSG_SIZ];
13016             sprintf(buf, "%d", matchGame);
13017             gameInfo.round = StrSave(buf);
13018         } else {
13019             gameInfo.round = StrSave("-");
13020         }
13021         if (first.twoMachinesColor[0] == 'w') {
13022             gameInfo.white = StrSave(first.tidy);
13023             gameInfo.black = StrSave(second.tidy);
13024         } else {
13025             gameInfo.white = StrSave(second.tidy);
13026             gameInfo.black = StrSave(first.tidy);
13027         }
13028         gameInfo.timeControl = TimeControlTagValue();
13029         break;
13030
13031       case EditGame:
13032         gameInfo.event = StrSave("Edited game");
13033         gameInfo.site = StrSave(HostName());
13034         gameInfo.date = PGNDate();
13035         gameInfo.round = StrSave("-");
13036         gameInfo.white = StrSave("-");
13037         gameInfo.black = StrSave("-");
13038         gameInfo.result = r;
13039         gameInfo.resultDetails = p;
13040         break;
13041
13042       case EditPosition:
13043         gameInfo.event = StrSave("Edited position");
13044         gameInfo.site = StrSave(HostName());
13045         gameInfo.date = PGNDate();
13046         gameInfo.round = StrSave("-");
13047         gameInfo.white = StrSave("-");
13048         gameInfo.black = StrSave("-");
13049         break;
13050
13051       case IcsPlayingWhite:
13052       case IcsPlayingBlack:
13053       case IcsObserving:
13054       case IcsExamining:
13055         break;
13056
13057       case PlayFromGameFile:
13058         gameInfo.event = StrSave("Game from non-PGN file");
13059         gameInfo.site = StrSave(HostName());
13060         gameInfo.date = PGNDate();
13061         gameInfo.round = StrSave("-");
13062         gameInfo.white = StrSave("?");
13063         gameInfo.black = StrSave("?");
13064         break;
13065
13066       default:
13067         break;
13068     }
13069 }
13070
13071 void
13072 ReplaceComment(index, text)
13073      int index;
13074      char *text;
13075 {
13076     int len;
13077
13078     while (*text == '\n') text++;
13079     len = strlen(text);
13080     while (len > 0 && text[len - 1] == '\n') len--;
13081
13082     if (commentList[index] != NULL)
13083       free(commentList[index]);
13084
13085     if (len == 0) {
13086         commentList[index] = NULL;
13087         return;
13088     }
13089   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13090       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13091       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13092     commentList[index] = (char *) malloc(len + 2);
13093     strncpy(commentList[index], text, len);
13094     commentList[index][len] = '\n';
13095     commentList[index][len + 1] = NULLCHAR;
13096   } else { 
13097     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13098     char *p;
13099     commentList[index] = (char *) malloc(len + 6);
13100     strcpy(commentList[index], "{\n");
13101     strncpy(commentList[index]+2, text, len);
13102     commentList[index][len+2] = NULLCHAR;
13103     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13104     strcat(commentList[index], "\n}\n");
13105   }
13106 }
13107
13108 void
13109 CrushCRs(text)
13110      char *text;
13111 {
13112   char *p = text;
13113   char *q = text;
13114   char ch;
13115
13116   do {
13117     ch = *p++;
13118     if (ch == '\r') continue;
13119     *q++ = ch;
13120   } while (ch != '\0');
13121 }
13122
13123 void
13124 AppendComment(index, text, addBraces)
13125      int index;
13126      char *text;
13127      Boolean addBraces; // [HGM] braces: tells if we should add {}
13128 {
13129     int oldlen, len;
13130     char *old;
13131
13132 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13133     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13134
13135     CrushCRs(text);
13136     while (*text == '\n') text++;
13137     len = strlen(text);
13138     while (len > 0 && text[len - 1] == '\n') len--;
13139
13140     if (len == 0) return;
13141
13142     if (commentList[index] != NULL) {
13143         old = commentList[index];
13144         oldlen = strlen(old);
13145         while(commentList[index][oldlen-1] ==  '\n')
13146           commentList[index][--oldlen] = NULLCHAR;
13147         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13148         strcpy(commentList[index], old);
13149         free(old);
13150         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13151         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13152           if(addBraces) addBraces = FALSE; else { text++; len--; }
13153           while (*text == '\n') { text++; len--; }
13154           commentList[index][--oldlen] = NULLCHAR;
13155       }
13156         if(addBraces) strcat(commentList[index], "\n{\n");
13157         else          strcat(commentList[index], "\n");
13158         strcat(commentList[index], text);
13159         if(addBraces) strcat(commentList[index], "\n}\n");
13160         else          strcat(commentList[index], "\n");
13161     } else {
13162         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13163         if(addBraces)
13164              strcpy(commentList[index], "{\n");
13165         else commentList[index][0] = NULLCHAR;
13166         strcat(commentList[index], text);
13167         strcat(commentList[index], "\n");
13168         if(addBraces) strcat(commentList[index], "}\n");
13169     }
13170 }
13171
13172 static char * FindStr( char * text, char * sub_text )
13173 {
13174     char * result = strstr( text, sub_text );
13175
13176     if( result != NULL ) {
13177         result += strlen( sub_text );
13178     }
13179
13180     return result;
13181 }
13182
13183 /* [AS] Try to extract PV info from PGN comment */
13184 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13185 char *GetInfoFromComment( int index, char * text )
13186 {
13187     char * sep = text;
13188
13189     if( text != NULL && index > 0 ) {
13190         int score = 0;
13191         int depth = 0;
13192         int time = -1, sec = 0, deci;
13193         char * s_eval = FindStr( text, "[%eval " );
13194         char * s_emt = FindStr( text, "[%emt " );
13195
13196         if( s_eval != NULL || s_emt != NULL ) {
13197             /* New style */
13198             char delim;
13199
13200             if( s_eval != NULL ) {
13201                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13202                     return text;
13203                 }
13204
13205                 if( delim != ']' ) {
13206                     return text;
13207                 }
13208             }
13209
13210             if( s_emt != NULL ) {
13211             }
13212                 return text;
13213         }
13214         else {
13215             /* We expect something like: [+|-]nnn.nn/dd */
13216             int score_lo = 0;
13217
13218             if(*text != '{') return text; // [HGM] braces: must be normal comment
13219
13220             sep = strchr( text, '/' );
13221             if( sep == NULL || sep < (text+4) ) {
13222                 return text;
13223             }
13224
13225             time = -1; sec = -1; deci = -1;
13226             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13227                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13228                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13229                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13230                 return text;
13231             }
13232
13233             if( score_lo < 0 || score_lo >= 100 ) {
13234                 return text;
13235             }
13236
13237             if(sec >= 0) time = 600*time + 10*sec; else
13238             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13239
13240             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13241
13242             /* [HGM] PV time: now locate end of PV info */
13243             while( *++sep >= '0' && *sep <= '9'); // strip depth
13244             if(time >= 0)
13245             while( *++sep >= '0' && *sep <= '9'); // strip time
13246             if(sec >= 0)
13247             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13248             if(deci >= 0)
13249             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13250             while(*sep == ' ') sep++;
13251         }
13252
13253         if( depth <= 0 ) {
13254             return text;
13255         }
13256
13257         if( time < 0 ) {
13258             time = -1;
13259         }
13260
13261         pvInfoList[index-1].depth = depth;
13262         pvInfoList[index-1].score = score;
13263         pvInfoList[index-1].time  = 10*time; // centi-sec
13264         if(*sep == '}') *sep = 0; else *--sep = '{';
13265     }
13266     return sep;
13267 }
13268
13269 void
13270 SendToProgram(message, cps)
13271      char *message;
13272      ChessProgramState *cps;
13273 {
13274     int count, outCount, error;
13275     char buf[MSG_SIZ];
13276
13277     if (cps->pr == NULL) return;
13278     Attention(cps);
13279     
13280     if (appData.debugMode) {
13281         TimeMark now;
13282         GetTimeMark(&now);
13283         fprintf(debugFP, "%ld >%-6s: %s", 
13284                 SubtractTimeMarks(&now, &programStartTime),
13285                 cps->which, message);
13286     }
13287     
13288     count = strlen(message);
13289     outCount = OutputToProcess(cps->pr, message, count, &error);
13290     if (outCount < count && !exiting 
13291                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13292         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13293         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13294             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13295                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13296                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13297             } else {
13298                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13299             }
13300             gameInfo.resultDetails = StrSave(buf);
13301         }
13302         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13303     }
13304 }
13305
13306 void
13307 ReceiveFromProgram(isr, closure, message, count, error)
13308      InputSourceRef isr;
13309      VOIDSTAR closure;
13310      char *message;
13311      int count;
13312      int error;
13313 {
13314     char *end_str;
13315     char buf[MSG_SIZ];
13316     ChessProgramState *cps = (ChessProgramState *)closure;
13317
13318     if (isr != cps->isr) return; /* Killed intentionally */
13319     if (count <= 0) {
13320         if (count == 0) {
13321             sprintf(buf,
13322                     _("Error: %s chess program (%s) exited unexpectedly"),
13323                     cps->which, cps->program);
13324         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13325                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13326                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13327                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13328                 } else {
13329                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13330                 }
13331                 gameInfo.resultDetails = StrSave(buf);
13332             }
13333             RemoveInputSource(cps->isr);
13334             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13335         } else {
13336             sprintf(buf,
13337                     _("Error reading from %s chess program (%s)"),
13338                     cps->which, cps->program);
13339             RemoveInputSource(cps->isr);
13340
13341             /* [AS] Program is misbehaving badly... kill it */
13342             if( count == -2 ) {
13343                 DestroyChildProcess( cps->pr, 9 );
13344                 cps->pr = NoProc;
13345             }
13346
13347             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13348         }
13349         return;
13350     }
13351     
13352     if ((end_str = strchr(message, '\r')) != NULL)
13353       *end_str = NULLCHAR;
13354     if ((end_str = strchr(message, '\n')) != NULL)
13355       *end_str = NULLCHAR;
13356     
13357     if (appData.debugMode) {
13358         TimeMark now; int print = 1;
13359         char *quote = ""; char c; int i;
13360
13361         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13362                 char start = message[0];
13363                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13364                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13365                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13366                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13367                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13368                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13369                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13370                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13371                         { quote = "# "; print = (appData.engineComments == 2); }
13372                 message[0] = start; // restore original message
13373         }
13374         if(print) {
13375                 GetTimeMark(&now);
13376                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13377                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13378                         quote,
13379                         message);
13380         }
13381     }
13382
13383     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13384     if (appData.icsEngineAnalyze) {
13385         if (strstr(message, "whisper") != NULL ||
13386              strstr(message, "kibitz") != NULL || 
13387             strstr(message, "tellics") != NULL) return;
13388     }
13389
13390     HandleMachineMove(message, cps);
13391 }
13392
13393
13394 void
13395 SendTimeControl(cps, mps, tc, inc, sd, st)
13396      ChessProgramState *cps;
13397      int mps, inc, sd, st;
13398      long tc;
13399 {
13400     char buf[MSG_SIZ];
13401     int seconds;
13402
13403     if( timeControl_2 > 0 ) {
13404         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13405             tc = timeControl_2;
13406         }
13407     }
13408     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13409     inc /= cps->timeOdds;
13410     st  /= cps->timeOdds;
13411
13412     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13413
13414     if (st > 0) {
13415       /* Set exact time per move, normally using st command */
13416       if (cps->stKludge) {
13417         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13418         seconds = st % 60;
13419         if (seconds == 0) {
13420           sprintf(buf, "level 1 %d\n", st/60);
13421         } else {
13422           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13423         }
13424       } else {
13425         sprintf(buf, "st %d\n", st);
13426       }
13427     } else {
13428       /* Set conventional or incremental time control, using level command */
13429       if (seconds == 0) {
13430         /* Note old gnuchess bug -- minutes:seconds used to not work.
13431            Fixed in later versions, but still avoid :seconds
13432            when seconds is 0. */
13433         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13434       } else {
13435         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13436                 seconds, inc/1000);
13437       }
13438     }
13439     SendToProgram(buf, cps);
13440
13441     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13442     /* Orthogonally, limit search to given depth */
13443     if (sd > 0) {
13444       if (cps->sdKludge) {
13445         sprintf(buf, "depth\n%d\n", sd);
13446       } else {
13447         sprintf(buf, "sd %d\n", sd);
13448       }
13449       SendToProgram(buf, cps);
13450     }
13451
13452     if(cps->nps > 0) { /* [HGM] nps */
13453         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13454         else {
13455                 sprintf(buf, "nps %d\n", cps->nps);
13456               SendToProgram(buf, cps);
13457         }
13458     }
13459 }
13460
13461 ChessProgramState *WhitePlayer()
13462 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13463 {
13464     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13465        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13466         return &second;
13467     return &first;
13468 }
13469
13470 void
13471 SendTimeRemaining(cps, machineWhite)
13472      ChessProgramState *cps;
13473      int /*boolean*/ machineWhite;
13474 {
13475     char message[MSG_SIZ];
13476     long time, otime;
13477
13478     /* Note: this routine must be called when the clocks are stopped
13479        or when they have *just* been set or switched; otherwise
13480        it will be off by the time since the current tick started.
13481     */
13482     if (machineWhite) {
13483         time = whiteTimeRemaining / 10;
13484         otime = blackTimeRemaining / 10;
13485     } else {
13486         time = blackTimeRemaining / 10;
13487         otime = whiteTimeRemaining / 10;
13488     }
13489     /* [HGM] translate opponent's time by time-odds factor */
13490     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13491     if (appData.debugMode) {
13492         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13493     }
13494
13495     if (time <= 0) time = 1;
13496     if (otime <= 0) otime = 1;
13497     
13498     sprintf(message, "time %ld\n", time);
13499     SendToProgram(message, cps);
13500
13501     sprintf(message, "otim %ld\n", otime);
13502     SendToProgram(message, cps);
13503 }
13504
13505 int
13506 BoolFeature(p, name, loc, cps)
13507      char **p;
13508      char *name;
13509      int *loc;
13510      ChessProgramState *cps;
13511 {
13512   char buf[MSG_SIZ];
13513   int len = strlen(name);
13514   int val;
13515   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13516     (*p) += len + 1;
13517     sscanf(*p, "%d", &val);
13518     *loc = (val != 0);
13519     while (**p && **p != ' ') (*p)++;
13520     sprintf(buf, "accepted %s\n", name);
13521     SendToProgram(buf, cps);
13522     return TRUE;
13523   }
13524   return FALSE;
13525 }
13526
13527 int
13528 IntFeature(p, name, loc, cps)
13529      char **p;
13530      char *name;
13531      int *loc;
13532      ChessProgramState *cps;
13533 {
13534   char buf[MSG_SIZ];
13535   int len = strlen(name);
13536   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13537     (*p) += len + 1;
13538     sscanf(*p, "%d", loc);
13539     while (**p && **p != ' ') (*p)++;
13540     sprintf(buf, "accepted %s\n", name);
13541     SendToProgram(buf, cps);
13542     return TRUE;
13543   }
13544   return FALSE;
13545 }
13546
13547 int
13548 StringFeature(p, name, loc, cps)
13549      char **p;
13550      char *name;
13551      char loc[];
13552      ChessProgramState *cps;
13553 {
13554   char buf[MSG_SIZ];
13555   int len = strlen(name);
13556   if (strncmp((*p), name, len) == 0
13557       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13558     (*p) += len + 2;
13559     sscanf(*p, "%[^\"]", loc);
13560     while (**p && **p != '\"') (*p)++;
13561     if (**p == '\"') (*p)++;
13562     sprintf(buf, "accepted %s\n", name);
13563     SendToProgram(buf, cps);
13564     return TRUE;
13565   }
13566   return FALSE;
13567 }
13568
13569 int 
13570 ParseOption(Option *opt, ChessProgramState *cps)
13571 // [HGM] options: process the string that defines an engine option, and determine
13572 // name, type, default value, and allowed value range
13573 {
13574         char *p, *q, buf[MSG_SIZ];
13575         int n, min = (-1)<<31, max = 1<<31, def;
13576
13577         if(p = strstr(opt->name, " -spin ")) {
13578             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13579             if(max < min) max = min; // enforce consistency
13580             if(def < min) def = min;
13581             if(def > max) def = max;
13582             opt->value = def;
13583             opt->min = min;
13584             opt->max = max;
13585             opt->type = Spin;
13586         } else if((p = strstr(opt->name, " -slider "))) {
13587             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13588             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13589             if(max < min) max = min; // enforce consistency
13590             if(def < min) def = min;
13591             if(def > max) def = max;
13592             opt->value = def;
13593             opt->min = min;
13594             opt->max = max;
13595             opt->type = Spin; // Slider;
13596         } else if((p = strstr(opt->name, " -string "))) {
13597             opt->textValue = p+9;
13598             opt->type = TextBox;
13599         } else if((p = strstr(opt->name, " -file "))) {
13600             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13601             opt->textValue = p+7;
13602             opt->type = TextBox; // FileName;
13603         } else if((p = strstr(opt->name, " -path "))) {
13604             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13605             opt->textValue = p+7;
13606             opt->type = TextBox; // PathName;
13607         } else if(p = strstr(opt->name, " -check ")) {
13608             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13609             opt->value = (def != 0);
13610             opt->type = CheckBox;
13611         } else if(p = strstr(opt->name, " -combo ")) {
13612             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13613             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13614             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13615             opt->value = n = 0;
13616             while(q = StrStr(q, " /// ")) {
13617                 n++; *q = 0;    // count choices, and null-terminate each of them
13618                 q += 5;
13619                 if(*q == '*') { // remember default, which is marked with * prefix
13620                     q++;
13621                     opt->value = n;
13622                 }
13623                 cps->comboList[cps->comboCnt++] = q;
13624             }
13625             cps->comboList[cps->comboCnt++] = NULL;
13626             opt->max = n + 1;
13627             opt->type = ComboBox;
13628         } else if(p = strstr(opt->name, " -button")) {
13629             opt->type = Button;
13630         } else if(p = strstr(opt->name, " -save")) {
13631             opt->type = SaveButton;
13632         } else return FALSE;
13633         *p = 0; // terminate option name
13634         // now look if the command-line options define a setting for this engine option.
13635         if(cps->optionSettings && cps->optionSettings[0])
13636             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13637         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13638                 sprintf(buf, "option %s", p);
13639                 if(p = strstr(buf, ",")) *p = 0;
13640                 strcat(buf, "\n");
13641                 SendToProgram(buf, cps);
13642         }
13643         return TRUE;
13644 }
13645
13646 void
13647 FeatureDone(cps, val)
13648      ChessProgramState* cps;
13649      int val;
13650 {
13651   DelayedEventCallback cb = GetDelayedEvent();
13652   if ((cb == InitBackEnd3 && cps == &first) ||
13653       (cb == TwoMachinesEventIfReady && cps == &second)) {
13654     CancelDelayedEvent();
13655     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13656   }
13657   cps->initDone = val;
13658 }
13659
13660 /* Parse feature command from engine */
13661 void
13662 ParseFeatures(args, cps)
13663      char* args;
13664      ChessProgramState *cps;  
13665 {
13666   char *p = args;
13667   char *q;
13668   int val;
13669   char buf[MSG_SIZ];
13670
13671   for (;;) {
13672     while (*p == ' ') p++;
13673     if (*p == NULLCHAR) return;
13674
13675     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13676     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13677     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13678     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13679     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13680     if (BoolFeature(&p, "reuse", &val, cps)) {
13681       /* Engine can disable reuse, but can't enable it if user said no */
13682       if (!val) cps->reuse = FALSE;
13683       continue;
13684     }
13685     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13686     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13687       if (gameMode == TwoMachinesPlay) {
13688         DisplayTwoMachinesTitle();
13689       } else {
13690         DisplayTitle("");
13691       }
13692       continue;
13693     }
13694     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13695     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13696     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13697     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13698     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13699     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13700     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13701     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13702     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13703     if (IntFeature(&p, "done", &val, cps)) {
13704       FeatureDone(cps, val);
13705       continue;
13706     }
13707     /* Added by Tord: */
13708     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13709     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13710     /* End of additions by Tord */
13711
13712     /* [HGM] added features: */
13713     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13714     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13715     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13716     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13717     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13718     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13719     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13720         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13721             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13722             SendToProgram(buf, cps);
13723             continue;
13724         }
13725         if(cps->nrOptions >= MAX_OPTIONS) {
13726             cps->nrOptions--;
13727             sprintf(buf, "%s engine has too many options\n", cps->which);
13728             DisplayError(buf, 0);
13729         }
13730         continue;
13731     }
13732     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13733     /* End of additions by HGM */
13734
13735     /* unknown feature: complain and skip */
13736     q = p;
13737     while (*q && *q != '=') q++;
13738     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13739     SendToProgram(buf, cps);
13740     p = q;
13741     if (*p == '=') {
13742       p++;
13743       if (*p == '\"') {
13744         p++;
13745         while (*p && *p != '\"') p++;
13746         if (*p == '\"') p++;
13747       } else {
13748         while (*p && *p != ' ') p++;
13749       }
13750     }
13751   }
13752
13753 }
13754
13755 void
13756 PeriodicUpdatesEvent(newState)
13757      int newState;
13758 {
13759     if (newState == appData.periodicUpdates)
13760       return;
13761
13762     appData.periodicUpdates=newState;
13763
13764     /* Display type changes, so update it now */
13765 //    DisplayAnalysis();
13766
13767     /* Get the ball rolling again... */
13768     if (newState) {
13769         AnalysisPeriodicEvent(1);
13770         StartAnalysisClock();
13771     }
13772 }
13773
13774 void
13775 PonderNextMoveEvent(newState)
13776      int newState;
13777 {
13778     if (newState == appData.ponderNextMove) return;
13779     if (gameMode == EditPosition) EditPositionDone(TRUE);
13780     if (newState) {
13781         SendToProgram("hard\n", &first);
13782         if (gameMode == TwoMachinesPlay) {
13783             SendToProgram("hard\n", &second);
13784         }
13785     } else {
13786         SendToProgram("easy\n", &first);
13787         thinkOutput[0] = NULLCHAR;
13788         if (gameMode == TwoMachinesPlay) {
13789             SendToProgram("easy\n", &second);
13790         }
13791     }
13792     appData.ponderNextMove = newState;
13793 }
13794
13795 void
13796 NewSettingEvent(option, command, value)
13797      char *command;
13798      int option, value;
13799 {
13800     char buf[MSG_SIZ];
13801
13802     if (gameMode == EditPosition) EditPositionDone(TRUE);
13803     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13804     SendToProgram(buf, &first);
13805     if (gameMode == TwoMachinesPlay) {
13806         SendToProgram(buf, &second);
13807     }
13808 }
13809
13810 void
13811 ShowThinkingEvent()
13812 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13813 {
13814     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13815     int newState = appData.showThinking
13816         // [HGM] thinking: other features now need thinking output as well
13817         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13818     
13819     if (oldState == newState) return;
13820     oldState = newState;
13821     if (gameMode == EditPosition) EditPositionDone(TRUE);
13822     if (oldState) {
13823         SendToProgram("post\n", &first);
13824         if (gameMode == TwoMachinesPlay) {
13825             SendToProgram("post\n", &second);
13826         }
13827     } else {
13828         SendToProgram("nopost\n", &first);
13829         thinkOutput[0] = NULLCHAR;
13830         if (gameMode == TwoMachinesPlay) {
13831             SendToProgram("nopost\n", &second);
13832         }
13833     }
13834 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13835 }
13836
13837 void
13838 AskQuestionEvent(title, question, replyPrefix, which)
13839      char *title; char *question; char *replyPrefix; char *which;
13840 {
13841   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13842   if (pr == NoProc) return;
13843   AskQuestion(title, question, replyPrefix, pr);
13844 }
13845
13846 void
13847 DisplayMove(moveNumber)
13848      int moveNumber;
13849 {
13850     char message[MSG_SIZ];
13851     char res[MSG_SIZ];
13852     char cpThinkOutput[MSG_SIZ];
13853
13854     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13855     
13856     if (moveNumber == forwardMostMove - 1 || 
13857         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13858
13859         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13860
13861         if (strchr(cpThinkOutput, '\n')) {
13862             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13863         }
13864     } else {
13865         *cpThinkOutput = NULLCHAR;
13866     }
13867
13868     /* [AS] Hide thinking from human user */
13869     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13870         *cpThinkOutput = NULLCHAR;
13871         if( thinkOutput[0] != NULLCHAR ) {
13872             int i;
13873
13874             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13875                 cpThinkOutput[i] = '.';
13876             }
13877             cpThinkOutput[i] = NULLCHAR;
13878             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13879         }
13880     }
13881
13882     if (moveNumber == forwardMostMove - 1 &&
13883         gameInfo.resultDetails != NULL) {
13884         if (gameInfo.resultDetails[0] == NULLCHAR) {
13885             sprintf(res, " %s", PGNResult(gameInfo.result));
13886         } else {
13887             sprintf(res, " {%s} %s",
13888                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13889         }
13890     } else {
13891         res[0] = NULLCHAR;
13892     }
13893
13894     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13895         DisplayMessage(res, cpThinkOutput);
13896     } else {
13897         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13898                 WhiteOnMove(moveNumber) ? " " : ".. ",
13899                 parseList[moveNumber], res);
13900         DisplayMessage(message, cpThinkOutput);
13901     }
13902 }
13903
13904 void
13905 DisplayComment(moveNumber, text)
13906      int moveNumber;
13907      char *text;
13908 {
13909     char title[MSG_SIZ];
13910     char buf[8000]; // comment can be long!
13911     int score, depth;
13912     
13913     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13914       strcpy(title, "Comment");
13915     } else {
13916       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13917               WhiteOnMove(moveNumber) ? " " : ".. ",
13918               parseList[moveNumber]);
13919     }
13920     // [HGM] PV info: display PV info together with (or as) comment
13921     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13922       if(text == NULL) text = "";                                           
13923       score = pvInfoList[moveNumber].score;
13924       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13925               depth, (pvInfoList[moveNumber].time+50)/100, text);
13926       text = buf;
13927     }
13928     if (text != NULL && (appData.autoDisplayComment || commentUp))
13929         CommentPopUp(title, text);
13930 }
13931
13932 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13933  * might be busy thinking or pondering.  It can be omitted if your
13934  * gnuchess is configured to stop thinking immediately on any user
13935  * input.  However, that gnuchess feature depends on the FIONREAD
13936  * ioctl, which does not work properly on some flavors of Unix.
13937  */
13938 void
13939 Attention(cps)
13940      ChessProgramState *cps;
13941 {
13942 #if ATTENTION
13943     if (!cps->useSigint) return;
13944     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13945     switch (gameMode) {
13946       case MachinePlaysWhite:
13947       case MachinePlaysBlack:
13948       case TwoMachinesPlay:
13949       case IcsPlayingWhite:
13950       case IcsPlayingBlack:
13951       case AnalyzeMode:
13952       case AnalyzeFile:
13953         /* Skip if we know it isn't thinking */
13954         if (!cps->maybeThinking) return;
13955         if (appData.debugMode)
13956           fprintf(debugFP, "Interrupting %s\n", cps->which);
13957         InterruptChildProcess(cps->pr);
13958         cps->maybeThinking = FALSE;
13959         break;
13960       default:
13961         break;
13962     }
13963 #endif /*ATTENTION*/
13964 }
13965
13966 int
13967 CheckFlags()
13968 {
13969     if (whiteTimeRemaining <= 0) {
13970         if (!whiteFlag) {
13971             whiteFlag = TRUE;
13972             if (appData.icsActive) {
13973                 if (appData.autoCallFlag &&
13974                     gameMode == IcsPlayingBlack && !blackFlag) {
13975                   SendToICS(ics_prefix);
13976                   SendToICS("flag\n");
13977                 }
13978             } else {
13979                 if (blackFlag) {
13980                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13981                 } else {
13982                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13983                     if (appData.autoCallFlag) {
13984                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13985                         return TRUE;
13986                     }
13987                 }
13988             }
13989         }
13990     }
13991     if (blackTimeRemaining <= 0) {
13992         if (!blackFlag) {
13993             blackFlag = TRUE;
13994             if (appData.icsActive) {
13995                 if (appData.autoCallFlag &&
13996                     gameMode == IcsPlayingWhite && !whiteFlag) {
13997                   SendToICS(ics_prefix);
13998                   SendToICS("flag\n");
13999                 }
14000             } else {
14001                 if (whiteFlag) {
14002                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14003                 } else {
14004                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14005                     if (appData.autoCallFlag) {
14006                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14007                         return TRUE;
14008                     }
14009                 }
14010             }
14011         }
14012     }
14013     return FALSE;
14014 }
14015
14016 void
14017 CheckTimeControl()
14018 {
14019     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14020         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14021
14022     /*
14023      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14024      */
14025     if ( !WhiteOnMove(forwardMostMove) )
14026         /* White made time control */
14027         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14028         /* [HGM] time odds: correct new time quota for time odds! */
14029                                             / WhitePlayer()->timeOdds;
14030       else
14031         /* Black made time control */
14032         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14033                                             / WhitePlayer()->other->timeOdds;
14034 }
14035
14036 void
14037 DisplayBothClocks()
14038 {
14039     int wom = gameMode == EditPosition ?
14040       !blackPlaysFirst : WhiteOnMove(currentMove);
14041     DisplayWhiteClock(whiteTimeRemaining, wom);
14042     DisplayBlackClock(blackTimeRemaining, !wom);
14043 }
14044
14045
14046 /* Timekeeping seems to be a portability nightmare.  I think everyone
14047    has ftime(), but I'm really not sure, so I'm including some ifdefs
14048    to use other calls if you don't.  Clocks will be less accurate if
14049    you have neither ftime nor gettimeofday.
14050 */
14051
14052 /* VS 2008 requires the #include outside of the function */
14053 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14054 #include <sys/timeb.h>
14055 #endif
14056
14057 /* Get the current time as a TimeMark */
14058 void
14059 GetTimeMark(tm)
14060      TimeMark *tm;
14061 {
14062 #if HAVE_GETTIMEOFDAY
14063
14064     struct timeval timeVal;
14065     struct timezone timeZone;
14066
14067     gettimeofday(&timeVal, &timeZone);
14068     tm->sec = (long) timeVal.tv_sec; 
14069     tm->ms = (int) (timeVal.tv_usec / 1000L);
14070
14071 #else /*!HAVE_GETTIMEOFDAY*/
14072 #if HAVE_FTIME
14073
14074 // include <sys/timeb.h> / moved to just above start of function
14075     struct timeb timeB;
14076
14077     ftime(&timeB);
14078     tm->sec = (long) timeB.time;
14079     tm->ms = (int) timeB.millitm;
14080
14081 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14082     tm->sec = (long) time(NULL);
14083     tm->ms = 0;
14084 #endif
14085 #endif
14086 }
14087
14088 /* Return the difference in milliseconds between two
14089    time marks.  We assume the difference will fit in a long!
14090 */
14091 long
14092 SubtractTimeMarks(tm2, tm1)
14093      TimeMark *tm2, *tm1;
14094 {
14095     return 1000L*(tm2->sec - tm1->sec) +
14096            (long) (tm2->ms - tm1->ms);
14097 }
14098
14099
14100 /*
14101  * Code to manage the game clocks.
14102  *
14103  * In tournament play, black starts the clock and then white makes a move.
14104  * We give the human user a slight advantage if he is playing white---the
14105  * clocks don't run until he makes his first move, so it takes zero time.
14106  * Also, we don't account for network lag, so we could get out of sync
14107  * with GNU Chess's clock -- but then, referees are always right.  
14108  */
14109
14110 static TimeMark tickStartTM;
14111 static long intendedTickLength;
14112
14113 long
14114 NextTickLength(timeRemaining)
14115      long timeRemaining;
14116 {
14117     long nominalTickLength, nextTickLength;
14118
14119     if (timeRemaining > 0L && timeRemaining <= 10000L)
14120       nominalTickLength = 100L;
14121     else
14122       nominalTickLength = 1000L;
14123     nextTickLength = timeRemaining % nominalTickLength;
14124     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14125
14126     return nextTickLength;
14127 }
14128
14129 /* Adjust clock one minute up or down */
14130 void
14131 AdjustClock(Boolean which, int dir)
14132 {
14133     if(which) blackTimeRemaining += 60000*dir;
14134     else      whiteTimeRemaining += 60000*dir;
14135     DisplayBothClocks();
14136 }
14137
14138 /* Stop clocks and reset to a fresh time control */
14139 void
14140 ResetClocks() 
14141 {
14142     (void) StopClockTimer();
14143     if (appData.icsActive) {
14144         whiteTimeRemaining = blackTimeRemaining = 0;
14145     } else if (searchTime) {
14146         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14147         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14148     } else { /* [HGM] correct new time quote for time odds */
14149         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14150         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14151     }
14152     if (whiteFlag || blackFlag) {
14153         DisplayTitle("");
14154         whiteFlag = blackFlag = FALSE;
14155     }
14156     DisplayBothClocks();
14157 }
14158
14159 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14160
14161 /* Decrement running clock by amount of time that has passed */
14162 void
14163 DecrementClocks()
14164 {
14165     long timeRemaining;
14166     long lastTickLength, fudge;
14167     TimeMark now;
14168
14169     if (!appData.clockMode) return;
14170     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14171         
14172     GetTimeMark(&now);
14173
14174     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14175
14176     /* Fudge if we woke up a little too soon */
14177     fudge = intendedTickLength - lastTickLength;
14178     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14179
14180     if (WhiteOnMove(forwardMostMove)) {
14181         if(whiteNPS >= 0) lastTickLength = 0;
14182         timeRemaining = whiteTimeRemaining -= lastTickLength;
14183         DisplayWhiteClock(whiteTimeRemaining - fudge,
14184                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14185     } else {
14186         if(blackNPS >= 0) lastTickLength = 0;
14187         timeRemaining = blackTimeRemaining -= lastTickLength;
14188         DisplayBlackClock(blackTimeRemaining - fudge,
14189                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14190     }
14191
14192     if (CheckFlags()) return;
14193         
14194     tickStartTM = now;
14195     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14196     StartClockTimer(intendedTickLength);
14197
14198     /* if the time remaining has fallen below the alarm threshold, sound the
14199      * alarm. if the alarm has sounded and (due to a takeback or time control
14200      * with increment) the time remaining has increased to a level above the
14201      * threshold, reset the alarm so it can sound again. 
14202      */
14203     
14204     if (appData.icsActive && appData.icsAlarm) {
14205
14206         /* make sure we are dealing with the user's clock */
14207         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14208                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14209            )) return;
14210
14211         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14212             alarmSounded = FALSE;
14213         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14214             PlayAlarmSound();
14215             alarmSounded = TRUE;
14216         }
14217     }
14218 }
14219
14220
14221 /* A player has just moved, so stop the previously running
14222    clock and (if in clock mode) start the other one.
14223    We redisplay both clocks in case we're in ICS mode, because
14224    ICS gives us an update to both clocks after every move.
14225    Note that this routine is called *after* forwardMostMove
14226    is updated, so the last fractional tick must be subtracted
14227    from the color that is *not* on move now.
14228 */
14229 void
14230 SwitchClocks(int newMoveNr)
14231 {
14232     long lastTickLength;
14233     TimeMark now;
14234     int flagged = FALSE;
14235
14236     GetTimeMark(&now);
14237
14238     if (StopClockTimer() && appData.clockMode) {
14239         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14240         if (!WhiteOnMove(forwardMostMove)) {
14241             if(blackNPS >= 0) lastTickLength = 0;
14242             blackTimeRemaining -= lastTickLength;
14243            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14244 //         if(pvInfoList[forwardMostMove-1].time == -1)
14245                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14246                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14247         } else {
14248            if(whiteNPS >= 0) lastTickLength = 0;
14249            whiteTimeRemaining -= lastTickLength;
14250            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14251 //         if(pvInfoList[forwardMostMove-1].time == -1)
14252                  pvInfoList[forwardMostMove-1].time = 
14253                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14254         }
14255         flagged = CheckFlags();
14256     }
14257     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14258     CheckTimeControl();
14259
14260     if (flagged || !appData.clockMode) return;
14261
14262     switch (gameMode) {
14263       case MachinePlaysBlack:
14264       case MachinePlaysWhite:
14265       case BeginningOfGame:
14266         if (pausing) return;
14267         break;
14268
14269       case EditGame:
14270       case PlayFromGameFile:
14271       case IcsExamining:
14272         return;
14273
14274       default:
14275         break;
14276     }
14277
14278     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14279         if(WhiteOnMove(forwardMostMove))
14280              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14281         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14282     }
14283
14284     tickStartTM = now;
14285     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14286       whiteTimeRemaining : blackTimeRemaining);
14287     StartClockTimer(intendedTickLength);
14288 }
14289         
14290
14291 /* Stop both clocks */
14292 void
14293 StopClocks()
14294 {       
14295     long lastTickLength;
14296     TimeMark now;
14297
14298     if (!StopClockTimer()) return;
14299     if (!appData.clockMode) return;
14300
14301     GetTimeMark(&now);
14302
14303     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14304     if (WhiteOnMove(forwardMostMove)) {
14305         if(whiteNPS >= 0) lastTickLength = 0;
14306         whiteTimeRemaining -= lastTickLength;
14307         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14308     } else {
14309         if(blackNPS >= 0) lastTickLength = 0;
14310         blackTimeRemaining -= lastTickLength;
14311         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14312     }
14313     CheckFlags();
14314 }
14315         
14316 /* Start clock of player on move.  Time may have been reset, so
14317    if clock is already running, stop and restart it. */
14318 void
14319 StartClocks()
14320 {
14321     (void) StopClockTimer(); /* in case it was running already */
14322     DisplayBothClocks();
14323     if (CheckFlags()) return;
14324
14325     if (!appData.clockMode) return;
14326     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14327
14328     GetTimeMark(&tickStartTM);
14329     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14330       whiteTimeRemaining : blackTimeRemaining);
14331
14332    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14333     whiteNPS = blackNPS = -1; 
14334     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14335        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14336         whiteNPS = first.nps;
14337     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14338        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14339         blackNPS = first.nps;
14340     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14341         whiteNPS = second.nps;
14342     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14343         blackNPS = second.nps;
14344     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14345
14346     StartClockTimer(intendedTickLength);
14347 }
14348
14349 char *
14350 TimeString(ms)
14351      long ms;
14352 {
14353     long second, minute, hour, day;
14354     char *sign = "";
14355     static char buf[32];
14356     
14357     if (ms > 0 && ms <= 9900) {
14358       /* convert milliseconds to tenths, rounding up */
14359       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14360
14361       sprintf(buf, " %03.1f ", tenths/10.0);
14362       return buf;
14363     }
14364
14365     /* convert milliseconds to seconds, rounding up */
14366     /* use floating point to avoid strangeness of integer division
14367        with negative dividends on many machines */
14368     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14369
14370     if (second < 0) {
14371         sign = "-";
14372         second = -second;
14373     }
14374     
14375     day = second / (60 * 60 * 24);
14376     second = second % (60 * 60 * 24);
14377     hour = second / (60 * 60);
14378     second = second % (60 * 60);
14379     minute = second / 60;
14380     second = second % 60;
14381     
14382     if (day > 0)
14383       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14384               sign, day, hour, minute, second);
14385     else if (hour > 0)
14386       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14387     else
14388       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14389     
14390     return buf;
14391 }
14392
14393
14394 /*
14395  * This is necessary because some C libraries aren't ANSI C compliant yet.
14396  */
14397 char *
14398 StrStr(string, match)
14399      char *string, *match;
14400 {
14401     int i, length;
14402     
14403     length = strlen(match);
14404     
14405     for (i = strlen(string) - length; i >= 0; i--, string++)
14406       if (!strncmp(match, string, length))
14407         return string;
14408     
14409     return NULL;
14410 }
14411
14412 char *
14413 StrCaseStr(string, match)
14414      char *string, *match;
14415 {
14416     int i, j, length;
14417     
14418     length = strlen(match);
14419     
14420     for (i = strlen(string) - length; i >= 0; i--, string++) {
14421         for (j = 0; j < length; j++) {
14422             if (ToLower(match[j]) != ToLower(string[j]))
14423               break;
14424         }
14425         if (j == length) return string;
14426     }
14427
14428     return NULL;
14429 }
14430
14431 #ifndef _amigados
14432 int
14433 StrCaseCmp(s1, s2)
14434      char *s1, *s2;
14435 {
14436     char c1, c2;
14437     
14438     for (;;) {
14439         c1 = ToLower(*s1++);
14440         c2 = ToLower(*s2++);
14441         if (c1 > c2) return 1;
14442         if (c1 < c2) return -1;
14443         if (c1 == NULLCHAR) return 0;
14444     }
14445 }
14446
14447
14448 int
14449 ToLower(c)
14450      int c;
14451 {
14452     return isupper(c) ? tolower(c) : c;
14453 }
14454
14455
14456 int
14457 ToUpper(c)
14458      int c;
14459 {
14460     return islower(c) ? toupper(c) : c;
14461 }
14462 #endif /* !_amigados    */
14463
14464 char *
14465 StrSave(s)
14466      char *s;
14467 {
14468     char *ret;
14469
14470     if ((ret = (char *) malloc(strlen(s) + 1))) {
14471         strcpy(ret, s);
14472     }
14473     return ret;
14474 }
14475
14476 char *
14477 StrSavePtr(s, savePtr)
14478      char *s, **savePtr;
14479 {
14480     if (*savePtr) {
14481         free(*savePtr);
14482     }
14483     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14484         strcpy(*savePtr, s);
14485     }
14486     return(*savePtr);
14487 }
14488
14489 char *
14490 PGNDate()
14491 {
14492     time_t clock;
14493     struct tm *tm;
14494     char buf[MSG_SIZ];
14495
14496     clock = time((time_t *)NULL);
14497     tm = localtime(&clock);
14498     sprintf(buf, "%04d.%02d.%02d",
14499             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14500     return StrSave(buf);
14501 }
14502
14503
14504 char *
14505 PositionToFEN(move, overrideCastling)
14506      int move;
14507      char *overrideCastling;
14508 {
14509     int i, j, fromX, fromY, toX, toY;
14510     int whiteToPlay;
14511     char buf[128];
14512     char *p, *q;
14513     int emptycount;
14514     ChessSquare piece;
14515
14516     whiteToPlay = (gameMode == EditPosition) ?
14517       !blackPlaysFirst : (move % 2 == 0);
14518     p = buf;
14519
14520     /* Piece placement data */
14521     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14522         emptycount = 0;
14523         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14524             if (boards[move][i][j] == EmptySquare) {
14525                 emptycount++;
14526             } else { ChessSquare piece = boards[move][i][j];
14527                 if (emptycount > 0) {
14528                     if(emptycount<10) /* [HGM] can be >= 10 */
14529                         *p++ = '0' + emptycount;
14530                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14531                     emptycount = 0;
14532                 }
14533                 if(PieceToChar(piece) == '+') {
14534                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14535                     *p++ = '+';
14536                     piece = (ChessSquare)(DEMOTED piece);
14537                 } 
14538                 *p++ = PieceToChar(piece);
14539                 if(p[-1] == '~') {
14540                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14541                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14542                     *p++ = '~';
14543                 }
14544             }
14545         }
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         *p++ = '/';
14553     }
14554     *(p - 1) = ' ';
14555
14556     /* [HGM] print Crazyhouse or Shogi holdings */
14557     if( gameInfo.holdingsWidth ) {
14558         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14559         q = p;
14560         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14561             piece = boards[move][i][BOARD_WIDTH-1];
14562             if( piece != EmptySquare )
14563               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14564                   *p++ = PieceToChar(piece);
14565         }
14566         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14567             piece = boards[move][BOARD_HEIGHT-i-1][0];
14568             if( piece != EmptySquare )
14569               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14570                   *p++ = PieceToChar(piece);
14571         }
14572
14573         if( q == p ) *p++ = '-';
14574         *p++ = ']';
14575         *p++ = ' ';
14576     }
14577
14578     /* Active color */
14579     *p++ = whiteToPlay ? 'w' : 'b';
14580     *p++ = ' ';
14581
14582   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14583     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14584   } else {
14585   if(nrCastlingRights) {
14586      q = p;
14587      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14588        /* [HGM] write directly from rights */
14589            if(boards[move][CASTLING][2] != NoRights &&
14590               boards[move][CASTLING][0] != NoRights   )
14591                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14592            if(boards[move][CASTLING][2] != NoRights &&
14593               boards[move][CASTLING][1] != NoRights   )
14594                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14595            if(boards[move][CASTLING][5] != NoRights &&
14596               boards[move][CASTLING][3] != NoRights   )
14597                 *p++ = boards[move][CASTLING][3] + AAA;
14598            if(boards[move][CASTLING][5] != NoRights &&
14599               boards[move][CASTLING][4] != NoRights   )
14600                 *p++ = boards[move][CASTLING][4] + AAA;
14601      } else {
14602
14603         /* [HGM] write true castling rights */
14604         if( nrCastlingRights == 6 ) {
14605             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14606                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14607             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14608                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14609             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14610                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14611             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14612                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14613         }
14614      }
14615      if (q == p) *p++ = '-'; /* No castling rights */
14616      *p++ = ' ';
14617   }
14618
14619   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14620      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14621     /* En passant target square */
14622     if (move > backwardMostMove) {
14623         fromX = moveList[move - 1][0] - AAA;
14624         fromY = moveList[move - 1][1] - ONE;
14625         toX = moveList[move - 1][2] - AAA;
14626         toY = moveList[move - 1][3] - ONE;
14627         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14628             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14629             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14630             fromX == toX) {
14631             /* 2-square pawn move just happened */
14632             *p++ = toX + AAA;
14633             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14634         } else {
14635             *p++ = '-';
14636         }
14637     } else if(move == backwardMostMove) {
14638         // [HGM] perhaps we should always do it like this, and forget the above?
14639         if((signed char)boards[move][EP_STATUS] >= 0) {
14640             *p++ = boards[move][EP_STATUS] + AAA;
14641             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14642         } else {
14643             *p++ = '-';
14644         }
14645     } else {
14646         *p++ = '-';
14647     }
14648     *p++ = ' ';
14649   }
14650   }
14651
14652     /* [HGM] find reversible plies */
14653     {   int i = 0, j=move;
14654
14655         if (appData.debugMode) { int k;
14656             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14657             for(k=backwardMostMove; k<=forwardMostMove; k++)
14658                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14659
14660         }
14661
14662         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14663         if( j == backwardMostMove ) i += initialRulePlies;
14664         sprintf(p, "%d ", i);
14665         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14666     }
14667     /* Fullmove number */
14668     sprintf(p, "%d", (move / 2) + 1);
14669     
14670     return StrSave(buf);
14671 }
14672
14673 Boolean
14674 ParseFEN(board, blackPlaysFirst, fen)
14675     Board board;
14676      int *blackPlaysFirst;
14677      char *fen;
14678 {
14679     int i, j;
14680     char *p;
14681     int emptycount;
14682     ChessSquare piece;
14683
14684     p = fen;
14685
14686     /* [HGM] by default clear Crazyhouse holdings, if present */
14687     if(gameInfo.holdingsWidth) {
14688        for(i=0; i<BOARD_HEIGHT; i++) {
14689            board[i][0]             = EmptySquare; /* black holdings */
14690            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14691            board[i][1]             = (ChessSquare) 0; /* black counts */
14692            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14693        }
14694     }
14695
14696     /* Piece placement data */
14697     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14698         j = 0;
14699         for (;;) {
14700             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14701                 if (*p == '/') p++;
14702                 emptycount = gameInfo.boardWidth - j;
14703                 while (emptycount--)
14704                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14705                 break;
14706 #if(BOARD_FILES >= 10)
14707             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14708                 p++; emptycount=10;
14709                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14710                 while (emptycount--)
14711                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14712 #endif
14713             } else if (isdigit(*p)) {
14714                 emptycount = *p++ - '0';
14715                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14716                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14717                 while (emptycount--)
14718                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14719             } else if (*p == '+' || isalpha(*p)) {
14720                 if (j >= gameInfo.boardWidth) return FALSE;
14721                 if(*p=='+') {
14722                     piece = CharToPiece(*++p);
14723                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14724                     piece = (ChessSquare) (PROMOTED piece ); p++;
14725                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14726                 } else piece = CharToPiece(*p++);
14727
14728                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14729                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14730                     piece = (ChessSquare) (PROMOTED piece);
14731                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14732                     p++;
14733                 }
14734                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14735             } else {
14736                 return FALSE;
14737             }
14738         }
14739     }
14740     while (*p == '/' || *p == ' ') p++;
14741
14742     /* [HGM] look for Crazyhouse holdings here */
14743     while(*p==' ') p++;
14744     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14745         if(*p == '[') p++;
14746         if(*p == '-' ) *p++; /* empty holdings */ else {
14747             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14748             /* if we would allow FEN reading to set board size, we would   */
14749             /* have to add holdings and shift the board read so far here   */
14750             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14751                 *p++;
14752                 if((int) piece >= (int) BlackPawn ) {
14753                     i = (int)piece - (int)BlackPawn;
14754                     i = PieceToNumber((ChessSquare)i);
14755                     if( i >= gameInfo.holdingsSize ) return FALSE;
14756                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14757                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14758                 } else {
14759                     i = (int)piece - (int)WhitePawn;
14760                     i = PieceToNumber((ChessSquare)i);
14761                     if( i >= gameInfo.holdingsSize ) return FALSE;
14762                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14763                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14764                 }
14765             }
14766         }
14767         if(*p == ']') *p++;
14768     }
14769
14770     while(*p == ' ') p++;
14771
14772     /* Active color */
14773     switch (*p++) {
14774       case 'w':
14775         *blackPlaysFirst = FALSE;
14776         break;
14777       case 'b': 
14778         *blackPlaysFirst = TRUE;
14779         break;
14780       default:
14781         return FALSE;
14782     }
14783
14784     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14785     /* return the extra info in global variiables             */
14786
14787     /* set defaults in case FEN is incomplete */
14788     board[EP_STATUS] = EP_UNKNOWN;
14789     for(i=0; i<nrCastlingRights; i++ ) {
14790         board[CASTLING][i] =
14791             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14792     }   /* assume possible unless obviously impossible */
14793     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14794     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14795     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14796                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14797     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14798     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14799     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14800                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14801     FENrulePlies = 0;
14802
14803     while(*p==' ') p++;
14804     if(nrCastlingRights) {
14805       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14806           /* castling indicator present, so default becomes no castlings */
14807           for(i=0; i<nrCastlingRights; i++ ) {
14808                  board[CASTLING][i] = NoRights;
14809           }
14810       }
14811       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14812              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14813              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14814              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14815         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14816
14817         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14818             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14819             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14820         }
14821         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14822             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14823         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14824                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14825         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14826                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14827         switch(c) {
14828           case'K':
14829               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14830               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14831               board[CASTLING][2] = whiteKingFile;
14832               break;
14833           case'Q':
14834               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14835               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14836               board[CASTLING][2] = whiteKingFile;
14837               break;
14838           case'k':
14839               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14840               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14841               board[CASTLING][5] = blackKingFile;
14842               break;
14843           case'q':
14844               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14845               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14846               board[CASTLING][5] = blackKingFile;
14847           case '-':
14848               break;
14849           default: /* FRC castlings */
14850               if(c >= 'a') { /* black rights */
14851                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14852                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14853                   if(i == BOARD_RGHT) break;
14854                   board[CASTLING][5] = i;
14855                   c -= AAA;
14856                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14857                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14858                   if(c > i)
14859                       board[CASTLING][3] = c;
14860                   else
14861                       board[CASTLING][4] = c;
14862               } else { /* white rights */
14863                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14864                     if(board[0][i] == WhiteKing) break;
14865                   if(i == BOARD_RGHT) break;
14866                   board[CASTLING][2] = i;
14867                   c -= AAA - 'a' + 'A';
14868                   if(board[0][c] >= WhiteKing) break;
14869                   if(c > i)
14870                       board[CASTLING][0] = c;
14871                   else
14872                       board[CASTLING][1] = c;
14873               }
14874         }
14875       }
14876       for(i=0; i<nrCastlingRights; i++)
14877         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14878     if (appData.debugMode) {
14879         fprintf(debugFP, "FEN castling rights:");
14880         for(i=0; i<nrCastlingRights; i++)
14881         fprintf(debugFP, " %d", board[CASTLING][i]);
14882         fprintf(debugFP, "\n");
14883     }
14884
14885       while(*p==' ') p++;
14886     }
14887
14888     /* read e.p. field in games that know e.p. capture */
14889     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14890        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14891       if(*p=='-') {
14892         p++; board[EP_STATUS] = EP_NONE;
14893       } else {
14894          char c = *p++ - AAA;
14895
14896          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14897          if(*p >= '0' && *p <='9') *p++;
14898          board[EP_STATUS] = c;
14899       }
14900     }
14901
14902
14903     if(sscanf(p, "%d", &i) == 1) {
14904         FENrulePlies = i; /* 50-move ply counter */
14905         /* (The move number is still ignored)    */
14906     }
14907
14908     return TRUE;
14909 }
14910       
14911 void
14912 EditPositionPasteFEN(char *fen)
14913 {
14914   if (fen != NULL) {
14915     Board initial_position;
14916
14917     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14918       DisplayError(_("Bad FEN position in clipboard"), 0);
14919       return ;
14920     } else {
14921       int savedBlackPlaysFirst = blackPlaysFirst;
14922       EditPositionEvent();
14923       blackPlaysFirst = savedBlackPlaysFirst;
14924       CopyBoard(boards[0], initial_position);
14925       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14926       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14927       DisplayBothClocks();
14928       DrawPosition(FALSE, boards[currentMove]);
14929     }
14930   }
14931 }
14932
14933 static char cseq[12] = "\\   ";
14934
14935 Boolean set_cont_sequence(char *new_seq)
14936 {
14937     int len;
14938     Boolean ret;
14939
14940     // handle bad attempts to set the sequence
14941         if (!new_seq)
14942                 return 0; // acceptable error - no debug
14943
14944     len = strlen(new_seq);
14945     ret = (len > 0) && (len < sizeof(cseq));
14946     if (ret)
14947         strcpy(cseq, new_seq);
14948     else if (appData.debugMode)
14949         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14950     return ret;
14951 }
14952
14953 /*
14954     reformat a source message so words don't cross the width boundary.  internal
14955     newlines are not removed.  returns the wrapped size (no null character unless
14956     included in source message).  If dest is NULL, only calculate the size required
14957     for the dest buffer.  lp argument indicats line position upon entry, and it's
14958     passed back upon exit.
14959 */
14960 int wrap(char *dest, char *src, int count, int width, int *lp)
14961 {
14962     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14963
14964     cseq_len = strlen(cseq);
14965     old_line = line = *lp;
14966     ansi = len = clen = 0;
14967
14968     for (i=0; i < count; i++)
14969     {
14970         if (src[i] == '\033')
14971             ansi = 1;
14972
14973         // if we hit the width, back up
14974         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14975         {
14976             // store i & len in case the word is too long
14977             old_i = i, old_len = len;
14978
14979             // find the end of the last word
14980             while (i && src[i] != ' ' && src[i] != '\n')
14981             {
14982                 i--;
14983                 len--;
14984             }
14985
14986             // word too long?  restore i & len before splitting it
14987             if ((old_i-i+clen) >= width)
14988             {
14989                 i = old_i;
14990                 len = old_len;
14991             }
14992
14993             // extra space?
14994             if (i && src[i-1] == ' ')
14995                 len--;
14996
14997             if (src[i] != ' ' && src[i] != '\n')
14998             {
14999                 i--;
15000                 if (len)
15001                     len--;
15002             }
15003
15004             // now append the newline and continuation sequence
15005             if (dest)
15006                 dest[len] = '\n';
15007             len++;
15008             if (dest)
15009                 strncpy(dest+len, cseq, cseq_len);
15010             len += cseq_len;
15011             line = cseq_len;
15012             clen = cseq_len;
15013             continue;
15014         }
15015
15016         if (dest)
15017             dest[len] = src[i];
15018         len++;
15019         if (!ansi)
15020             line++;
15021         if (src[i] == '\n')
15022             line = 0;
15023         if (src[i] == 'm')
15024             ansi = 0;
15025     }
15026     if (dest && appData.debugMode)
15027     {
15028         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15029             count, width, line, len, *lp);
15030         show_bytes(debugFP, src, count);
15031         fprintf(debugFP, "\ndest: ");
15032         show_bytes(debugFP, dest, len);
15033         fprintf(debugFP, "\n");
15034     }
15035     *lp = dest ? line : old_line;
15036
15037     return len;
15038 }
15039
15040 // [HGM] vari: routines for shelving variations
15041
15042 void 
15043 PushTail(int firstMove, int lastMove)
15044 {
15045         int i, j, nrMoves = lastMove - firstMove;
15046
15047         if(appData.icsActive) { // only in local mode
15048                 forwardMostMove = currentMove; // mimic old ICS behavior
15049                 return;
15050         }
15051         if(storedGames >= MAX_VARIATIONS-1) return;
15052
15053         // push current tail of game on stack
15054         savedResult[storedGames] = gameInfo.result;
15055         savedDetails[storedGames] = gameInfo.resultDetails;
15056         gameInfo.resultDetails = NULL;
15057         savedFirst[storedGames] = firstMove;
15058         savedLast [storedGames] = lastMove;
15059         savedFramePtr[storedGames] = framePtr;
15060         framePtr -= nrMoves; // reserve space for the boards
15061         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15062             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15063             for(j=0; j<MOVE_LEN; j++)
15064                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15065             for(j=0; j<2*MOVE_LEN; j++)
15066                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15067             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15068             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15069             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15070             pvInfoList[firstMove+i-1].depth = 0;
15071             commentList[framePtr+i] = commentList[firstMove+i];
15072             commentList[firstMove+i] = NULL;
15073         }
15074
15075         storedGames++;
15076         forwardMostMove = firstMove; // truncate game so we can start variation
15077         if(storedGames == 1) GreyRevert(FALSE);
15078 }
15079
15080 Boolean
15081 PopTail(Boolean annotate)
15082 {
15083         int i, j, nrMoves;
15084         char buf[8000], moveBuf[20];
15085
15086         if(appData.icsActive) return FALSE; // only in local mode
15087         if(!storedGames) return FALSE; // sanity
15088         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15089
15090         storedGames--;
15091         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15092         nrMoves = savedLast[storedGames] - currentMove;
15093         if(annotate) {
15094                 int cnt = 10;
15095                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15096                 else strcpy(buf, "(");
15097                 for(i=currentMove; i<forwardMostMove; i++) {
15098                         if(WhiteOnMove(i))
15099                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15100                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15101                         strcat(buf, moveBuf);
15102                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15103                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15104                 }
15105                 strcat(buf, ")");
15106         }
15107         for(i=1; i<=nrMoves; i++) { // copy last variation back
15108             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15109             for(j=0; j<MOVE_LEN; j++)
15110                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15111             for(j=0; j<2*MOVE_LEN; j++)
15112                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15113             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15114             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15115             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15116             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15117             commentList[currentMove+i] = commentList[framePtr+i];
15118             commentList[framePtr+i] = NULL;
15119         }
15120         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15121         framePtr = savedFramePtr[storedGames];
15122         gameInfo.result = savedResult[storedGames];
15123         if(gameInfo.resultDetails != NULL) {
15124             free(gameInfo.resultDetails);
15125       }
15126         gameInfo.resultDetails = savedDetails[storedGames];
15127         forwardMostMove = currentMove + nrMoves;
15128         if(storedGames == 0) GreyRevert(TRUE);
15129         return TRUE;
15130 }
15131
15132 void 
15133 CleanupTail()
15134 {       // remove all shelved variations
15135         int i;
15136         for(i=0; i<storedGames; i++) {
15137             if(savedDetails[i])
15138                 free(savedDetails[i]);
15139             savedDetails[i] = NULL;
15140         }
15141         for(i=framePtr; i<MAX_MOVES; i++) {
15142                 if(commentList[i]) free(commentList[i]);
15143                 commentList[i] = NULL;
15144         }
15145         framePtr = MAX_MOVES-1;
15146         storedGames = 0;
15147 }
15148
15149 void
15150 LoadVariation(int index, char *text)
15151 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15152         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15153         int level = 0, move;
15154
15155         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15156         // first find outermost bracketing variation
15157         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15158             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15159                 if(*p == '{') wait = '}'; else
15160                 if(*p == '[') wait = ']'; else
15161                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15162                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15163             }
15164             if(*p == wait) wait = NULLCHAR; // closing ]} found
15165             p++;
15166         }
15167         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15168         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15169         end[1] = NULLCHAR; // clip off comment beyond variation
15170         ToNrEvent(currentMove-1);
15171         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15172         // kludge: use ParsePV() to append variation to game
15173         move = currentMove;
15174         ParsePV(start, TRUE);
15175         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15176         ClearPremoveHighlights();
15177         CommentPopDown();
15178         ToNrEvent(currentMove+1);
15179 }