Fix display of uninitialized boards in background observe
[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; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own
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;
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       if(appData.dualBoard) { twoBoards = partnerUp = 1; flipView = !flipView; InitDrawingSizes(-2,0); } // [HGM] dual
3833       CopyBoard(partnerBoard, board);
3834       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3835       if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own game!
3836       sprintf(partnerStatus, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3837                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3838       DisplayMessage(partnerStatus, "");
3839         partnerBoardValid = TRUE;
3840       return;
3841     }
3842
3843     /* Modify behavior for initial board display on move listing
3844        of wild games.
3845        */
3846     switch (ics_getting_history) {
3847       case H_FALSE:
3848       case H_REQUESTED:
3849         break;
3850       case H_GOT_REQ_HEADER:
3851       case H_GOT_UNREQ_HEADER:
3852         /* This is the initial position of the current game */
3853         gamenum = ics_gamenum;
3854         moveNum = 0;            /* old ICS bug workaround */
3855         if (to_play == 'B') {
3856           startedFromSetupPosition = TRUE;
3857           blackPlaysFirst = TRUE;
3858           moveNum = 1;
3859           if (forwardMostMove == 0) forwardMostMove = 1;
3860           if (backwardMostMove == 0) backwardMostMove = 1;
3861           if (currentMove == 0) currentMove = 1;
3862         }
3863         newGameMode = gameMode;
3864         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3865         break;
3866       case H_GOT_UNWANTED_HEADER:
3867         /* This is an initial board that we don't want */
3868         return;
3869       case H_GETTING_MOVES:
3870         /* Should not happen */
3871         DisplayError(_("Error gathering move list: extra board"), 0);
3872         ics_getting_history = H_FALSE;
3873         return;
3874     }
3875
3876    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3877                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3878      /* [HGM] We seem to have switched variant unexpectedly
3879       * Try to guess new variant from board size
3880       */
3881           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3882           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3883           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3884           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3885           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3886           if(!weird) newVariant = VariantNormal;
3887           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3888           /* Get a move list just to see the header, which
3889              will tell us whether this is really bug or zh */
3890           if (ics_getting_history == H_FALSE) {
3891             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3892             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3893             SendToICS(str);
3894           }
3895     }
3896     
3897     /* Take action if this is the first board of a new game, or of a
3898        different game than is currently being displayed.  */
3899     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3900         relation == RELATION_ISOLATED_BOARD) {
3901         
3902         /* Forget the old game and get the history (if any) of the new one */
3903         if (gameMode != BeginningOfGame) {
3904           Reset(TRUE, TRUE);
3905         }
3906         newGame = TRUE;
3907         if (appData.autoRaiseBoard) BoardToTop();
3908         prevMove = -3;
3909         if (gamenum == -1) {
3910             newGameMode = IcsIdle;
3911         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3912                    appData.getMoveList && !reqFlag) {
3913             /* Need to get game history */
3914             ics_getting_history = H_REQUESTED;
3915             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3916             SendToICS(str);
3917         }
3918         
3919         /* Initially flip the board to have black on the bottom if playing
3920            black or if the ICS flip flag is set, but let the user change
3921            it with the Flip View button. */
3922         flipView = appData.autoFlipView ? 
3923           (newGameMode == IcsPlayingBlack) || ics_flip :
3924           appData.flipView;
3925         
3926         /* Done with values from previous mode; copy in new ones */
3927         gameMode = newGameMode;
3928         ModeHighlight();
3929         ics_gamenum = gamenum;
3930         if (gamenum == gs_gamenum) {
3931             int klen = strlen(gs_kind);
3932             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3933             sprintf(str, "ICS %s", gs_kind);
3934             gameInfo.event = StrSave(str);
3935         } else {
3936             gameInfo.event = StrSave("ICS game");
3937         }
3938         gameInfo.site = StrSave(appData.icsHost);
3939         gameInfo.date = PGNDate();
3940         gameInfo.round = StrSave("-");
3941         gameInfo.white = StrSave(white);
3942         gameInfo.black = StrSave(black);
3943         timeControl = basetime * 60 * 1000;
3944         timeControl_2 = 0;
3945         timeIncrement = increment * 1000;
3946         movesPerSession = 0;
3947         gameInfo.timeControl = TimeControlTagValue();
3948         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3949   if (appData.debugMode) {
3950     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3951     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3952     setbuf(debugFP, NULL);
3953   }
3954
3955         gameInfo.outOfBook = NULL;
3956         
3957         /* Do we have the ratings? */
3958         if (strcmp(player1Name, white) == 0 &&
3959             strcmp(player2Name, black) == 0) {
3960             if (appData.debugMode)
3961               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3962                       player1Rating, player2Rating);
3963             gameInfo.whiteRating = player1Rating;
3964             gameInfo.blackRating = player2Rating;
3965         } else if (strcmp(player2Name, white) == 0 &&
3966                    strcmp(player1Name, black) == 0) {
3967             if (appData.debugMode)
3968               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3969                       player2Rating, player1Rating);
3970             gameInfo.whiteRating = player2Rating;
3971             gameInfo.blackRating = player1Rating;
3972         }
3973         player1Name[0] = player2Name[0] = NULLCHAR;
3974
3975         /* Silence shouts if requested */
3976         if (appData.quietPlay &&
3977             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3978             SendToICS(ics_prefix);
3979             SendToICS("set shout 0\n");
3980         }
3981     }
3982     
3983     /* Deal with midgame name changes */
3984     if (!newGame) {
3985         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3986             if (gameInfo.white) free(gameInfo.white);
3987             gameInfo.white = StrSave(white);
3988         }
3989         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3990             if (gameInfo.black) free(gameInfo.black);
3991             gameInfo.black = StrSave(black);
3992         }
3993     }
3994     
3995     /* Throw away game result if anything actually changes in examine mode */
3996     if (gameMode == IcsExamining && !newGame) {
3997         gameInfo.result = GameUnfinished;
3998         if (gameInfo.resultDetails != NULL) {
3999             free(gameInfo.resultDetails);
4000             gameInfo.resultDetails = NULL;
4001         }
4002     }
4003     
4004     /* In pausing && IcsExamining mode, we ignore boards coming
4005        in if they are in a different variation than we are. */
4006     if (pauseExamInvalid) return;
4007     if (pausing && gameMode == IcsExamining) {
4008         if (moveNum <= pauseExamForwardMostMove) {
4009             pauseExamInvalid = TRUE;
4010             forwardMostMove = pauseExamForwardMostMove;
4011             return;
4012         }
4013     }
4014     
4015   if (appData.debugMode) {
4016     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4017   }
4018     /* Parse the board */
4019     for (k = 0; k < ranks; k++) {
4020       for (j = 0; j < files; j++)
4021         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4022       if(gameInfo.holdingsWidth > 1) {
4023            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4024            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4025       }
4026     }
4027     CopyBoard(boards[moveNum], board);
4028     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4029     if (moveNum == 0) {
4030         startedFromSetupPosition =
4031           !CompareBoards(board, initialPosition);
4032         if(startedFromSetupPosition)
4033             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4034     }
4035
4036     /* [HGM] Set castling rights. Take the outermost Rooks,
4037        to make it also work for FRC opening positions. Note that board12
4038        is really defective for later FRC positions, as it has no way to
4039        indicate which Rook can castle if they are on the same side of King.
4040        For the initial position we grant rights to the outermost Rooks,
4041        and remember thos rights, and we then copy them on positions
4042        later in an FRC game. This means WB might not recognize castlings with
4043        Rooks that have moved back to their original position as illegal,
4044        but in ICS mode that is not its job anyway.
4045     */
4046     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4047     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4048
4049         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4050             if(board[0][i] == WhiteRook) j = i;
4051         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4052         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4053             if(board[0][i] == WhiteRook) j = i;
4054         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4055         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4056             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4057         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4058         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4059             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4060         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4061
4062         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4063         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4064             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4065         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4066             if(board[BOARD_HEIGHT-1][k] == bKing)
4067                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4068         if(gameInfo.variant == VariantTwoKings) {
4069             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4070             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4071             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4072         }
4073     } else { int r;
4074         r = boards[moveNum][CASTLING][0] = initialRights[0];
4075         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4076         r = boards[moveNum][CASTLING][1] = initialRights[1];
4077         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4078         r = boards[moveNum][CASTLING][3] = initialRights[3];
4079         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4080         r = boards[moveNum][CASTLING][4] = initialRights[4];
4081         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4082         /* wildcastle kludge: always assume King has rights */
4083         r = boards[moveNum][CASTLING][2] = initialRights[2];
4084         r = boards[moveNum][CASTLING][5] = initialRights[5];
4085     }
4086     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4087     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4088
4089     
4090     if (ics_getting_history == H_GOT_REQ_HEADER ||
4091         ics_getting_history == H_GOT_UNREQ_HEADER) {
4092         /* This was an initial position from a move list, not
4093            the current position */
4094         return;
4095     }
4096     
4097     /* Update currentMove and known move number limits */
4098     newMove = newGame || moveNum > forwardMostMove;
4099
4100     if (newGame) {
4101         forwardMostMove = backwardMostMove = currentMove = moveNum;
4102         if (gameMode == IcsExamining && moveNum == 0) {
4103           /* Workaround for ICS limitation: we are not told the wild
4104              type when starting to examine a game.  But if we ask for
4105              the move list, the move list header will tell us */
4106             ics_getting_history = H_REQUESTED;
4107             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4108             SendToICS(str);
4109         }
4110     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4111                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4112 #if ZIPPY
4113         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4114         /* [HGM] applied this also to an engine that is silently watching        */
4115         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4116             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4117             gameInfo.variant == currentlyInitializedVariant) {
4118           takeback = forwardMostMove - moveNum;
4119           for (i = 0; i < takeback; i++) {
4120             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4121             SendToProgram("undo\n", &first);
4122           }
4123         }
4124 #endif
4125
4126         forwardMostMove = moveNum;
4127         if (!pausing || currentMove > forwardMostMove)
4128           currentMove = forwardMostMove;
4129     } else {
4130         /* New part of history that is not contiguous with old part */ 
4131         if (pausing && gameMode == IcsExamining) {
4132             pauseExamInvalid = TRUE;
4133             forwardMostMove = pauseExamForwardMostMove;
4134             return;
4135         }
4136         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4137 #if ZIPPY
4138             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4139                 // [HGM] when we will receive the move list we now request, it will be
4140                 // fed to the engine from the first move on. So if the engine is not
4141                 // in the initial position now, bring it there.
4142                 InitChessProgram(&first, 0);
4143             }
4144 #endif
4145             ics_getting_history = H_REQUESTED;
4146             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4147             SendToICS(str);
4148         }
4149         forwardMostMove = backwardMostMove = currentMove = moveNum;
4150     }
4151     
4152     /* Update the clocks */
4153     if (strchr(elapsed_time, '.')) {
4154       /* Time is in ms */
4155       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4156       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4157     } else {
4158       /* Time is in seconds */
4159       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4160       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4161     }
4162       
4163
4164 #if ZIPPY
4165     if (appData.zippyPlay && newGame &&
4166         gameMode != IcsObserving && gameMode != IcsIdle &&
4167         gameMode != IcsExamining)
4168       ZippyFirstBoard(moveNum, basetime, increment);
4169 #endif
4170     
4171     /* Put the move on the move list, first converting
4172        to canonical algebraic form. */
4173     if (moveNum > 0) {
4174   if (appData.debugMode) {
4175     if (appData.debugMode) { int f = forwardMostMove;
4176         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4177                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4178                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4179     }
4180     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4181     fprintf(debugFP, "moveNum = %d\n", moveNum);
4182     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4183     setbuf(debugFP, NULL);
4184   }
4185         if (moveNum <= backwardMostMove) {
4186             /* We don't know what the board looked like before
4187                this move.  Punt. */
4188             strcpy(parseList[moveNum - 1], move_str);
4189             strcat(parseList[moveNum - 1], " ");
4190             strcat(parseList[moveNum - 1], elapsed_time);
4191             moveList[moveNum - 1][0] = NULLCHAR;
4192         } else if (strcmp(move_str, "none") == 0) {
4193             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4194             /* Again, we don't know what the board looked like;
4195                this is really the start of the game. */
4196             parseList[moveNum - 1][0] = NULLCHAR;
4197             moveList[moveNum - 1][0] = NULLCHAR;
4198             backwardMostMove = moveNum;
4199             startedFromSetupPosition = TRUE;
4200             fromX = fromY = toX = toY = -1;
4201         } else {
4202           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4203           //                 So we parse the long-algebraic move string in stead of the SAN move
4204           int valid; char buf[MSG_SIZ], *prom;
4205
4206           // str looks something like "Q/a1-a2"; kill the slash
4207           if(str[1] == '/') 
4208                 sprintf(buf, "%c%s", str[0], str+2);
4209           else  strcpy(buf, str); // might be castling
4210           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4211                 strcat(buf, prom); // long move lacks promo specification!
4212           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4213                 if(appData.debugMode) 
4214                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4215                 strcpy(move_str, buf);
4216           }
4217           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4218                                 &fromX, &fromY, &toX, &toY, &promoChar)
4219                || ParseOneMove(buf, moveNum - 1, &moveType,
4220                                 &fromX, &fromY, &toX, &toY, &promoChar);
4221           // end of long SAN patch
4222           if (valid) {
4223             (void) CoordsToAlgebraic(boards[moveNum - 1],
4224                                      PosFlags(moveNum - 1),
4225                                      fromY, fromX, toY, toX, promoChar,
4226                                      parseList[moveNum-1]);
4227             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4228               case MT_NONE:
4229               case MT_STALEMATE:
4230               default:
4231                 break;
4232               case MT_CHECK:
4233                 if(gameInfo.variant != VariantShogi)
4234                     strcat(parseList[moveNum - 1], "+");
4235                 break;
4236               case MT_CHECKMATE:
4237               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4238                 strcat(parseList[moveNum - 1], "#");
4239                 break;
4240             }
4241             strcat(parseList[moveNum - 1], " ");
4242             strcat(parseList[moveNum - 1], elapsed_time);
4243             /* currentMoveString is set as a side-effect of ParseOneMove */
4244             strcpy(moveList[moveNum - 1], currentMoveString);
4245             strcat(moveList[moveNum - 1], "\n");
4246           } else {
4247             /* Move from ICS was illegal!?  Punt. */
4248   if (appData.debugMode) {
4249     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4250     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4251   }
4252             strcpy(parseList[moveNum - 1], move_str);
4253             strcat(parseList[moveNum - 1], " ");
4254             strcat(parseList[moveNum - 1], elapsed_time);
4255             moveList[moveNum - 1][0] = NULLCHAR;
4256             fromX = fromY = toX = toY = -1;
4257           }
4258         }
4259   if (appData.debugMode) {
4260     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4261     setbuf(debugFP, NULL);
4262   }
4263
4264 #if ZIPPY
4265         /* Send move to chess program (BEFORE animating it). */
4266         if (appData.zippyPlay && !newGame && newMove && 
4267            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4268
4269             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4270                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4271                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4272                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4273                             move_str);
4274                     DisplayError(str, 0);
4275                 } else {
4276                     if (first.sendTime) {
4277                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4278                     }
4279                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4280                     if (firstMove && !bookHit) {
4281                         firstMove = FALSE;
4282                         if (first.useColors) {
4283                           SendToProgram(gameMode == IcsPlayingWhite ?
4284                                         "white\ngo\n" :
4285                                         "black\ngo\n", &first);
4286                         } else {
4287                           SendToProgram("go\n", &first);
4288                         }
4289                         first.maybeThinking = TRUE;
4290                     }
4291                 }
4292             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4293               if (moveList[moveNum - 1][0] == NULLCHAR) {
4294                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4295                 DisplayError(str, 0);
4296               } else {
4297                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4298                 SendMoveToProgram(moveNum - 1, &first);
4299               }
4300             }
4301         }
4302 #endif
4303     }
4304
4305     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4306         /* If move comes from a remote source, animate it.  If it
4307            isn't remote, it will have already been animated. */
4308         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4309             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4310         }
4311         if (!pausing && appData.highlightLastMove) {
4312             SetHighlights(fromX, fromY, toX, toY);
4313         }
4314     }
4315     
4316     /* Start the clocks */
4317     whiteFlag = blackFlag = FALSE;
4318     appData.clockMode = !(basetime == 0 && increment == 0);
4319     if (ticking == 0) {
4320       ics_clock_paused = TRUE;
4321       StopClocks();
4322     } else if (ticking == 1) {
4323       ics_clock_paused = FALSE;
4324     }
4325     if (gameMode == IcsIdle ||
4326         relation == RELATION_OBSERVING_STATIC ||
4327         relation == RELATION_EXAMINING ||
4328         ics_clock_paused)
4329       DisplayBothClocks();
4330     else
4331       StartClocks();
4332     
4333     /* Display opponents and material strengths */
4334     if (gameInfo.variant != VariantBughouse &&
4335         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4336         if (tinyLayout || smallLayout) {
4337             if(gameInfo.variant == VariantNormal)
4338                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4339                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4340                     basetime, increment);
4341             else
4342                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment, (int) gameInfo.variant);
4345         } else {
4346             if(gameInfo.variant == VariantNormal)
4347                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4348                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4349                     basetime, increment);
4350             else
4351                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4352                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4353                     basetime, increment, VariantName(gameInfo.variant));
4354         }
4355         DisplayTitle(str);
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4358   }
4359     }
4360
4361
4362     /* Display the board */
4363     if (!pausing && !appData.noGUI) {
4364       
4365       if (appData.premove)
4366           if (!gotPremove || 
4367              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4368              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4369               ClearPremoveHighlights();
4370
4371       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4372         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4373       DrawPosition(j, boards[currentMove]);
4374
4375       DisplayMove(moveNum - 1);
4376       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4377             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4378               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4379         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4380       }
4381     }
4382
4383     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4384 #if ZIPPY
4385     if(bookHit) { // [HGM] book: simulate book reply
4386         static char bookMove[MSG_SIZ]; // a bit generous?
4387
4388         programStats.nodes = programStats.depth = programStats.time = 
4389         programStats.score = programStats.got_only_move = 0;
4390         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4391
4392         strcpy(bookMove, "move ");
4393         strcat(bookMove, bookHit);
4394         HandleMachineMove(bookMove, &first);
4395     }
4396 #endif
4397 }
4398
4399 void
4400 GetMoveListEvent()
4401 {
4402     char buf[MSG_SIZ];
4403     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4404         ics_getting_history = H_REQUESTED;
4405         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4406         SendToICS(buf);
4407     }
4408 }
4409
4410 void
4411 AnalysisPeriodicEvent(force)
4412      int force;
4413 {
4414     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4415          && !force) || !appData.periodicUpdates)
4416       return;
4417
4418     /* Send . command to Crafty to collect stats */
4419     SendToProgram(".\n", &first);
4420
4421     /* Don't send another until we get a response (this makes
4422        us stop sending to old Crafty's which don't understand
4423        the "." command (sending illegal cmds resets node count & time,
4424        which looks bad)) */
4425     programStats.ok_to_send = 0;
4426 }
4427
4428 void ics_update_width(new_width)
4429         int new_width;
4430 {
4431         ics_printf("set width %d\n", new_width);
4432 }
4433
4434 void
4435 SendMoveToProgram(moveNum, cps)
4436      int moveNum;
4437      ChessProgramState *cps;
4438 {
4439     char buf[MSG_SIZ];
4440
4441     if (cps->useUsermove) {
4442       SendToProgram("usermove ", cps);
4443     }
4444     if (cps->useSAN) {
4445       char *space;
4446       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4447         int len = space - parseList[moveNum];
4448         memcpy(buf, parseList[moveNum], len);
4449         buf[len++] = '\n';
4450         buf[len] = NULLCHAR;
4451       } else {
4452         sprintf(buf, "%s\n", parseList[moveNum]);
4453       }
4454       SendToProgram(buf, cps);
4455     } else {
4456       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4457         AlphaRank(moveList[moveNum], 4);
4458         SendToProgram(moveList[moveNum], cps);
4459         AlphaRank(moveList[moveNum], 4); // and back
4460       } else
4461       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4462        * the engine. It would be nice to have a better way to identify castle 
4463        * moves here. */
4464       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4465                                                                          && cps->useOOCastle) {
4466         int fromX = moveList[moveNum][0] - AAA; 
4467         int fromY = moveList[moveNum][1] - ONE;
4468         int toX = moveList[moveNum][2] - AAA; 
4469         int toY = moveList[moveNum][3] - ONE;
4470         if((boards[moveNum][fromY][fromX] == WhiteKing 
4471             && boards[moveNum][toY][toX] == WhiteRook)
4472            || (boards[moveNum][fromY][fromX] == BlackKing 
4473                && boards[moveNum][toY][toX] == BlackRook)) {
4474           if(toX > fromX) SendToProgram("O-O\n", cps);
4475           else SendToProgram("O-O-O\n", cps);
4476         }
4477         else SendToProgram(moveList[moveNum], cps);
4478       }
4479       else SendToProgram(moveList[moveNum], cps);
4480       /* End of additions by Tord */
4481     }
4482
4483     /* [HGM] setting up the opening has brought engine in force mode! */
4484     /*       Send 'go' if we are in a mode where machine should play. */
4485     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4486         (gameMode == TwoMachinesPlay   ||
4487 #ifdef ZIPPY
4488          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4489 #endif
4490          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4491         SendToProgram("go\n", cps);
4492   if (appData.debugMode) {
4493     fprintf(debugFP, "(extra)\n");
4494   }
4495     }
4496     setboardSpoiledMachineBlack = 0;
4497 }
4498
4499 void
4500 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4501      ChessMove moveType;
4502      int fromX, fromY, toX, toY;
4503 {
4504     char user_move[MSG_SIZ];
4505
4506     switch (moveType) {
4507       default:
4508         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4509                 (int)moveType, fromX, fromY, toX, toY);
4510         DisplayError(user_move + strlen("say "), 0);
4511         break;
4512       case WhiteKingSideCastle:
4513       case BlackKingSideCastle:
4514       case WhiteQueenSideCastleWild:
4515       case BlackQueenSideCastleWild:
4516       /* PUSH Fabien */
4517       case WhiteHSideCastleFR:
4518       case BlackHSideCastleFR:
4519       /* POP Fabien */
4520         sprintf(user_move, "o-o\n");
4521         break;
4522       case WhiteQueenSideCastle:
4523       case BlackQueenSideCastle:
4524       case WhiteKingSideCastleWild:
4525       case BlackKingSideCastleWild:
4526       /* PUSH Fabien */
4527       case WhiteASideCastleFR:
4528       case BlackASideCastleFR:
4529       /* POP Fabien */
4530         sprintf(user_move, "o-o-o\n");
4531         break;
4532       case WhitePromotionQueen:
4533       case BlackPromotionQueen:
4534       case WhitePromotionRook:
4535       case BlackPromotionRook:
4536       case WhitePromotionBishop:
4537       case BlackPromotionBishop:
4538       case WhitePromotionKnight:
4539       case BlackPromotionKnight:
4540       case WhitePromotionKing:
4541       case BlackPromotionKing:
4542       case WhitePromotionChancellor:
4543       case BlackPromotionChancellor:
4544       case WhitePromotionArchbishop:
4545       case BlackPromotionArchbishop:
4546         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4547             sprintf(user_move, "%c%c%c%c=%c\n",
4548                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4549                 PieceToChar(WhiteFerz));
4550         else if(gameInfo.variant == VariantGreat)
4551             sprintf(user_move, "%c%c%c%c=%c\n",
4552                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4553                 PieceToChar(WhiteMan));
4554         else
4555             sprintf(user_move, "%c%c%c%c=%c\n",
4556                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4557                 PieceToChar(PromoPiece(moveType)));
4558         break;
4559       case WhiteDrop:
4560       case BlackDrop:
4561         sprintf(user_move, "%c@%c%c\n",
4562                 ToUpper(PieceToChar((ChessSquare) fromX)),
4563                 AAA + toX, ONE + toY);
4564         break;
4565       case NormalMove:
4566       case WhiteCapturesEnPassant:
4567       case BlackCapturesEnPassant:
4568       case IllegalMove:  /* could be a variant we don't quite understand */
4569         sprintf(user_move, "%c%c%c%c\n",
4570                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4571         break;
4572     }
4573     SendToICS(user_move);
4574     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4575         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4576 }
4577
4578 void
4579 UploadGameEvent()
4580 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4581     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4582     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4583     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4584         DisplayError("You cannot do this while you are playing or observing", 0);
4585         return;
4586     }
4587     if(gameMode != IcsExamining) { // is this ever not the case?
4588         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4589
4590         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4591             sprintf(command, "match %s", ics_handle);
4592         } else { // on FICS we must first go to general examine mode
4593             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4594         }
4595         if(gameInfo.variant != VariantNormal) {
4596             // try figure out wild number, as xboard names are not always valid on ICS
4597             for(i=1; i<=36; i++) {
4598                 sprintf(buf, "wild/%d", i);
4599                 if(StringToVariant(buf) == gameInfo.variant) break;
4600             }
4601             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4602             else if(i == 22) sprintf(buf, "%s fr\n", command);
4603             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4604         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4605         SendToICS(ics_prefix);
4606         SendToICS(buf);
4607         if(startedFromSetupPosition || backwardMostMove != 0) {
4608           fen = PositionToFEN(backwardMostMove, NULL);
4609           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4610             sprintf(buf, "loadfen %s\n", fen);
4611             SendToICS(buf);
4612           } else { // FICS: everything has to set by separate bsetup commands
4613             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4614             sprintf(buf, "bsetup fen %s\n", fen);
4615             SendToICS(buf);
4616             if(!WhiteOnMove(backwardMostMove)) {
4617                 SendToICS("bsetup tomove black\n");
4618             }
4619             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4620             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4621             SendToICS(buf);
4622             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4623             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4624             SendToICS(buf);
4625             i = boards[backwardMostMove][EP_STATUS];
4626             if(i >= 0) { // set e.p.
4627                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4628                 SendToICS(buf);
4629             }
4630             bsetup++;
4631           }
4632         }
4633       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4634             SendToICS("bsetup done\n"); // switch to normal examining.
4635     }
4636     for(i = backwardMostMove; i<last; i++) {
4637         char buf[20];
4638         sprintf(buf, "%s\n", parseList[i]);
4639         SendToICS(buf);
4640     }
4641     SendToICS(ics_prefix);
4642     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4643 }
4644
4645 void
4646 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4647      int rf, ff, rt, ft;
4648      char promoChar;
4649      char move[7];
4650 {
4651     if (rf == DROP_RANK) {
4652         sprintf(move, "%c@%c%c\n",
4653                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4654     } else {
4655         if (promoChar == 'x' || promoChar == NULLCHAR) {
4656             sprintf(move, "%c%c%c%c\n",
4657                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4658         } else {
4659             sprintf(move, "%c%c%c%c%c\n",
4660                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4661         }
4662     }
4663 }
4664
4665 void
4666 ProcessICSInitScript(f)
4667      FILE *f;
4668 {
4669     char buf[MSG_SIZ];
4670
4671     while (fgets(buf, MSG_SIZ, f)) {
4672         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4673     }
4674
4675     fclose(f);
4676 }
4677
4678
4679 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4680 void
4681 AlphaRank(char *move, int n)
4682 {
4683 //    char *p = move, c; int x, y;
4684
4685     if (appData.debugMode) {
4686         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4687     }
4688
4689     if(move[1]=='*' && 
4690        move[2]>='0' && move[2]<='9' &&
4691        move[3]>='a' && move[3]<='x'    ) {
4692         move[1] = '@';
4693         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4694         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4695     } else
4696     if(move[0]>='0' && move[0]<='9' &&
4697        move[1]>='a' && move[1]<='x' &&
4698        move[2]>='0' && move[2]<='9' &&
4699        move[3]>='a' && move[3]<='x'    ) {
4700         /* input move, Shogi -> normal */
4701         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4702         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4703         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4704         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4705     } else
4706     if(move[1]=='@' &&
4707        move[3]>='0' && move[3]<='9' &&
4708        move[2]>='a' && move[2]<='x'    ) {
4709         move[1] = '*';
4710         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4711         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4712     } else
4713     if(
4714        move[0]>='a' && move[0]<='x' &&
4715        move[3]>='0' && move[3]<='9' &&
4716        move[2]>='a' && move[2]<='x'    ) {
4717          /* output move, normal -> Shogi */
4718         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4719         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4720         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4721         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4722         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4723     }
4724     if (appData.debugMode) {
4725         fprintf(debugFP, "   out = '%s'\n", move);
4726     }
4727 }
4728
4729 char yy_textstr[8000];
4730
4731 /* Parser for moves from gnuchess, ICS, or user typein box */
4732 Boolean
4733 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4734      char *move;
4735      int moveNum;
4736      ChessMove *moveType;
4737      int *fromX, *fromY, *toX, *toY;
4738      char *promoChar;
4739 {       
4740     if (appData.debugMode) {
4741         fprintf(debugFP, "move to parse: %s\n", move);
4742     }
4743     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4744
4745     switch (*moveType) {
4746       case WhitePromotionChancellor:
4747       case BlackPromotionChancellor:
4748       case WhitePromotionArchbishop:
4749       case BlackPromotionArchbishop:
4750       case WhitePromotionQueen:
4751       case BlackPromotionQueen:
4752       case WhitePromotionRook:
4753       case BlackPromotionRook:
4754       case WhitePromotionBishop:
4755       case BlackPromotionBishop:
4756       case WhitePromotionKnight:
4757       case BlackPromotionKnight:
4758       case WhitePromotionKing:
4759       case BlackPromotionKing:
4760       case NormalMove:
4761       case WhiteCapturesEnPassant:
4762       case BlackCapturesEnPassant:
4763       case WhiteKingSideCastle:
4764       case WhiteQueenSideCastle:
4765       case BlackKingSideCastle:
4766       case BlackQueenSideCastle:
4767       case WhiteKingSideCastleWild:
4768       case WhiteQueenSideCastleWild:
4769       case BlackKingSideCastleWild:
4770       case BlackQueenSideCastleWild:
4771       /* Code added by Tord: */
4772       case WhiteHSideCastleFR:
4773       case WhiteASideCastleFR:
4774       case BlackHSideCastleFR:
4775       case BlackASideCastleFR:
4776       /* End of code added by Tord */
4777       case IllegalMove:         /* bug or odd chess variant */
4778         *fromX = currentMoveString[0] - AAA;
4779         *fromY = currentMoveString[1] - ONE;
4780         *toX = currentMoveString[2] - AAA;
4781         *toY = currentMoveString[3] - ONE;
4782         *promoChar = currentMoveString[4];
4783         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4784             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4785     if (appData.debugMode) {
4786         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4787     }
4788             *fromX = *fromY = *toX = *toY = 0;
4789             return FALSE;
4790         }
4791         if (appData.testLegality) {
4792           return (*moveType != IllegalMove);
4793         } else {
4794           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4795                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4796         }
4797
4798       case WhiteDrop:
4799       case BlackDrop:
4800         *fromX = *moveType == WhiteDrop ?
4801           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4802           (int) CharToPiece(ToLower(currentMoveString[0]));
4803         *fromY = DROP_RANK;
4804         *toX = currentMoveString[2] - AAA;
4805         *toY = currentMoveString[3] - ONE;
4806         *promoChar = NULLCHAR;
4807         return TRUE;
4808
4809       case AmbiguousMove:
4810       case ImpossibleMove:
4811       case (ChessMove) 0:       /* end of file */
4812       case ElapsedTime:
4813       case Comment:
4814       case PGNTag:
4815       case NAG:
4816       case WhiteWins:
4817       case BlackWins:
4818       case GameIsDrawn:
4819       default:
4820     if (appData.debugMode) {
4821         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4822     }
4823         /* bug? */
4824         *fromX = *fromY = *toX = *toY = 0;
4825         *promoChar = NULLCHAR;
4826         return FALSE;
4827     }
4828 }
4829
4830
4831 void
4832 ParsePV(char *pv, Boolean storeComments)
4833 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4834   int fromX, fromY, toX, toY; char promoChar;
4835   ChessMove moveType;
4836   Boolean valid;
4837   int nr = 0;
4838
4839   endPV = forwardMostMove;
4840   do {
4841     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4842     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4843     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4844 if(appData.debugMode){
4845 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);
4846 }
4847     if(!valid && nr == 0 &&
4848        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4849         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4850         // Hande case where played move is different from leading PV move
4851         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4852         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4853         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4854         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4855           endPV += 2; // if position different, keep this
4856           moveList[endPV-1][0] = fromX + AAA;
4857           moveList[endPV-1][1] = fromY + ONE;
4858           moveList[endPV-1][2] = toX + AAA;
4859           moveList[endPV-1][3] = toY + ONE;
4860           parseList[endPV-1][0] = NULLCHAR;
4861           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4862         }
4863       }
4864     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4865     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4866     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4867     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4868         valid++; // allow comments in PV
4869         continue;
4870     }
4871     nr++;
4872     if(endPV+1 > framePtr) break; // no space, truncate
4873     if(!valid) break;
4874     endPV++;
4875     CopyBoard(boards[endPV], boards[endPV-1]);
4876     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4877     moveList[endPV-1][0] = fromX + AAA;
4878     moveList[endPV-1][1] = fromY + ONE;
4879     moveList[endPV-1][2] = toX + AAA;
4880     moveList[endPV-1][3] = toY + ONE;
4881     if(storeComments)
4882         CoordsToAlgebraic(boards[endPV - 1],
4883                              PosFlags(endPV - 1),
4884                              fromY, fromX, toY, toX, promoChar,
4885                              parseList[endPV - 1]);
4886     else
4887         parseList[endPV-1][0] = NULLCHAR;
4888   } while(valid);
4889   currentMove = endPV;
4890   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4891   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4892                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4893   DrawPosition(TRUE, boards[currentMove]);
4894 }
4895
4896 static int lastX, lastY;
4897
4898 Boolean
4899 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4900 {
4901         int startPV;
4902         char *p;
4903
4904         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4905         lastX = x; lastY = y;
4906         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4907         startPV = index;
4908         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4909         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4910         index = startPV;
4911         do{ while(buf[index] && buf[index] != '\n') index++;
4912         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4913         buf[index] = 0;
4914         ParsePV(buf+startPV, FALSE);
4915         *start = startPV; *end = index-1;
4916         return TRUE;
4917 }
4918
4919 Boolean
4920 LoadPV(int x, int y)
4921 { // called on right mouse click to load PV
4922   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4923   lastX = x; lastY = y;
4924   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4925   return TRUE;
4926 }
4927
4928 void
4929 UnLoadPV()
4930 {
4931   if(endPV < 0) return;
4932   endPV = -1;
4933   currentMove = forwardMostMove;
4934   ClearPremoveHighlights();
4935   DrawPosition(TRUE, boards[currentMove]);
4936 }
4937
4938 void
4939 MovePV(int x, int y, int h)
4940 { // step through PV based on mouse coordinates (called on mouse move)
4941   int margin = h>>3, step = 0;
4942
4943   if(endPV < 0) return;
4944   // we must somehow check if right button is still down (might be released off board!)
4945   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4946   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4947   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4948   if(!step) return;
4949   lastX = x; lastY = y;
4950   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4951   currentMove += step;
4952   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4953   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4954                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4955   DrawPosition(FALSE, boards[currentMove]);
4956 }
4957
4958
4959 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4960 // All positions will have equal probability, but the current method will not provide a unique
4961 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4962 #define DARK 1
4963 #define LITE 2
4964 #define ANY 3
4965
4966 int squaresLeft[4];
4967 int piecesLeft[(int)BlackPawn];
4968 int seed, nrOfShuffles;
4969
4970 void GetPositionNumber()
4971 {       // sets global variable seed
4972         int i;
4973
4974         seed = appData.defaultFrcPosition;
4975         if(seed < 0) { // randomize based on time for negative FRC position numbers
4976                 for(i=0; i<50; i++) seed += random();
4977                 seed = random() ^ random() >> 8 ^ random() << 8;
4978                 if(seed<0) seed = -seed;
4979         }
4980 }
4981
4982 int put(Board board, int pieceType, int rank, int n, int shade)
4983 // put the piece on the (n-1)-th empty squares of the given shade
4984 {
4985         int i;
4986
4987         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4988                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4989                         board[rank][i] = (ChessSquare) pieceType;
4990                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4991                         squaresLeft[ANY]--;
4992                         piecesLeft[pieceType]--; 
4993                         return i;
4994                 }
4995         }
4996         return -1;
4997 }
4998
4999
5000 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5001 // calculate where the next piece goes, (any empty square), and put it there
5002 {
5003         int i;
5004
5005         i = seed % squaresLeft[shade];
5006         nrOfShuffles *= squaresLeft[shade];
5007         seed /= squaresLeft[shade];
5008         put(board, pieceType, rank, i, shade);
5009 }
5010
5011 void AddTwoPieces(Board board, int pieceType, int rank)
5012 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5013 {
5014         int i, n=squaresLeft[ANY], j=n-1, k;
5015
5016         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5017         i = seed % k;  // pick one
5018         nrOfShuffles *= k;
5019         seed /= k;
5020         while(i >= j) i -= j--;
5021         j = n - 1 - j; i += j;
5022         put(board, pieceType, rank, j, ANY);
5023         put(board, pieceType, rank, i, ANY);
5024 }
5025
5026 void SetUpShuffle(Board board, int number)
5027 {
5028         int i, p, first=1;
5029
5030         GetPositionNumber(); nrOfShuffles = 1;
5031
5032         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5033         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5034         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5035
5036         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5037
5038         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5039             p = (int) board[0][i];
5040             if(p < (int) BlackPawn) piecesLeft[p] ++;
5041             board[0][i] = EmptySquare;
5042         }
5043
5044         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5045             // shuffles restricted to allow normal castling put KRR first
5046             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5047                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5048             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5049                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5050             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5051                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5052             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5053                 put(board, WhiteRook, 0, 0, ANY);
5054             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5055         }
5056
5057         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5058             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5059             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5060                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5061                 while(piecesLeft[p] >= 2) {
5062                     AddOnePiece(board, p, 0, LITE);
5063                     AddOnePiece(board, p, 0, DARK);
5064                 }
5065                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5066             }
5067
5068         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5069             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5070             // but we leave King and Rooks for last, to possibly obey FRC restriction
5071             if(p == (int)WhiteRook) continue;
5072             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5073             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5074         }
5075
5076         // now everything is placed, except perhaps King (Unicorn) and Rooks
5077
5078         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5079             // Last King gets castling rights
5080             while(piecesLeft[(int)WhiteUnicorn]) {
5081                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5082                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5083             }
5084
5085             while(piecesLeft[(int)WhiteKing]) {
5086                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5087                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5088             }
5089
5090
5091         } else {
5092             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5093             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5094         }
5095
5096         // Only Rooks can be left; simply place them all
5097         while(piecesLeft[(int)WhiteRook]) {
5098                 i = put(board, WhiteRook, 0, 0, ANY);
5099                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5100                         if(first) {
5101                                 first=0;
5102                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5103                         }
5104                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5105                 }
5106         }
5107         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5108             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5109         }
5110
5111         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5112 }
5113
5114 int SetCharTable( char *table, const char * map )
5115 /* [HGM] moved here from winboard.c because of its general usefulness */
5116 /*       Basically a safe strcpy that uses the last character as King */
5117 {
5118     int result = FALSE; int NrPieces;
5119
5120     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5121                     && NrPieces >= 12 && !(NrPieces&1)) {
5122         int i; /* [HGM] Accept even length from 12 to 34 */
5123
5124         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5125         for( i=0; i<NrPieces/2-1; i++ ) {
5126             table[i] = map[i];
5127             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5128         }
5129         table[(int) WhiteKing]  = map[NrPieces/2-1];
5130         table[(int) BlackKing]  = map[NrPieces-1];
5131
5132         result = TRUE;
5133     }
5134
5135     return result;
5136 }
5137
5138 void Prelude(Board board)
5139 {       // [HGM] superchess: random selection of exo-pieces
5140         int i, j, k; ChessSquare p; 
5141         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5142
5143         GetPositionNumber(); // use FRC position number
5144
5145         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5146             SetCharTable(pieceToChar, appData.pieceToCharTable);
5147             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5148                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5149         }
5150
5151         j = seed%4;                 seed /= 4; 
5152         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5153         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5154         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5155         j = seed%3 + (seed%3 >= j); seed /= 3; 
5156         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5157         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5158         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5159         j = seed%3;                 seed /= 3; 
5160         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5161         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5162         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5163         j = seed%2 + (seed%2 >= j); seed /= 2; 
5164         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5165         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5166         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5167         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5168         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5169         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5170         put(board, exoPieces[0],    0, 0, ANY);
5171         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5172 }
5173
5174 void
5175 InitPosition(redraw)
5176      int redraw;
5177 {
5178     ChessSquare (* pieces)[BOARD_FILES];
5179     int i, j, pawnRow, overrule,
5180     oldx = gameInfo.boardWidth,
5181     oldy = gameInfo.boardHeight,
5182     oldh = gameInfo.holdingsWidth,
5183     oldv = gameInfo.variant;
5184
5185     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5186
5187     /* [AS] Initialize pv info list [HGM] and game status */
5188     {
5189         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5190             pvInfoList[i].depth = 0;
5191             boards[i][EP_STATUS] = EP_NONE;
5192             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5193         }
5194
5195         initialRulePlies = 0; /* 50-move counter start */
5196
5197         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5198         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5199     }
5200
5201     
5202     /* [HGM] logic here is completely changed. In stead of full positions */
5203     /* the initialized data only consist of the two backranks. The switch */
5204     /* selects which one we will use, which is than copied to the Board   */
5205     /* initialPosition, which for the rest is initialized by Pawns and    */
5206     /* empty squares. This initial position is then copied to boards[0],  */
5207     /* possibly after shuffling, so that it remains available.            */
5208
5209     gameInfo.holdingsWidth = 0; /* default board sizes */
5210     gameInfo.boardWidth    = 8;
5211     gameInfo.boardHeight   = 8;
5212     gameInfo.holdingsSize  = 0;
5213     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5214     for(i=0; i<BOARD_FILES-2; i++)
5215       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5216     initialPosition[EP_STATUS] = EP_NONE;
5217     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5218
5219     switch (gameInfo.variant) {
5220     case VariantFischeRandom:
5221       shuffleOpenings = TRUE;
5222     default:
5223       pieces = FIDEArray;
5224       break;
5225     case VariantShatranj:
5226       pieces = ShatranjArray;
5227       nrCastlingRights = 0;
5228       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5229       break;
5230     case VariantMakruk:
5231       pieces = makrukArray;
5232       nrCastlingRights = 0;
5233       startedFromSetupPosition = TRUE;
5234       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5235       break;
5236     case VariantTwoKings:
5237       pieces = twoKingsArray;
5238       break;
5239     case VariantCapaRandom:
5240       shuffleOpenings = TRUE;
5241     case VariantCapablanca:
5242       pieces = CapablancaArray;
5243       gameInfo.boardWidth = 10;
5244       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5245       break;
5246     case VariantGothic:
5247       pieces = GothicArray;
5248       gameInfo.boardWidth = 10;
5249       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5250       break;
5251     case VariantJanus:
5252       pieces = JanusArray;
5253       gameInfo.boardWidth = 10;
5254       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5255       nrCastlingRights = 6;
5256         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5257         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5258         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5259         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5260         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5261         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5262       break;
5263     case VariantFalcon:
5264       pieces = FalconArray;
5265       gameInfo.boardWidth = 10;
5266       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5267       break;
5268     case VariantXiangqi:
5269       pieces = XiangqiArray;
5270       gameInfo.boardWidth  = 9;
5271       gameInfo.boardHeight = 10;
5272       nrCastlingRights = 0;
5273       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5274       break;
5275     case VariantShogi:
5276       pieces = ShogiArray;
5277       gameInfo.boardWidth  = 9;
5278       gameInfo.boardHeight = 9;
5279       gameInfo.holdingsSize = 7;
5280       nrCastlingRights = 0;
5281       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5282       break;
5283     case VariantCourier:
5284       pieces = CourierArray;
5285       gameInfo.boardWidth  = 12;
5286       nrCastlingRights = 0;
5287       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5288       break;
5289     case VariantKnightmate:
5290       pieces = KnightmateArray;
5291       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5292       break;
5293     case VariantFairy:
5294       pieces = fairyArray;
5295       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5296       break;
5297     case VariantGreat:
5298       pieces = GreatArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5301       gameInfo.holdingsSize = 8;
5302       break;
5303     case VariantSuper:
5304       pieces = FIDEArray;
5305       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5306       gameInfo.holdingsSize = 8;
5307       startedFromSetupPosition = TRUE;
5308       break;
5309     case VariantCrazyhouse:
5310     case VariantBughouse:
5311       pieces = FIDEArray;
5312       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5313       gameInfo.holdingsSize = 5;
5314       break;
5315     case VariantWildCastle:
5316       pieces = FIDEArray;
5317       /* !!?shuffle with kings guaranteed to be on d or e file */
5318       shuffleOpenings = 1;
5319       break;
5320     case VariantNoCastle:
5321       pieces = FIDEArray;
5322       nrCastlingRights = 0;
5323       /* !!?unconstrained back-rank shuffle */
5324       shuffleOpenings = 1;
5325       break;
5326     }
5327
5328     overrule = 0;
5329     if(appData.NrFiles >= 0) {
5330         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5331         gameInfo.boardWidth = appData.NrFiles;
5332     }
5333     if(appData.NrRanks >= 0) {
5334         gameInfo.boardHeight = appData.NrRanks;
5335     }
5336     if(appData.holdingsSize >= 0) {
5337         i = appData.holdingsSize;
5338         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5339         gameInfo.holdingsSize = i;
5340     }
5341     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5342     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5343         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5344
5345     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5346     if(pawnRow < 1) pawnRow = 1;
5347     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5348
5349     /* User pieceToChar list overrules defaults */
5350     if(appData.pieceToCharTable != NULL)
5351         SetCharTable(pieceToChar, appData.pieceToCharTable);
5352
5353     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5354
5355         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5356             s = (ChessSquare) 0; /* account holding counts in guard band */
5357         for( i=0; i<BOARD_HEIGHT; i++ )
5358             initialPosition[i][j] = s;
5359
5360         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5361         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5362         initialPosition[pawnRow][j] = WhitePawn;
5363         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5364         if(gameInfo.variant == VariantXiangqi) {
5365             if(j&1) {
5366                 initialPosition[pawnRow][j] = 
5367                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5368                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5369                    initialPosition[2][j] = WhiteCannon;
5370                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5371                 }
5372             }
5373         }
5374         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5375     }
5376     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5377
5378             j=BOARD_LEFT+1;
5379             initialPosition[1][j] = WhiteBishop;
5380             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5381             j=BOARD_RGHT-2;
5382             initialPosition[1][j] = WhiteRook;
5383             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5384     }
5385
5386     if( nrCastlingRights == -1) {
5387         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5388         /*       This sets default castling rights from none to normal corners   */
5389         /* Variants with other castling rights must set them themselves above    */
5390         nrCastlingRights = 6;
5391        
5392         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5393         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5394         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5395         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5396         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5397         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5398      }
5399
5400      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5401      if(gameInfo.variant == VariantGreat) { // promotion commoners
5402         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5403         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5404         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5405         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5406      }
5407   if (appData.debugMode) {
5408     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5409   }
5410     if(shuffleOpenings) {
5411         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5412         startedFromSetupPosition = TRUE;
5413     }
5414     if(startedFromPositionFile) {
5415       /* [HGM] loadPos: use PositionFile for every new game */
5416       CopyBoard(initialPosition, filePosition);
5417       for(i=0; i<nrCastlingRights; i++)
5418           initialRights[i] = filePosition[CASTLING][i];
5419       startedFromSetupPosition = TRUE;
5420     }
5421
5422     CopyBoard(boards[0], initialPosition);
5423
5424     if(oldx != gameInfo.boardWidth ||
5425        oldy != gameInfo.boardHeight ||
5426        oldh != gameInfo.holdingsWidth
5427 #ifdef GOTHIC
5428        || oldv == VariantGothic ||        // For licensing popups
5429        gameInfo.variant == VariantGothic
5430 #endif
5431 #ifdef FALCON
5432        || oldv == VariantFalcon ||
5433        gameInfo.variant == VariantFalcon
5434 #endif
5435                                          )
5436             InitDrawingSizes(-2 ,0);
5437
5438     if (redraw)
5439       DrawPosition(TRUE, boards[currentMove]);
5440 }
5441
5442 void
5443 SendBoard(cps, moveNum)
5444      ChessProgramState *cps;
5445      int moveNum;
5446 {
5447     char message[MSG_SIZ];
5448     
5449     if (cps->useSetboard) {
5450       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5451       sprintf(message, "setboard %s\n", fen);
5452       SendToProgram(message, cps);
5453       free(fen);
5454
5455     } else {
5456       ChessSquare *bp;
5457       int i, j;
5458       /* Kludge to set black to move, avoiding the troublesome and now
5459        * deprecated "black" command.
5460        */
5461       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5462
5463       SendToProgram("edit\n", cps);
5464       SendToProgram("#\n", cps);
5465       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5466         bp = &boards[moveNum][i][BOARD_LEFT];
5467         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5468           if ((int) *bp < (int) BlackPawn) {
5469             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5470                     AAA + j, ONE + i);
5471             if(message[0] == '+' || message[0] == '~') {
5472                 sprintf(message, "%c%c%c+\n",
5473                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5474                         AAA + j, ONE + i);
5475             }
5476             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5477                 message[1] = BOARD_RGHT   - 1 - j + '1';
5478                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5479             }
5480             SendToProgram(message, cps);
5481           }
5482         }
5483       }
5484     
5485       SendToProgram("c\n", cps);
5486       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5487         bp = &boards[moveNum][i][BOARD_LEFT];
5488         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5489           if (((int) *bp != (int) EmptySquare)
5490               && ((int) *bp >= (int) BlackPawn)) {
5491             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5492                     AAA + j, ONE + i);
5493             if(message[0] == '+' || message[0] == '~') {
5494                 sprintf(message, "%c%c%c+\n",
5495                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5496                         AAA + j, ONE + i);
5497             }
5498             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5499                 message[1] = BOARD_RGHT   - 1 - j + '1';
5500                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5501             }
5502             SendToProgram(message, cps);
5503           }
5504         }
5505       }
5506     
5507       SendToProgram(".\n", cps);
5508     }
5509     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5510 }
5511
5512 static int autoQueen; // [HGM] oneclick
5513
5514 int
5515 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5516 {
5517     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5518     /* [HGM] add Shogi promotions */
5519     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5520     ChessSquare piece;
5521     ChessMove moveType;
5522     Boolean premove;
5523
5524     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5525     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5526
5527     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5528       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5529         return FALSE;
5530
5531     piece = boards[currentMove][fromY][fromX];
5532     if(gameInfo.variant == VariantShogi) {
5533         promotionZoneSize = 3;
5534         highestPromotingPiece = (int)WhiteFerz;
5535     } else if(gameInfo.variant == VariantMakruk) {
5536         promotionZoneSize = 3;
5537     }
5538
5539     // next weed out all moves that do not touch the promotion zone at all
5540     if((int)piece >= BlackPawn) {
5541         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5542              return FALSE;
5543         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5544     } else {
5545         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5546            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5547     }
5548
5549     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5550
5551     // weed out mandatory Shogi promotions
5552     if(gameInfo.variant == VariantShogi) {
5553         if(piece >= BlackPawn) {
5554             if(toY == 0 && piece == BlackPawn ||
5555                toY == 0 && piece == BlackQueen ||
5556                toY <= 1 && piece == BlackKnight) {
5557                 *promoChoice = '+';
5558                 return FALSE;
5559             }
5560         } else {
5561             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5562                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5563                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5564                 *promoChoice = '+';
5565                 return FALSE;
5566             }
5567         }
5568     }
5569
5570     // weed out obviously illegal Pawn moves
5571     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5572         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5573         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5574         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5575         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5576         // note we are not allowed to test for valid (non-)capture, due to premove
5577     }
5578
5579     // we either have a choice what to promote to, or (in Shogi) whether to promote
5580     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5581         *promoChoice = PieceToChar(BlackFerz);  // no choice
5582         return FALSE;
5583     }
5584     if(autoQueen) { // predetermined
5585         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5586              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5587         else *promoChoice = PieceToChar(BlackQueen);
5588         return FALSE;
5589     }
5590
5591     // suppress promotion popup on illegal moves that are not premoves
5592     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5593               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5594     if(appData.testLegality && !premove) {
5595         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5596                         fromY, fromX, toY, toX, NULLCHAR);
5597         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5598            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5599             return FALSE;
5600     }
5601
5602     return TRUE;
5603 }
5604
5605 int
5606 InPalace(row, column)
5607      int row, column;
5608 {   /* [HGM] for Xiangqi */
5609     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5610          column < (BOARD_WIDTH + 4)/2 &&
5611          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5612     return FALSE;
5613 }
5614
5615 int
5616 PieceForSquare (x, y)
5617      int x;
5618      int y;
5619 {
5620   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5621      return -1;
5622   else
5623      return boards[currentMove][y][x];
5624 }
5625
5626 int
5627 OKToStartUserMove(x, y)
5628      int x, y;
5629 {
5630     ChessSquare from_piece;
5631     int white_piece;
5632
5633     if (matchMode) return FALSE;
5634     if (gameMode == EditPosition) return TRUE;
5635
5636     if (x >= 0 && y >= 0)
5637       from_piece = boards[currentMove][y][x];
5638     else
5639       from_piece = EmptySquare;
5640
5641     if (from_piece == EmptySquare) return FALSE;
5642
5643     white_piece = (int)from_piece >= (int)WhitePawn &&
5644       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5645
5646     switch (gameMode) {
5647       case PlayFromGameFile:
5648       case AnalyzeFile:
5649       case TwoMachinesPlay:
5650       case EndOfGame:
5651         return FALSE;
5652
5653       case IcsObserving:
5654       case IcsIdle:
5655         return FALSE;
5656
5657       case MachinePlaysWhite:
5658       case IcsPlayingBlack:
5659         if (appData.zippyPlay) return FALSE;
5660         if (white_piece) {
5661             DisplayMoveError(_("You are playing Black"));
5662             return FALSE;
5663         }
5664         break;
5665
5666       case MachinePlaysBlack:
5667       case IcsPlayingWhite:
5668         if (appData.zippyPlay) return FALSE;
5669         if (!white_piece) {
5670             DisplayMoveError(_("You are playing White"));
5671             return FALSE;
5672         }
5673         break;
5674
5675       case EditGame:
5676         if (!white_piece && WhiteOnMove(currentMove)) {
5677             DisplayMoveError(_("It is White's turn"));
5678             return FALSE;
5679         }           
5680         if (white_piece && !WhiteOnMove(currentMove)) {
5681             DisplayMoveError(_("It is Black's turn"));
5682             return FALSE;
5683         }           
5684         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5685             /* Editing correspondence game history */
5686             /* Could disallow this or prompt for confirmation */
5687             cmailOldMove = -1;
5688         }
5689         break;
5690
5691       case BeginningOfGame:
5692         if (appData.icsActive) return FALSE;
5693         if (!appData.noChessProgram) {
5694             if (!white_piece) {
5695                 DisplayMoveError(_("You are playing White"));
5696                 return FALSE;
5697             }
5698         }
5699         break;
5700         
5701       case Training:
5702         if (!white_piece && WhiteOnMove(currentMove)) {
5703             DisplayMoveError(_("It is White's turn"));
5704             return FALSE;
5705         }           
5706         if (white_piece && !WhiteOnMove(currentMove)) {
5707             DisplayMoveError(_("It is Black's turn"));
5708             return FALSE;
5709         }           
5710         break;
5711
5712       default:
5713       case IcsExamining:
5714         break;
5715     }
5716     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5717         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5718         && gameMode != AnalyzeFile && gameMode != Training) {
5719         DisplayMoveError(_("Displayed position is not current"));
5720         return FALSE;
5721     }
5722     return TRUE;
5723 }
5724
5725 Boolean
5726 OnlyMove(int *x, int *y, Boolean captures) {
5727     DisambiguateClosure cl;
5728     if (appData.zippyPlay) return FALSE;
5729     switch(gameMode) {
5730       case MachinePlaysBlack:
5731       case IcsPlayingWhite:
5732       case BeginningOfGame:
5733         if(!WhiteOnMove(currentMove)) return FALSE;
5734         break;
5735       case MachinePlaysWhite:
5736       case IcsPlayingBlack:
5737         if(WhiteOnMove(currentMove)) return FALSE;
5738         break;
5739       default:
5740         return FALSE;
5741     }
5742     cl.pieceIn = EmptySquare; 
5743     cl.rfIn = *y;
5744     cl.ffIn = *x;
5745     cl.rtIn = -1;
5746     cl.ftIn = -1;
5747     cl.promoCharIn = NULLCHAR;
5748     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5749     if( cl.kind == NormalMove ||
5750         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5751         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5752         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5753         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5754       fromX = cl.ff;
5755       fromY = cl.rf;
5756       *x = cl.ft;
5757       *y = cl.rt;
5758       return TRUE;
5759     }
5760     if(cl.kind != ImpossibleMove) return FALSE;
5761     cl.pieceIn = EmptySquare;
5762     cl.rfIn = -1;
5763     cl.ffIn = -1;
5764     cl.rtIn = *y;
5765     cl.ftIn = *x;
5766     cl.promoCharIn = NULLCHAR;
5767     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5768     if( cl.kind == NormalMove ||
5769         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5770         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5771         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5772         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5773       fromX = cl.ff;
5774       fromY = cl.rf;
5775       *x = cl.ft;
5776       *y = cl.rt;
5777       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5778       return TRUE;
5779     }
5780     return FALSE;
5781 }
5782
5783 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5784 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5785 int lastLoadGameUseList = FALSE;
5786 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5787 ChessMove lastLoadGameStart = (ChessMove) 0;
5788
5789 ChessMove
5790 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5791      int fromX, fromY, toX, toY;
5792      int promoChar;
5793      Boolean captureOwn;
5794 {
5795     ChessMove moveType;
5796     ChessSquare pdown, pup;
5797
5798     /* Check if the user is playing in turn.  This is complicated because we
5799        let the user "pick up" a piece before it is his turn.  So the piece he
5800        tried to pick up may have been captured by the time he puts it down!
5801        Therefore we use the color the user is supposed to be playing in this
5802        test, not the color of the piece that is currently on the starting
5803        square---except in EditGame mode, where the user is playing both
5804        sides; fortunately there the capture race can't happen.  (It can
5805        now happen in IcsExamining mode, but that's just too bad.  The user
5806        will get a somewhat confusing message in that case.)
5807        */
5808
5809     switch (gameMode) {
5810       case PlayFromGameFile:
5811       case AnalyzeFile:
5812       case TwoMachinesPlay:
5813       case EndOfGame:
5814       case IcsObserving:
5815       case IcsIdle:
5816         /* We switched into a game mode where moves are not accepted,
5817            perhaps while the mouse button was down. */
5818         return ImpossibleMove;
5819
5820       case MachinePlaysWhite:
5821         /* User is moving for Black */
5822         if (WhiteOnMove(currentMove)) {
5823             DisplayMoveError(_("It is White's turn"));
5824             return ImpossibleMove;
5825         }
5826         break;
5827
5828       case MachinePlaysBlack:
5829         /* User is moving for White */
5830         if (!WhiteOnMove(currentMove)) {
5831             DisplayMoveError(_("It is Black's turn"));
5832             return ImpossibleMove;
5833         }
5834         break;
5835
5836       case EditGame:
5837       case IcsExamining:
5838       case BeginningOfGame:
5839       case AnalyzeMode:
5840       case Training:
5841         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5842             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5843             /* User is moving for Black */
5844             if (WhiteOnMove(currentMove)) {
5845                 DisplayMoveError(_("It is White's turn"));
5846                 return ImpossibleMove;
5847             }
5848         } else {
5849             /* User is moving for White */
5850             if (!WhiteOnMove(currentMove)) {
5851                 DisplayMoveError(_("It is Black's turn"));
5852                 return ImpossibleMove;
5853             }
5854         }
5855         break;
5856
5857       case IcsPlayingBlack:
5858         /* User is moving for Black */
5859         if (WhiteOnMove(currentMove)) {
5860             if (!appData.premove) {
5861                 DisplayMoveError(_("It is White's turn"));
5862             } else if (toX >= 0 && toY >= 0) {
5863                 premoveToX = toX;
5864                 premoveToY = toY;
5865                 premoveFromX = fromX;
5866                 premoveFromY = fromY;
5867                 premovePromoChar = promoChar;
5868                 gotPremove = 1;
5869                 if (appData.debugMode) 
5870                     fprintf(debugFP, "Got premove: fromX %d,"
5871                             "fromY %d, toX %d, toY %d\n",
5872                             fromX, fromY, toX, toY);
5873             }
5874             return ImpossibleMove;
5875         }
5876         break;
5877
5878       case IcsPlayingWhite:
5879         /* User is moving for White */
5880         if (!WhiteOnMove(currentMove)) {
5881             if (!appData.premove) {
5882                 DisplayMoveError(_("It is Black's turn"));
5883             } else if (toX >= 0 && toY >= 0) {
5884                 premoveToX = toX;
5885                 premoveToY = toY;
5886                 premoveFromX = fromX;
5887                 premoveFromY = fromY;
5888                 premovePromoChar = promoChar;
5889                 gotPremove = 1;
5890                 if (appData.debugMode) 
5891                     fprintf(debugFP, "Got premove: fromX %d,"
5892                             "fromY %d, toX %d, toY %d\n",
5893                             fromX, fromY, toX, toY);
5894             }
5895             return ImpossibleMove;
5896         }
5897         break;
5898
5899       default:
5900         break;
5901
5902       case EditPosition:
5903         /* EditPosition, empty square, or different color piece;
5904            click-click move is possible */
5905         if (toX == -2 || toY == -2) {
5906             boards[0][fromY][fromX] = EmptySquare;
5907             return AmbiguousMove;
5908         } else if (toX >= 0 && toY >= 0) {
5909             boards[0][toY][toX] = boards[0][fromY][fromX];
5910             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5911                 if(boards[0][fromY][0] != EmptySquare) {
5912                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5913                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5914                 }
5915             } else
5916             if(fromX == BOARD_RGHT+1) {
5917                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5918                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5919                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5920                 }
5921             } else
5922             boards[0][fromY][fromX] = EmptySquare;
5923             return AmbiguousMove;
5924         }
5925         return ImpossibleMove;
5926     }
5927
5928     if(toX < 0 || toY < 0) return ImpossibleMove;
5929     pdown = boards[currentMove][fromY][fromX];
5930     pup = boards[currentMove][toY][toX];
5931
5932     /* [HGM] If move started in holdings, it means a drop */
5933     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5934          if( pup != EmptySquare ) return ImpossibleMove;
5935          if(appData.testLegality) {
5936              /* it would be more logical if LegalityTest() also figured out
5937               * which drops are legal. For now we forbid pawns on back rank.
5938               * Shogi is on its own here...
5939               */
5940              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5941                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5942                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5943          }
5944          return WhiteDrop; /* Not needed to specify white or black yet */
5945     }
5946
5947     /* [HGM] always test for legality, to get promotion info */
5948     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5949                                          fromY, fromX, toY, toX, promoChar);
5950     /* [HGM] but possibly ignore an IllegalMove result */
5951     if (appData.testLegality) {
5952         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5953             DisplayMoveError(_("Illegal move"));
5954             return ImpossibleMove;
5955         }
5956     }
5957
5958     return moveType;
5959     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5960        function is made into one that returns an OK move type if FinishMove
5961        should be called. This to give the calling driver routine the
5962        opportunity to finish the userMove input with a promotion popup,
5963        without bothering the user with this for invalid or illegal moves */
5964
5965 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5966 }
5967
5968 /* Common tail of UserMoveEvent and DropMenuEvent */
5969 int
5970 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5971      ChessMove moveType;
5972      int fromX, fromY, toX, toY;
5973      /*char*/int promoChar;
5974 {
5975     char *bookHit = 0;
5976
5977     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5978         // [HGM] superchess: suppress promotions to non-available piece
5979         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5980         if(WhiteOnMove(currentMove)) {
5981             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5982         } else {
5983             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5984         }
5985     }
5986
5987     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5988        move type in caller when we know the move is a legal promotion */
5989     if(moveType == NormalMove && promoChar)
5990         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5991
5992     /* [HGM] convert drag-and-drop piece drops to standard form */
5993     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5994          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5995            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5996                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5997            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5998            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5999            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6000            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6001          fromY = DROP_RANK;
6002     }
6003
6004     /* [HGM] <popupFix> The following if has been moved here from
6005        UserMoveEvent(). Because it seemed to belong here (why not allow
6006        piece drops in training games?), and because it can only be
6007        performed after it is known to what we promote. */
6008     if (gameMode == Training) {
6009       /* compare the move played on the board to the next move in the
6010        * game. If they match, display the move and the opponent's response. 
6011        * If they don't match, display an error message.
6012        */
6013       int saveAnimate;
6014       Board testBoard;
6015       CopyBoard(testBoard, boards[currentMove]);
6016       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6017
6018       if (CompareBoards(testBoard, boards[currentMove+1])) {
6019         ForwardInner(currentMove+1);
6020
6021         /* Autoplay the opponent's response.
6022          * if appData.animate was TRUE when Training mode was entered,
6023          * the response will be animated.
6024          */
6025         saveAnimate = appData.animate;
6026         appData.animate = animateTraining;
6027         ForwardInner(currentMove+1);
6028         appData.animate = saveAnimate;
6029
6030         /* check for the end of the game */
6031         if (currentMove >= forwardMostMove) {
6032           gameMode = PlayFromGameFile;
6033           ModeHighlight();
6034           SetTrainingModeOff();
6035           DisplayInformation(_("End of game"));
6036         }
6037       } else {
6038         DisplayError(_("Incorrect move"), 0);
6039       }
6040       return 1;
6041     }
6042
6043   /* Ok, now we know that the move is good, so we can kill
6044      the previous line in Analysis Mode */
6045   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6046                                 && currentMove < forwardMostMove) {
6047     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6048   }
6049
6050   /* If we need the chess program but it's dead, restart it */
6051   ResurrectChessProgram();
6052
6053   /* A user move restarts a paused game*/
6054   if (pausing)
6055     PauseEvent();
6056
6057   thinkOutput[0] = NULLCHAR;
6058
6059   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6060
6061   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6062
6063   if (gameMode == BeginningOfGame) {
6064     if (appData.noChessProgram) {
6065       gameMode = EditGame;
6066       SetGameInfo();
6067     } else {
6068       char buf[MSG_SIZ];
6069       gameMode = MachinePlaysBlack;
6070       StartClocks();
6071       SetGameInfo();
6072       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6073       DisplayTitle(buf);
6074       if (first.sendName) {
6075         sprintf(buf, "name %s\n", gameInfo.white);
6076         SendToProgram(buf, &first);
6077       }
6078       StartClocks();
6079     }
6080     ModeHighlight();
6081   }
6082
6083   /* Relay move to ICS or chess engine */
6084   if (appData.icsActive) {
6085     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6086         gameMode == IcsExamining) {
6087       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6088         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6089         SendToICS("draw ");
6090         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6091       }
6092       // also send plain move, in case ICS does not understand atomic claims
6093       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6094       ics_user_moved = 1;
6095     }
6096   } else {
6097     if (first.sendTime && (gameMode == BeginningOfGame ||
6098                            gameMode == MachinePlaysWhite ||
6099                            gameMode == MachinePlaysBlack)) {
6100       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6101     }
6102     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6103          // [HGM] book: if program might be playing, let it use book
6104         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6105         first.maybeThinking = TRUE;
6106     } else SendMoveToProgram(forwardMostMove-1, &first);
6107     if (currentMove == cmailOldMove + 1) {
6108       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6109     }
6110   }
6111
6112   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6113
6114   switch (gameMode) {
6115   case EditGame:
6116     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6117     case MT_NONE:
6118     case MT_CHECK:
6119       break;
6120     case MT_CHECKMATE:
6121     case MT_STAINMATE:
6122       if (WhiteOnMove(currentMove)) {
6123         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6124       } else {
6125         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6126       }
6127       break;
6128     case MT_STALEMATE:
6129       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6130       break;
6131     }
6132     break;
6133     
6134   case MachinePlaysBlack:
6135   case MachinePlaysWhite:
6136     /* disable certain menu options while machine is thinking */
6137     SetMachineThinkingEnables();
6138     break;
6139
6140   default:
6141     break;
6142   }
6143
6144   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6145         
6146   if(bookHit) { // [HGM] book: simulate book reply
6147         static char bookMove[MSG_SIZ]; // a bit generous?
6148
6149         programStats.nodes = programStats.depth = programStats.time = 
6150         programStats.score = programStats.got_only_move = 0;
6151         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6152
6153         strcpy(bookMove, "move ");
6154         strcat(bookMove, bookHit);
6155         HandleMachineMove(bookMove, &first);
6156   }
6157   return 1;
6158 }
6159
6160 void
6161 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6162      int fromX, fromY, toX, toY;
6163      int promoChar;
6164 {
6165     /* [HGM] This routine was added to allow calling of its two logical
6166        parts from other modules in the old way. Before, UserMoveEvent()
6167        automatically called FinishMove() if the move was OK, and returned
6168        otherwise. I separated the two, in order to make it possible to
6169        slip a promotion popup in between. But that it always needs two
6170        calls, to the first part, (now called UserMoveTest() ), and to
6171        FinishMove if the first part succeeded. Calls that do not need
6172        to do anything in between, can call this routine the old way. 
6173     */
6174     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6175 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6176     if(moveType == AmbiguousMove)
6177         DrawPosition(FALSE, boards[currentMove]);
6178     else if(moveType != ImpossibleMove && moveType != Comment)
6179         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6180 }
6181
6182 void
6183 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6184      Board board;
6185      int flags;
6186      ChessMove kind;
6187      int rf, ff, rt, ft;
6188      VOIDSTAR closure;
6189 {
6190     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6191     Markers *m = (Markers *) closure;
6192     if(rf == fromY && ff == fromX)
6193         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6194                          || kind == WhiteCapturesEnPassant
6195                          || kind == BlackCapturesEnPassant);
6196     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6197 }
6198
6199 void
6200 MarkTargetSquares(int clear)
6201 {
6202   int x, y;
6203   if(!appData.markers || !appData.highlightDragging || 
6204      !appData.testLegality || gameMode == EditPosition) return;
6205   if(clear) {
6206     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6207   } else {
6208     int capt = 0;
6209     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6210     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6211       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6212       if(capt)
6213       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6214     }
6215   }
6216   DrawPosition(TRUE, NULL);
6217 }
6218
6219 void LeftClick(ClickType clickType, int xPix, int yPix)
6220 {
6221     int x, y;
6222     Boolean saveAnimate;
6223     static int second = 0, promotionChoice = 0;
6224     char promoChoice = NULLCHAR;
6225
6226     if(appData.seekGraph && appData.icsActive && loggedOn &&
6227         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6228         SeekGraphClick(clickType, xPix, yPix, 0);
6229         return;
6230     }
6231
6232     if (clickType == Press) ErrorPopDown();
6233     MarkTargetSquares(1);
6234
6235     x = EventToSquare(xPix, BOARD_WIDTH);
6236     y = EventToSquare(yPix, BOARD_HEIGHT);
6237     if (!flipView && y >= 0) {
6238         y = BOARD_HEIGHT - 1 - y;
6239     }
6240     if (flipView && x >= 0) {
6241         x = BOARD_WIDTH - 1 - x;
6242     }
6243
6244     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6245         if(clickType == Release) return; // ignore upclick of click-click destination
6246         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6247         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6248         if(gameInfo.holdingsWidth && 
6249                 (WhiteOnMove(currentMove) 
6250                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6251                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6252             // click in right holdings, for determining promotion piece
6253             ChessSquare p = boards[currentMove][y][x];
6254             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6255             if(p != EmptySquare) {
6256                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6257                 fromX = fromY = -1;
6258                 return;
6259             }
6260         }
6261         DrawPosition(FALSE, boards[currentMove]);
6262         return;
6263     }
6264
6265     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6266     if(clickType == Press
6267             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6268               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6269               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6270         return;
6271
6272     autoQueen = appData.alwaysPromoteToQueen;
6273
6274     if (fromX == -1) {
6275       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6276         if (clickType == Press) {
6277             /* First square */
6278             if (OKToStartUserMove(x, y)) {
6279                 fromX = x;
6280                 fromY = y;
6281                 second = 0;
6282                 MarkTargetSquares(0);
6283                 DragPieceBegin(xPix, yPix);
6284                 if (appData.highlightDragging) {
6285                     SetHighlights(x, y, -1, -1);
6286                 }
6287             }
6288         }
6289         return;
6290       }
6291     }
6292
6293     /* fromX != -1 */
6294     if (clickType == Press && gameMode != EditPosition) {
6295         ChessSquare fromP;
6296         ChessSquare toP;
6297         int frc;
6298
6299         // ignore off-board to clicks
6300         if(y < 0 || x < 0) return;
6301
6302         /* Check if clicking again on the same color piece */
6303         fromP = boards[currentMove][fromY][fromX];
6304         toP = boards[currentMove][y][x];
6305         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6306         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6307              WhitePawn <= toP && toP <= WhiteKing &&
6308              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6309              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6310             (BlackPawn <= fromP && fromP <= BlackKing && 
6311              BlackPawn <= toP && toP <= BlackKing &&
6312              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6313              !(fromP == BlackKing && toP == BlackRook && frc))) {
6314             /* Clicked again on same color piece -- changed his mind */
6315             second = (x == fromX && y == fromY);
6316            if(!second || !OnlyMove(&x, &y, TRUE)) {
6317             if (appData.highlightDragging) {
6318                 SetHighlights(x, y, -1, -1);
6319             } else {
6320                 ClearHighlights();
6321             }
6322             if (OKToStartUserMove(x, y)) {
6323                 fromX = x;
6324                 fromY = y;
6325                 MarkTargetSquares(0);
6326                 DragPieceBegin(xPix, yPix);
6327             }
6328             return;
6329            }
6330         }
6331         // ignore clicks on holdings
6332         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6333     }
6334
6335     if (clickType == Release && x == fromX && y == fromY) {
6336         DragPieceEnd(xPix, yPix);
6337         if (appData.animateDragging) {
6338             /* Undo animation damage if any */
6339             DrawPosition(FALSE, NULL);
6340         }
6341         if (second) {
6342             /* Second up/down in same square; just abort move */
6343             second = 0;
6344             fromX = fromY = -1;
6345             ClearHighlights();
6346             gotPremove = 0;
6347             ClearPremoveHighlights();
6348         } else {
6349             /* First upclick in same square; start click-click mode */
6350             SetHighlights(x, y, -1, -1);
6351         }
6352         return;
6353     }
6354
6355     /* we now have a different from- and (possibly off-board) to-square */
6356     /* Completed move */
6357     toX = x;
6358     toY = y;
6359     saveAnimate = appData.animate;
6360     if (clickType == Press) {
6361         /* Finish clickclick move */
6362         if (appData.animate || appData.highlightLastMove) {
6363             SetHighlights(fromX, fromY, toX, toY);
6364         } else {
6365             ClearHighlights();
6366         }
6367     } else {
6368         /* Finish drag move */
6369         if (appData.highlightLastMove) {
6370             SetHighlights(fromX, fromY, toX, toY);
6371         } else {
6372             ClearHighlights();
6373         }
6374         DragPieceEnd(xPix, yPix);
6375         /* Don't animate move and drag both */
6376         appData.animate = FALSE;
6377     }
6378
6379     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6380     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6381         ChessSquare piece = boards[currentMove][fromY][fromX];
6382         if(gameMode == EditPosition && piece != EmptySquare &&
6383            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6384             int n;
6385              
6386             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6387                 n = PieceToNumber(piece - (int)BlackPawn);
6388                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6389                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6390                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6391             } else
6392             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6393                 n = PieceToNumber(piece);
6394                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6395                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6396                 boards[currentMove][n][BOARD_WIDTH-2]++;
6397             }
6398             boards[currentMove][fromY][fromX] = EmptySquare;
6399         }
6400         ClearHighlights();
6401         fromX = fromY = -1;
6402         DrawPosition(TRUE, boards[currentMove]);
6403         return;
6404     }
6405
6406     // off-board moves should not be highlighted
6407     if(x < 0 || x < 0) ClearHighlights();
6408
6409     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6410         SetHighlights(fromX, fromY, toX, toY);
6411         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6412             // [HGM] super: promotion to captured piece selected from holdings
6413             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6414             promotionChoice = TRUE;
6415             // kludge follows to temporarily execute move on display, without promoting yet
6416             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6417             boards[currentMove][toY][toX] = p;
6418             DrawPosition(FALSE, boards[currentMove]);
6419             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6420             boards[currentMove][toY][toX] = q;
6421             DisplayMessage("Click in holdings to choose piece", "");
6422             return;
6423         }
6424         PromotionPopUp();
6425     } else {
6426         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6427         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6428         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6429         fromX = fromY = -1;
6430     }
6431     appData.animate = saveAnimate;
6432     if (appData.animate || appData.animateDragging) {
6433         /* Undo animation damage if needed */
6434         DrawPosition(FALSE, NULL);
6435     }
6436 }
6437
6438 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6439 {   // front-end-free part taken out of PieceMenuPopup
6440     int whichMenu; int xSqr, ySqr;
6441
6442     if(seekGraphUp) { // [HGM] seekgraph
6443         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6444         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6445         return -2;
6446     }
6447
6448     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6449          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6450         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6451         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6452         if(action == Press)   {
6453             originalFlip = flipView;
6454             flipView = !flipView; // temporarily flip board to see game from partners perspective
6455             DrawPosition(TRUE, partnerBoard);
6456             DisplayMessage(partnerStatus, "");
6457             partnerUp = TRUE;
6458         } else if(action == Release) {
6459             flipView = originalFlip;
6460             DrawPosition(TRUE, boards[currentMove]);
6461             partnerUp = FALSE;
6462         }
6463         return -2;
6464     }
6465
6466     xSqr = EventToSquare(x, BOARD_WIDTH);
6467     ySqr = EventToSquare(y, BOARD_HEIGHT);
6468     if (action == Release) UnLoadPV(); // [HGM] pv
6469     if (action != Press) return -2; // return code to be ignored
6470     switch (gameMode) {
6471       case IcsExamining:
6472         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6473       case EditPosition:
6474         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6475         if (xSqr < 0 || ySqr < 0) return -1;\r
6476         whichMenu = 0; // edit-position menu
6477         break;
6478       case IcsObserving:
6479         if(!appData.icsEngineAnalyze) return -1;
6480       case IcsPlayingWhite:
6481       case IcsPlayingBlack:
6482         if(!appData.zippyPlay) goto noZip;
6483       case AnalyzeMode:
6484       case AnalyzeFile:
6485       case MachinePlaysWhite:
6486       case MachinePlaysBlack:
6487       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6488         if (!appData.dropMenu) {
6489           LoadPV(x, y);
6490           return 2; // flag front-end to grab mouse events
6491         }
6492         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6493            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6494       case EditGame:
6495       noZip:
6496         if (xSqr < 0 || ySqr < 0) return -1;
6497         if (!appData.dropMenu || appData.testLegality &&
6498             gameInfo.variant != VariantBughouse &&
6499             gameInfo.variant != VariantCrazyhouse) return -1;
6500         whichMenu = 1; // drop menu
6501         break;
6502       default:
6503         return -1;
6504     }
6505
6506     if (((*fromX = xSqr) < 0) ||
6507         ((*fromY = ySqr) < 0)) {
6508         *fromX = *fromY = -1;
6509         return -1;
6510     }
6511     if (flipView)
6512       *fromX = BOARD_WIDTH - 1 - *fromX;
6513     else
6514       *fromY = BOARD_HEIGHT - 1 - *fromY;
6515
6516     return whichMenu;
6517 }
6518
6519 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6520 {
6521 //    char * hint = lastHint;
6522     FrontEndProgramStats stats;
6523
6524     stats.which = cps == &first ? 0 : 1;
6525     stats.depth = cpstats->depth;
6526     stats.nodes = cpstats->nodes;
6527     stats.score = cpstats->score;
6528     stats.time = cpstats->time;
6529     stats.pv = cpstats->movelist;
6530     stats.hint = lastHint;
6531     stats.an_move_index = 0;
6532     stats.an_move_count = 0;
6533
6534     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6535         stats.hint = cpstats->move_name;
6536         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6537         stats.an_move_count = cpstats->nr_moves;
6538     }
6539
6540     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6541
6542     SetProgramStats( &stats );
6543 }
6544
6545 int
6546 Adjudicate(ChessProgramState *cps)
6547 {       // [HGM] some adjudications useful with buggy engines
6548         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6549         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6550         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6551         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6552         int k, count = 0; static int bare = 1;
6553         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6554         Boolean canAdjudicate = !appData.icsActive;
6555
6556         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6557         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6558             if( appData.testLegality )
6559             {   /* [HGM] Some more adjudications for obstinate engines */
6560                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6561                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6562                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6563                 static int moveCount = 6;
6564                 ChessMove result;
6565                 char *reason = NULL;
6566
6567                 /* Count what is on board. */
6568                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6569                 {   ChessSquare p = boards[forwardMostMove][i][j];
6570                     int m=i;
6571
6572                     switch((int) p)
6573                     {   /* count B,N,R and other of each side */
6574                         case WhiteKing:
6575                         case BlackKing:
6576                              NrK++; break; // [HGM] atomic: count Kings
6577                         case WhiteKnight:
6578                              NrWN++; break;
6579                         case WhiteBishop:
6580                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6581                              bishopsColor |= 1 << ((i^j)&1);
6582                              NrWB++; break;
6583                         case BlackKnight:
6584                              NrBN++; break;
6585                         case BlackBishop:
6586                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6587                              bishopsColor |= 1 << ((i^j)&1);
6588                              NrBB++; break;
6589                         case WhiteRook:
6590                              NrWR++; break;
6591                         case BlackRook:
6592                              NrBR++; break;
6593                         case WhiteQueen:
6594                              NrWQ++; break;
6595                         case BlackQueen:
6596                              NrBQ++; break;
6597                         case EmptySquare: 
6598                              break;
6599                         case BlackPawn:
6600                              m = 7-i;
6601                         case WhitePawn:
6602                              PawnAdvance += m; NrPawns++;
6603                     }
6604                     NrPieces += (p != EmptySquare);
6605                     NrW += ((int)p < (int)BlackPawn);
6606                     if(gameInfo.variant == VariantXiangqi && 
6607                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6608                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6609                         NrW -= ((int)p < (int)BlackPawn);
6610                     }
6611                 }
6612
6613                 /* Some material-based adjudications that have to be made before stalemate test */
6614                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6615                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6616                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6617                      if(canAdjudicate && appData.checkMates) {
6618                          if(engineOpponent)
6619                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6620                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6621                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6622                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6623                          return 1;
6624                      }
6625                 }
6626
6627                 /* Bare King in Shatranj (loses) or Losers (wins) */
6628                 if( NrW == 1 || NrPieces - NrW == 1) {
6629                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6630                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6631                      if(canAdjudicate && appData.checkMates) {
6632                          if(engineOpponent)
6633                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6634                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6635                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6636                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6637                          return 1;
6638                      }
6639                   } else
6640                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6641                   {    /* bare King */
6642                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6643                         if(canAdjudicate && appData.checkMates) {
6644                             /* but only adjudicate if adjudication enabled */
6645                             if(engineOpponent)
6646                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6647                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6649                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6650                             return 1;
6651                         }
6652                   }
6653                 } else bare = 1;
6654
6655
6656             // don't wait for engine to announce game end if we can judge ourselves
6657             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6658               case MT_CHECK:
6659                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6660                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6661                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6662                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6663                             checkCnt++;
6664                         if(checkCnt >= 2) {
6665                             reason = "Xboard adjudication: 3rd check";
6666                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6667                             break;
6668                         }
6669                     }
6670                 }
6671               case MT_NONE:
6672               default:
6673                 break;
6674               case MT_STALEMATE:
6675               case MT_STAINMATE:
6676                 reason = "Xboard adjudication: Stalemate";
6677                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6678                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6679                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6680                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6681                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6682                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6683                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6684                                                                         EP_CHECKMATE : EP_WINS);
6685                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6686                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6687                 }
6688                 break;
6689               case MT_CHECKMATE:
6690                 reason = "Xboard adjudication: Checkmate";
6691                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6692                 break;
6693             }
6694
6695                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6696                     case EP_STALEMATE:
6697                         result = GameIsDrawn; break;
6698                     case EP_CHECKMATE:
6699                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6700                     case EP_WINS:
6701                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6702                     default:
6703                         result = (ChessMove) 0;
6704                 }
6705                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6706                     if(engineOpponent)
6707                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6708                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6709                     GameEnds( result, reason, GE_XBOARD );
6710                     return 1;
6711                 }
6712
6713                 /* Next absolutely insufficient mating material. */
6714                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6715                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6716                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6717                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6718                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6719
6720                      /* always flag draws, for judging claims */
6721                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6722
6723                      if(canAdjudicate && appData.materialDraws) {
6724                          /* but only adjudicate them if adjudication enabled */
6725                          if(engineOpponent) {
6726                            SendToProgram("force\n", engineOpponent); // suppress reply
6727                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6728                          }
6729                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6730                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6731                          return 1;
6732                      }
6733                 }
6734
6735                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6736                 if(NrPieces == 4 && 
6737                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6738                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6739                    || NrWN==2 || NrBN==2     /* KNNK */
6740                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6741                   ) ) {
6742                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6743                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6744                           if(engineOpponent) {
6745                             SendToProgram("force\n", engineOpponent); // suppress reply
6746                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6747                           }
6748                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6749                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6750                           return 1;
6751                      }
6752                 } else moveCount = 6;
6753             }
6754         }
6755           
6756         if (appData.debugMode) { int i;
6757             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6758                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6759                     appData.drawRepeats);
6760             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6761               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6762             
6763         }
6764
6765         // Repetition draws and 50-move rule can be applied independently of legality testing
6766
6767                 /* Check for rep-draws */
6768                 count = 0;
6769                 for(k = forwardMostMove-2;
6770                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6771                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6772                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6773                     k-=2)
6774                 {   int rights=0;
6775                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6776                         /* compare castling rights */
6777                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6778                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6779                                 rights++; /* King lost rights, while rook still had them */
6780                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6781                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6782                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6783                                    rights++; /* but at least one rook lost them */
6784                         }
6785                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6786                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6787                                 rights++; 
6788                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6789                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6790                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6791                                    rights++;
6792                         }
6793                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6794                             && appData.drawRepeats > 1) {
6795                              /* adjudicate after user-specified nr of repeats */
6796                              if(engineOpponent) {
6797                                SendToProgram("force\n", engineOpponent); // suppress reply
6798                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6799                              }
6800                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6801                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6802                                 // [HGM] xiangqi: check for forbidden perpetuals
6803                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6804                                 for(m=forwardMostMove; m>k; m-=2) {
6805                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6806                                         ourPerpetual = 0; // the current mover did not always check
6807                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6808                                         hisPerpetual = 0; // the opponent did not always check
6809                                 }
6810                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6811                                                                         ourPerpetual, hisPerpetual);
6812                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6813                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6814                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6815                                     return 1;
6816                                 }
6817                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6818                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6819                                 // Now check for perpetual chases
6820                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6821                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6822                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6823                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6824                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6825                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6826                                         return 1;
6827                                     }
6828                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6829                                         break; // Abort repetition-checking loop.
6830                                 }
6831                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6832                              }
6833                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6834                              return 1;
6835                         }
6836                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6837                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6838                     }
6839                 }
6840
6841                 /* Now we test for 50-move draws. Determine ply count */
6842                 count = forwardMostMove;
6843                 /* look for last irreversble move */
6844                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6845                     count--;
6846                 /* if we hit starting position, add initial plies */
6847                 if( count == backwardMostMove )
6848                     count -= initialRulePlies;
6849                 count = forwardMostMove - count; 
6850                 if( count >= 100)
6851                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6852                          /* this is used to judge if draw claims are legal */
6853                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6854                          if(engineOpponent) {
6855                            SendToProgram("force\n", engineOpponent); // suppress reply
6856                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6857                          }
6858                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6859                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6860                          return 1;
6861                 }
6862
6863                 /* if draw offer is pending, treat it as a draw claim
6864                  * when draw condition present, to allow engines a way to
6865                  * claim draws before making their move to avoid a race
6866                  * condition occurring after their move
6867                  */
6868                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6869                          char *p = NULL;
6870                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6871                              p = "Draw claim: 50-move rule";
6872                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6873                              p = "Draw claim: 3-fold repetition";
6874                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6875                              p = "Draw claim: insufficient mating material";
6876                          if( p != NULL && canAdjudicate) {
6877                              if(engineOpponent) {
6878                                SendToProgram("force\n", engineOpponent); // suppress reply
6879                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6880                              }
6881                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6883                              return 1;
6884                          }
6885                 }
6886
6887                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6888                     if(engineOpponent) {
6889                       SendToProgram("force\n", engineOpponent); // suppress reply
6890                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6891                     }
6892                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6893                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6894                     return 1;
6895                 }
6896         return 0;
6897 }
6898
6899 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6900 {   // [HGM] book: this routine intercepts moves to simulate book replies
6901     char *bookHit = NULL;
6902
6903     //first determine if the incoming move brings opponent into his book
6904     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6905         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6906     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6907     if(bookHit != NULL && !cps->bookSuspend) {
6908         // make sure opponent is not going to reply after receiving move to book position
6909         SendToProgram("force\n", cps);
6910         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6911     }
6912     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6913     // now arrange restart after book miss
6914     if(bookHit) {
6915         // after a book hit we never send 'go', and the code after the call to this routine
6916         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6917         char buf[MSG_SIZ];
6918         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6919         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6920         SendToProgram(buf, cps);
6921         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6922     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6923         SendToProgram("go\n", cps);
6924         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6925     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6926         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6927             SendToProgram("go\n", cps); 
6928         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6929     }
6930     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6931 }
6932
6933 char *savedMessage;
6934 ChessProgramState *savedState;
6935 void DeferredBookMove(void)
6936 {
6937         if(savedState->lastPing != savedState->lastPong)
6938                     ScheduleDelayedEvent(DeferredBookMove, 10);
6939         else
6940         HandleMachineMove(savedMessage, savedState);
6941 }
6942
6943 void
6944 HandleMachineMove(message, cps)
6945      char *message;
6946      ChessProgramState *cps;
6947 {
6948     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6949     char realname[MSG_SIZ];
6950     int fromX, fromY, toX, toY;
6951     ChessMove moveType;
6952     char promoChar;
6953     char *p;
6954     int machineWhite;
6955     char *bookHit;
6956
6957     cps->userError = 0;
6958
6959 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6960     /*
6961      * Kludge to ignore BEL characters
6962      */
6963     while (*message == '\007') message++;
6964
6965     /*
6966      * [HGM] engine debug message: ignore lines starting with '#' character
6967      */
6968     if(cps->debug && *message == '#') return;
6969
6970     /*
6971      * Look for book output
6972      */
6973     if (cps == &first && bookRequested) {
6974         if (message[0] == '\t' || message[0] == ' ') {
6975             /* Part of the book output is here; append it */
6976             strcat(bookOutput, message);
6977             strcat(bookOutput, "  \n");
6978             return;
6979         } else if (bookOutput[0] != NULLCHAR) {
6980             /* All of book output has arrived; display it */
6981             char *p = bookOutput;
6982             while (*p != NULLCHAR) {
6983                 if (*p == '\t') *p = ' ';
6984                 p++;
6985             }
6986             DisplayInformation(bookOutput);
6987             bookRequested = FALSE;
6988             /* Fall through to parse the current output */
6989         }
6990     }
6991
6992     /*
6993      * Look for machine move.
6994      */
6995     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6996         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6997     {
6998         /* This method is only useful on engines that support ping */
6999         if (cps->lastPing != cps->lastPong) {
7000           if (gameMode == BeginningOfGame) {
7001             /* Extra move from before last new; ignore */
7002             if (appData.debugMode) {
7003                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7004             }
7005           } else {
7006             if (appData.debugMode) {
7007                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7008                         cps->which, gameMode);
7009             }
7010
7011             SendToProgram("undo\n", cps);
7012           }
7013           return;
7014         }
7015
7016         switch (gameMode) {
7017           case BeginningOfGame:
7018             /* Extra move from before last reset; ignore */
7019             if (appData.debugMode) {
7020                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7021             }
7022             return;
7023
7024           case EndOfGame:
7025           case IcsIdle:
7026           default:
7027             /* Extra move after we tried to stop.  The mode test is
7028                not a reliable way of detecting this problem, but it's
7029                the best we can do on engines that don't support ping.
7030             */
7031             if (appData.debugMode) {
7032                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7033                         cps->which, gameMode);
7034             }
7035             SendToProgram("undo\n", cps);
7036             return;
7037
7038           case MachinePlaysWhite:
7039           case IcsPlayingWhite:
7040             machineWhite = TRUE;
7041             break;
7042
7043           case MachinePlaysBlack:
7044           case IcsPlayingBlack:
7045             machineWhite = FALSE;
7046             break;
7047
7048           case TwoMachinesPlay:
7049             machineWhite = (cps->twoMachinesColor[0] == 'w');
7050             break;
7051         }
7052         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7053             if (appData.debugMode) {
7054                 fprintf(debugFP,
7055                         "Ignoring move out of turn by %s, gameMode %d"
7056                         ", forwardMost %d\n",
7057                         cps->which, gameMode, forwardMostMove);
7058             }
7059             return;
7060         }
7061
7062     if (appData.debugMode) { int f = forwardMostMove;
7063         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7064                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7065                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7066     }
7067         if(cps->alphaRank) AlphaRank(machineMove, 4);
7068         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7069                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7070             /* Machine move could not be parsed; ignore it. */
7071             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7072                     machineMove, cps->which);
7073             DisplayError(buf1, 0);
7074             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7075                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7076             if (gameMode == TwoMachinesPlay) {
7077               GameEnds(machineWhite ? BlackWins : WhiteWins,
7078                        buf1, GE_XBOARD);
7079             }
7080             return;
7081         }
7082
7083         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7084         /* So we have to redo legality test with true e.p. status here,  */
7085         /* to make sure an illegal e.p. capture does not slip through,   */
7086         /* to cause a forfeit on a justified illegal-move complaint      */
7087         /* of the opponent.                                              */
7088         if( gameMode==TwoMachinesPlay && appData.testLegality
7089             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7090                                                               ) {
7091            ChessMove moveType;
7092            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7093                              fromY, fromX, toY, toX, promoChar);
7094             if (appData.debugMode) {
7095                 int i;
7096                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7097                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7098                 fprintf(debugFP, "castling rights\n");
7099             }
7100             if(moveType == IllegalMove) {
7101                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7102                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7103                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7104                            buf1, GE_XBOARD);
7105                 return;
7106            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7107            /* [HGM] Kludge to handle engines that send FRC-style castling
7108               when they shouldn't (like TSCP-Gothic) */
7109            switch(moveType) {
7110              case WhiteASideCastleFR:
7111              case BlackASideCastleFR:
7112                toX+=2;
7113                currentMoveString[2]++;
7114                break;
7115              case WhiteHSideCastleFR:
7116              case BlackHSideCastleFR:
7117                toX--;
7118                currentMoveString[2]--;
7119                break;
7120              default: ; // nothing to do, but suppresses warning of pedantic compilers
7121            }
7122         }
7123         hintRequested = FALSE;
7124         lastHint[0] = NULLCHAR;
7125         bookRequested = FALSE;
7126         /* Program may be pondering now */
7127         cps->maybeThinking = TRUE;
7128         if (cps->sendTime == 2) cps->sendTime = 1;
7129         if (cps->offeredDraw) cps->offeredDraw--;
7130
7131         /* currentMoveString is set as a side-effect of ParseOneMove */
7132         strcpy(machineMove, currentMoveString);
7133         strcat(machineMove, "\n");
7134         strcpy(moveList[forwardMostMove], machineMove);
7135
7136         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7137
7138         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7139         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7140             int count = 0;
7141
7142             while( count < adjudicateLossPlies ) {
7143                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7144
7145                 if( count & 1 ) {
7146                     score = -score; /* Flip score for winning side */
7147                 }
7148
7149                 if( score > adjudicateLossThreshold ) {
7150                     break;
7151                 }
7152
7153                 count++;
7154             }
7155
7156             if( count >= adjudicateLossPlies ) {
7157                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7158
7159                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7160                     "Xboard adjudication", 
7161                     GE_XBOARD );
7162
7163                 return;
7164             }
7165         }
7166
7167         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7168
7169 #if ZIPPY
7170         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7171             first.initDone) {
7172           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7173                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7174                 SendToICS("draw ");
7175                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7176           }
7177           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7178           ics_user_moved = 1;
7179           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7180                 char buf[3*MSG_SIZ];
7181
7182                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7183                         programStats.score / 100.,
7184                         programStats.depth,
7185                         programStats.time / 100.,
7186                         (unsigned int)programStats.nodes,
7187                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7188                         programStats.movelist);
7189                 SendToICS(buf);
7190 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7191           }
7192         }
7193 #endif
7194
7195         /* [AS] Save move info and clear stats for next move */
7196         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7197         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7198         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7199         ClearProgramStats();
7200         thinkOutput[0] = NULLCHAR;
7201         hiddenThinkOutputState = 0;
7202
7203         bookHit = NULL;
7204         if (gameMode == TwoMachinesPlay) {
7205             /* [HGM] relaying draw offers moved to after reception of move */
7206             /* and interpreting offer as claim if it brings draw condition */
7207             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7208                 SendToProgram("draw\n", cps->other);
7209             }
7210             if (cps->other->sendTime) {
7211                 SendTimeRemaining(cps->other,
7212                                   cps->other->twoMachinesColor[0] == 'w');
7213             }
7214             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7215             if (firstMove && !bookHit) {
7216                 firstMove = FALSE;
7217                 if (cps->other->useColors) {
7218                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7219                 }
7220                 SendToProgram("go\n", cps->other);
7221             }
7222             cps->other->maybeThinking = TRUE;
7223         }
7224
7225         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7226         
7227         if (!pausing && appData.ringBellAfterMoves) {
7228             RingBell();
7229         }
7230
7231         /* 
7232          * Reenable menu items that were disabled while
7233          * machine was thinking
7234          */
7235         if (gameMode != TwoMachinesPlay)
7236             SetUserThinkingEnables();
7237
7238         // [HGM] book: after book hit opponent has received move and is now in force mode
7239         // force the book reply into it, and then fake that it outputted this move by jumping
7240         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7241         if(bookHit) {
7242                 static char bookMove[MSG_SIZ]; // a bit generous?
7243
7244                 strcpy(bookMove, "move ");
7245                 strcat(bookMove, bookHit);
7246                 message = bookMove;
7247                 cps = cps->other;
7248                 programStats.nodes = programStats.depth = programStats.time = 
7249                 programStats.score = programStats.got_only_move = 0;
7250                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7251
7252                 if(cps->lastPing != cps->lastPong) {
7253                     savedMessage = message; // args for deferred call
7254                     savedState = cps;
7255                     ScheduleDelayedEvent(DeferredBookMove, 10);
7256                     return;
7257                 }
7258                 goto FakeBookMove;
7259         }
7260
7261         return;
7262     }
7263
7264     /* Set special modes for chess engines.  Later something general
7265      *  could be added here; for now there is just one kludge feature,
7266      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7267      *  when "xboard" is given as an interactive command.
7268      */
7269     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7270         cps->useSigint = FALSE;
7271         cps->useSigterm = FALSE;
7272     }
7273     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7274       ParseFeatures(message+8, cps);
7275       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7276     }
7277
7278     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7279      * want this, I was asked to put it in, and obliged.
7280      */
7281     if (!strncmp(message, "setboard ", 9)) {
7282         Board initial_position;
7283
7284         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7285
7286         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7287             DisplayError(_("Bad FEN received from engine"), 0);
7288             return ;
7289         } else {
7290            Reset(TRUE, FALSE);
7291            CopyBoard(boards[0], initial_position);
7292            initialRulePlies = FENrulePlies;
7293            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7294            else gameMode = MachinePlaysBlack;                 
7295            DrawPosition(FALSE, boards[currentMove]);
7296         }
7297         return;
7298     }
7299
7300     /*
7301      * Look for communication commands
7302      */
7303     if (!strncmp(message, "telluser ", 9)) {
7304         DisplayNote(message + 9);
7305         return;
7306     }
7307     if (!strncmp(message, "tellusererror ", 14)) {
7308         cps->userError = 1;
7309         DisplayError(message + 14, 0);
7310         return;
7311     }
7312     if (!strncmp(message, "tellopponent ", 13)) {
7313       if (appData.icsActive) {
7314         if (loggedOn) {
7315           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7316           SendToICS(buf1);
7317         }
7318       } else {
7319         DisplayNote(message + 13);
7320       }
7321       return;
7322     }
7323     if (!strncmp(message, "tellothers ", 11)) {
7324       if (appData.icsActive) {
7325         if (loggedOn) {
7326           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7327           SendToICS(buf1);
7328         }
7329       }
7330       return;
7331     }
7332     if (!strncmp(message, "tellall ", 8)) {
7333       if (appData.icsActive) {
7334         if (loggedOn) {
7335           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7336           SendToICS(buf1);
7337         }
7338       } else {
7339         DisplayNote(message + 8);
7340       }
7341       return;
7342     }
7343     if (strncmp(message, "warning", 7) == 0) {
7344         /* Undocumented feature, use tellusererror in new code */
7345         DisplayError(message, 0);
7346         return;
7347     }
7348     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7349         strcpy(realname, cps->tidy);
7350         strcat(realname, " query");
7351         AskQuestion(realname, buf2, buf1, cps->pr);
7352         return;
7353     }
7354     /* Commands from the engine directly to ICS.  We don't allow these to be 
7355      *  sent until we are logged on. Crafty kibitzes have been known to 
7356      *  interfere with the login process.
7357      */
7358     if (loggedOn) {
7359         if (!strncmp(message, "tellics ", 8)) {
7360             SendToICS(message + 8);
7361             SendToICS("\n");
7362             return;
7363         }
7364         if (!strncmp(message, "tellicsnoalias ", 15)) {
7365             SendToICS(ics_prefix);
7366             SendToICS(message + 15);
7367             SendToICS("\n");
7368             return;
7369         }
7370         /* The following are for backward compatibility only */
7371         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7372             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7373             SendToICS(ics_prefix);
7374             SendToICS(message);
7375             SendToICS("\n");
7376             return;
7377         }
7378     }
7379     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7380         return;
7381     }
7382     /*
7383      * If the move is illegal, cancel it and redraw the board.
7384      * Also deal with other error cases.  Matching is rather loose
7385      * here to accommodate engines written before the spec.
7386      */
7387     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7388         strncmp(message, "Error", 5) == 0) {
7389         if (StrStr(message, "name") || 
7390             StrStr(message, "rating") || StrStr(message, "?") ||
7391             StrStr(message, "result") || StrStr(message, "board") ||
7392             StrStr(message, "bk") || StrStr(message, "computer") ||
7393             StrStr(message, "variant") || StrStr(message, "hint") ||
7394             StrStr(message, "random") || StrStr(message, "depth") ||
7395             StrStr(message, "accepted")) {
7396             return;
7397         }
7398         if (StrStr(message, "protover")) {
7399           /* Program is responding to input, so it's apparently done
7400              initializing, and this error message indicates it is
7401              protocol version 1.  So we don't need to wait any longer
7402              for it to initialize and send feature commands. */
7403           FeatureDone(cps, 1);
7404           cps->protocolVersion = 1;
7405           return;
7406         }
7407         cps->maybeThinking = FALSE;
7408
7409         if (StrStr(message, "draw")) {
7410             /* Program doesn't have "draw" command */
7411             cps->sendDrawOffers = 0;
7412             return;
7413         }
7414         if (cps->sendTime != 1 &&
7415             (StrStr(message, "time") || StrStr(message, "otim"))) {
7416           /* Program apparently doesn't have "time" or "otim" command */
7417           cps->sendTime = 0;
7418           return;
7419         }
7420         if (StrStr(message, "analyze")) {
7421             cps->analysisSupport = FALSE;
7422             cps->analyzing = FALSE;
7423             Reset(FALSE, TRUE);
7424             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7425             DisplayError(buf2, 0);
7426             return;
7427         }
7428         if (StrStr(message, "(no matching move)st")) {
7429           /* Special kludge for GNU Chess 4 only */
7430           cps->stKludge = TRUE;
7431           SendTimeControl(cps, movesPerSession, timeControl,
7432                           timeIncrement, appData.searchDepth,
7433                           searchTime);
7434           return;
7435         }
7436         if (StrStr(message, "(no matching move)sd")) {
7437           /* Special kludge for GNU Chess 4 only */
7438           cps->sdKludge = TRUE;
7439           SendTimeControl(cps, movesPerSession, timeControl,
7440                           timeIncrement, appData.searchDepth,
7441                           searchTime);
7442           return;
7443         }
7444         if (!StrStr(message, "llegal")) {
7445             return;
7446         }
7447         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7448             gameMode == IcsIdle) return;
7449         if (forwardMostMove <= backwardMostMove) return;
7450         if (pausing) PauseEvent();
7451       if(appData.forceIllegal) {
7452             // [HGM] illegal: machine refused move; force position after move into it
7453           SendToProgram("force\n", cps);
7454           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7455                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7456                 // when black is to move, while there might be nothing on a2 or black
7457                 // might already have the move. So send the board as if white has the move.
7458                 // But first we must change the stm of the engine, as it refused the last move
7459                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7460                 if(WhiteOnMove(forwardMostMove)) {
7461                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7462                     SendBoard(cps, forwardMostMove); // kludgeless board
7463                 } else {
7464                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7465                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7466                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7467                 }
7468           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7469             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7470                  gameMode == TwoMachinesPlay)
7471               SendToProgram("go\n", cps);
7472             return;
7473       } else
7474         if (gameMode == PlayFromGameFile) {
7475             /* Stop reading this game file */
7476             gameMode = EditGame;
7477             ModeHighlight();
7478         }
7479         currentMove = forwardMostMove-1;
7480         DisplayMove(currentMove-1); /* before DisplayMoveError */
7481         SwitchClocks(forwardMostMove-1); // [HGM] race
7482         DisplayBothClocks();
7483         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7484                 parseList[currentMove], cps->which);
7485         DisplayMoveError(buf1);
7486         DrawPosition(FALSE, boards[currentMove]);
7487
7488         /* [HGM] illegal-move claim should forfeit game when Xboard */
7489         /* only passes fully legal moves                            */
7490         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7491             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7492                                 "False illegal-move claim", GE_XBOARD );
7493         }
7494         return;
7495     }
7496     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7497         /* Program has a broken "time" command that
7498            outputs a string not ending in newline.
7499            Don't use it. */
7500         cps->sendTime = 0;
7501     }
7502     
7503     /*
7504      * If chess program startup fails, exit with an error message.
7505      * Attempts to recover here are futile.
7506      */
7507     if ((StrStr(message, "unknown host") != NULL)
7508         || (StrStr(message, "No remote directory") != NULL)
7509         || (StrStr(message, "not found") != NULL)
7510         || (StrStr(message, "No such file") != NULL)
7511         || (StrStr(message, "can't alloc") != NULL)
7512         || (StrStr(message, "Permission denied") != NULL)) {
7513
7514         cps->maybeThinking = FALSE;
7515         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7516                 cps->which, cps->program, cps->host, message);
7517         RemoveInputSource(cps->isr);
7518         DisplayFatalError(buf1, 0, 1);
7519         return;
7520     }
7521     
7522     /* 
7523      * Look for hint output
7524      */
7525     if (sscanf(message, "Hint: %s", buf1) == 1) {
7526         if (cps == &first && hintRequested) {
7527             hintRequested = FALSE;
7528             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7529                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7530                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7531                                     PosFlags(forwardMostMove),
7532                                     fromY, fromX, toY, toX, promoChar, buf1);
7533                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7534                 DisplayInformation(buf2);
7535             } else {
7536                 /* Hint move could not be parsed!? */
7537               snprintf(buf2, sizeof(buf2),
7538                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7539                         buf1, cps->which);
7540                 DisplayError(buf2, 0);
7541             }
7542         } else {
7543             strcpy(lastHint, buf1);
7544         }
7545         return;
7546     }
7547
7548     /*
7549      * Ignore other messages if game is not in progress
7550      */
7551     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7552         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7553
7554     /*
7555      * look for win, lose, draw, or draw offer
7556      */
7557     if (strncmp(message, "1-0", 3) == 0) {
7558         char *p, *q, *r = "";
7559         p = strchr(message, '{');
7560         if (p) {
7561             q = strchr(p, '}');
7562             if (q) {
7563                 *q = NULLCHAR;
7564                 r = p + 1;
7565             }
7566         }
7567         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7568         return;
7569     } else if (strncmp(message, "0-1", 3) == 0) {
7570         char *p, *q, *r = "";
7571         p = strchr(message, '{');
7572         if (p) {
7573             q = strchr(p, '}');
7574             if (q) {
7575                 *q = NULLCHAR;
7576                 r = p + 1;
7577             }
7578         }
7579         /* Kludge for Arasan 4.1 bug */
7580         if (strcmp(r, "Black resigns") == 0) {
7581             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7582             return;
7583         }
7584         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7585         return;
7586     } else if (strncmp(message, "1/2", 3) == 0) {
7587         char *p, *q, *r = "";
7588         p = strchr(message, '{');
7589         if (p) {
7590             q = strchr(p, '}');
7591             if (q) {
7592                 *q = NULLCHAR;
7593                 r = p + 1;
7594             }
7595         }
7596             
7597         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7598         return;
7599
7600     } else if (strncmp(message, "White resign", 12) == 0) {
7601         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7602         return;
7603     } else if (strncmp(message, "Black resign", 12) == 0) {
7604         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7605         return;
7606     } else if (strncmp(message, "White matches", 13) == 0 ||
7607                strncmp(message, "Black matches", 13) == 0   ) {
7608         /* [HGM] ignore GNUShogi noises */
7609         return;
7610     } else if (strncmp(message, "White", 5) == 0 &&
7611                message[5] != '(' &&
7612                StrStr(message, "Black") == NULL) {
7613         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7614         return;
7615     } else if (strncmp(message, "Black", 5) == 0 &&
7616                message[5] != '(') {
7617         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7618         return;
7619     } else if (strcmp(message, "resign") == 0 ||
7620                strcmp(message, "computer resigns") == 0) {
7621         switch (gameMode) {
7622           case MachinePlaysBlack:
7623           case IcsPlayingBlack:
7624             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7625             break;
7626           case MachinePlaysWhite:
7627           case IcsPlayingWhite:
7628             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7629             break;
7630           case TwoMachinesPlay:
7631             if (cps->twoMachinesColor[0] == 'w')
7632               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7633             else
7634               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7635             break;
7636           default:
7637             /* can't happen */
7638             break;
7639         }
7640         return;
7641     } else if (strncmp(message, "opponent mates", 14) == 0) {
7642         switch (gameMode) {
7643           case MachinePlaysBlack:
7644           case IcsPlayingBlack:
7645             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7646             break;
7647           case MachinePlaysWhite:
7648           case IcsPlayingWhite:
7649             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7650             break;
7651           case TwoMachinesPlay:
7652             if (cps->twoMachinesColor[0] == 'w')
7653               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7654             else
7655               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7656             break;
7657           default:
7658             /* can't happen */
7659             break;
7660         }
7661         return;
7662     } else if (strncmp(message, "computer mates", 14) == 0) {
7663         switch (gameMode) {
7664           case MachinePlaysBlack:
7665           case IcsPlayingBlack:
7666             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7667             break;
7668           case MachinePlaysWhite:
7669           case IcsPlayingWhite:
7670             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7671             break;
7672           case TwoMachinesPlay:
7673             if (cps->twoMachinesColor[0] == 'w')
7674               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7675             else
7676               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7677             break;
7678           default:
7679             /* can't happen */
7680             break;
7681         }
7682         return;
7683     } else if (strncmp(message, "checkmate", 9) == 0) {
7684         if (WhiteOnMove(forwardMostMove)) {
7685             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7686         } else {
7687             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7688         }
7689         return;
7690     } else if (strstr(message, "Draw") != NULL ||
7691                strstr(message, "game is a draw") != NULL) {
7692         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7693         return;
7694     } else if (strstr(message, "offer") != NULL &&
7695                strstr(message, "draw") != NULL) {
7696 #if ZIPPY
7697         if (appData.zippyPlay && first.initDone) {
7698             /* Relay offer to ICS */
7699             SendToICS(ics_prefix);
7700             SendToICS("draw\n");
7701         }
7702 #endif
7703         cps->offeredDraw = 2; /* valid until this engine moves twice */
7704         if (gameMode == TwoMachinesPlay) {
7705             if (cps->other->offeredDraw) {
7706                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7707             /* [HGM] in two-machine mode we delay relaying draw offer      */
7708             /* until after we also have move, to see if it is really claim */
7709             }
7710         } else if (gameMode == MachinePlaysWhite ||
7711                    gameMode == MachinePlaysBlack) {
7712           if (userOfferedDraw) {
7713             DisplayInformation(_("Machine accepts your draw offer"));
7714             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7715           } else {
7716             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7717           }
7718         }
7719     }
7720
7721     
7722     /*
7723      * Look for thinking output
7724      */
7725     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7726           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7727                                 ) {
7728         int plylev, mvleft, mvtot, curscore, time;
7729         char mvname[MOVE_LEN];
7730         u64 nodes; // [DM]
7731         char plyext;
7732         int ignore = FALSE;
7733         int prefixHint = FALSE;
7734         mvname[0] = NULLCHAR;
7735
7736         switch (gameMode) {
7737           case MachinePlaysBlack:
7738           case IcsPlayingBlack:
7739             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7740             break;
7741           case MachinePlaysWhite:
7742           case IcsPlayingWhite:
7743             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7744             break;
7745           case AnalyzeMode:
7746           case AnalyzeFile:
7747             break;
7748           case IcsObserving: /* [DM] icsEngineAnalyze */
7749             if (!appData.icsEngineAnalyze) ignore = TRUE;
7750             break;
7751           case TwoMachinesPlay:
7752             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7753                 ignore = TRUE;
7754             }
7755             break;
7756           default:
7757             ignore = TRUE;
7758             break;
7759         }
7760
7761         if (!ignore) {
7762             buf1[0] = NULLCHAR;
7763             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7764                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7765
7766                 if (plyext != ' ' && plyext != '\t') {
7767                     time *= 100;
7768                 }
7769
7770                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7771                 if( cps->scoreIsAbsolute && 
7772                     ( gameMode == MachinePlaysBlack ||
7773                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7774                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7775                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7776                      !WhiteOnMove(currentMove)
7777                     ) )
7778                 {
7779                     curscore = -curscore;
7780                 }
7781
7782
7783                 programStats.depth = plylev;
7784                 programStats.nodes = nodes;
7785                 programStats.time = time;
7786                 programStats.score = curscore;
7787                 programStats.got_only_move = 0;
7788
7789                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7790                         int ticklen;
7791
7792                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7793                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7794                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7795                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7796                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7797                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7798                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7799                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7800                 }
7801
7802                 /* Buffer overflow protection */
7803                 if (buf1[0] != NULLCHAR) {
7804                     if (strlen(buf1) >= sizeof(programStats.movelist)
7805                         && appData.debugMode) {
7806                         fprintf(debugFP,
7807                                 "PV is too long; using the first %u bytes.\n",
7808                                 (unsigned) sizeof(programStats.movelist) - 1);
7809                     }
7810
7811                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7812                 } else {
7813                     sprintf(programStats.movelist, " no PV\n");
7814                 }
7815
7816                 if (programStats.seen_stat) {
7817                     programStats.ok_to_send = 1;
7818                 }
7819
7820                 if (strchr(programStats.movelist, '(') != NULL) {
7821                     programStats.line_is_book = 1;
7822                     programStats.nr_moves = 0;
7823                     programStats.moves_left = 0;
7824                 } else {
7825                     programStats.line_is_book = 0;
7826                 }
7827
7828                 SendProgramStatsToFrontend( cps, &programStats );
7829
7830                 /* 
7831                     [AS] Protect the thinkOutput buffer from overflow... this
7832                     is only useful if buf1 hasn't overflowed first!
7833                 */
7834                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7835                         plylev, 
7836                         (gameMode == TwoMachinesPlay ?
7837                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7838                         ((double) curscore) / 100.0,
7839                         prefixHint ? lastHint : "",
7840                         prefixHint ? " " : "" );
7841
7842                 if( buf1[0] != NULLCHAR ) {
7843                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7844
7845                     if( strlen(buf1) > max_len ) {
7846                         if( appData.debugMode) {
7847                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7848                         }
7849                         buf1[max_len+1] = '\0';
7850                     }
7851
7852                     strcat( thinkOutput, buf1 );
7853                 }
7854
7855                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7856                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7857                     DisplayMove(currentMove - 1);
7858                 }
7859                 return;
7860
7861             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7862                 /* crafty (9.25+) says "(only move) <move>"
7863                  * if there is only 1 legal move
7864                  */
7865                 sscanf(p, "(only move) %s", buf1);
7866                 sprintf(thinkOutput, "%s (only move)", buf1);
7867                 sprintf(programStats.movelist, "%s (only move)", buf1);
7868                 programStats.depth = 1;
7869                 programStats.nr_moves = 1;
7870                 programStats.moves_left = 1;
7871                 programStats.nodes = 1;
7872                 programStats.time = 1;
7873                 programStats.got_only_move = 1;
7874
7875                 /* Not really, but we also use this member to
7876                    mean "line isn't going to change" (Crafty
7877                    isn't searching, so stats won't change) */
7878                 programStats.line_is_book = 1;
7879
7880                 SendProgramStatsToFrontend( cps, &programStats );
7881                 
7882                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7883                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7884                     DisplayMove(currentMove - 1);
7885                 }
7886                 return;
7887             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7888                               &time, &nodes, &plylev, &mvleft,
7889                               &mvtot, mvname) >= 5) {
7890                 /* The stat01: line is from Crafty (9.29+) in response
7891                    to the "." command */
7892                 programStats.seen_stat = 1;
7893                 cps->maybeThinking = TRUE;
7894
7895                 if (programStats.got_only_move || !appData.periodicUpdates)
7896                   return;
7897
7898                 programStats.depth = plylev;
7899                 programStats.time = time;
7900                 programStats.nodes = nodes;
7901                 programStats.moves_left = mvleft;
7902                 programStats.nr_moves = mvtot;
7903                 strcpy(programStats.move_name, mvname);
7904                 programStats.ok_to_send = 1;
7905                 programStats.movelist[0] = '\0';
7906
7907                 SendProgramStatsToFrontend( cps, &programStats );
7908
7909                 return;
7910
7911             } else if (strncmp(message,"++",2) == 0) {
7912                 /* Crafty 9.29+ outputs this */
7913                 programStats.got_fail = 2;
7914                 return;
7915
7916             } else if (strncmp(message,"--",2) == 0) {
7917                 /* Crafty 9.29+ outputs this */
7918                 programStats.got_fail = 1;
7919                 return;
7920
7921             } else if (thinkOutput[0] != NULLCHAR &&
7922                        strncmp(message, "    ", 4) == 0) {
7923                 unsigned message_len;
7924
7925                 p = message;
7926                 while (*p && *p == ' ') p++;
7927
7928                 message_len = strlen( p );
7929
7930                 /* [AS] Avoid buffer overflow */
7931                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7932                     strcat(thinkOutput, " ");
7933                     strcat(thinkOutput, p);
7934                 }
7935
7936                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7937                     strcat(programStats.movelist, " ");
7938                     strcat(programStats.movelist, p);
7939                 }
7940
7941                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7942                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7943                     DisplayMove(currentMove - 1);
7944                 }
7945                 return;
7946             }
7947         }
7948         else {
7949             buf1[0] = NULLCHAR;
7950
7951             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7952                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7953             {
7954                 ChessProgramStats cpstats;
7955
7956                 if (plyext != ' ' && plyext != '\t') {
7957                     time *= 100;
7958                 }
7959
7960                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7961                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7962                     curscore = -curscore;
7963                 }
7964
7965                 cpstats.depth = plylev;
7966                 cpstats.nodes = nodes;
7967                 cpstats.time = time;
7968                 cpstats.score = curscore;
7969                 cpstats.got_only_move = 0;
7970                 cpstats.movelist[0] = '\0';
7971
7972                 if (buf1[0] != NULLCHAR) {
7973                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7974                 }
7975
7976                 cpstats.ok_to_send = 0;
7977                 cpstats.line_is_book = 0;
7978                 cpstats.nr_moves = 0;
7979                 cpstats.moves_left = 0;
7980
7981                 SendProgramStatsToFrontend( cps, &cpstats );
7982             }
7983         }
7984     }
7985 }
7986
7987
7988 /* Parse a game score from the character string "game", and
7989    record it as the history of the current game.  The game
7990    score is NOT assumed to start from the standard position. 
7991    The display is not updated in any way.
7992    */
7993 void
7994 ParseGameHistory(game)
7995      char *game;
7996 {
7997     ChessMove moveType;
7998     int fromX, fromY, toX, toY, boardIndex;
7999     char promoChar;
8000     char *p, *q;
8001     char buf[MSG_SIZ];
8002
8003     if (appData.debugMode)
8004       fprintf(debugFP, "Parsing game history: %s\n", game);
8005
8006     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8007     gameInfo.site = StrSave(appData.icsHost);
8008     gameInfo.date = PGNDate();
8009     gameInfo.round = StrSave("-");
8010
8011     /* Parse out names of players */
8012     while (*game == ' ') game++;
8013     p = buf;
8014     while (*game != ' ') *p++ = *game++;
8015     *p = NULLCHAR;
8016     gameInfo.white = StrSave(buf);
8017     while (*game == ' ') game++;
8018     p = buf;
8019     while (*game != ' ' && *game != '\n') *p++ = *game++;
8020     *p = NULLCHAR;
8021     gameInfo.black = StrSave(buf);
8022
8023     /* Parse moves */
8024     boardIndex = blackPlaysFirst ? 1 : 0;
8025     yynewstr(game);
8026     for (;;) {
8027         yyboardindex = boardIndex;
8028         moveType = (ChessMove) yylex();
8029         switch (moveType) {
8030           case IllegalMove:             /* maybe suicide chess, etc. */
8031   if (appData.debugMode) {
8032     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8033     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8034     setbuf(debugFP, NULL);
8035   }
8036           case WhitePromotionChancellor:
8037           case BlackPromotionChancellor:
8038           case WhitePromotionArchbishop:
8039           case BlackPromotionArchbishop:
8040           case WhitePromotionQueen:
8041           case BlackPromotionQueen:
8042           case WhitePromotionRook:
8043           case BlackPromotionRook:
8044           case WhitePromotionBishop:
8045           case BlackPromotionBishop:
8046           case WhitePromotionKnight:
8047           case BlackPromotionKnight:
8048           case WhitePromotionKing:
8049           case BlackPromotionKing:
8050           case NormalMove:
8051           case WhiteCapturesEnPassant:
8052           case BlackCapturesEnPassant:
8053           case WhiteKingSideCastle:
8054           case WhiteQueenSideCastle:
8055           case BlackKingSideCastle:
8056           case BlackQueenSideCastle:
8057           case WhiteKingSideCastleWild:
8058           case WhiteQueenSideCastleWild:
8059           case BlackKingSideCastleWild:
8060           case BlackQueenSideCastleWild:
8061           /* PUSH Fabien */
8062           case WhiteHSideCastleFR:
8063           case WhiteASideCastleFR:
8064           case BlackHSideCastleFR:
8065           case BlackASideCastleFR:
8066           /* POP Fabien */
8067             fromX = currentMoveString[0] - AAA;
8068             fromY = currentMoveString[1] - ONE;
8069             toX = currentMoveString[2] - AAA;
8070             toY = currentMoveString[3] - ONE;
8071             promoChar = currentMoveString[4];
8072             break;
8073           case WhiteDrop:
8074           case BlackDrop:
8075             fromX = moveType == WhiteDrop ?
8076               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8077             (int) CharToPiece(ToLower(currentMoveString[0]));
8078             fromY = DROP_RANK;
8079             toX = currentMoveString[2] - AAA;
8080             toY = currentMoveString[3] - ONE;
8081             promoChar = NULLCHAR;
8082             break;
8083           case AmbiguousMove:
8084             /* bug? */
8085             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8086   if (appData.debugMode) {
8087     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8088     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8089     setbuf(debugFP, NULL);
8090   }
8091             DisplayError(buf, 0);
8092             return;
8093           case ImpossibleMove:
8094             /* bug? */
8095             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8096   if (appData.debugMode) {
8097     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8098     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8099     setbuf(debugFP, NULL);
8100   }
8101             DisplayError(buf, 0);
8102             return;
8103           case (ChessMove) 0:   /* end of file */
8104             if (boardIndex < backwardMostMove) {
8105                 /* Oops, gap.  How did that happen? */
8106                 DisplayError(_("Gap in move list"), 0);
8107                 return;
8108             }
8109             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8110             if (boardIndex > forwardMostMove) {
8111                 forwardMostMove = boardIndex;
8112             }
8113             return;
8114           case ElapsedTime:
8115             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8116                 strcat(parseList[boardIndex-1], " ");
8117                 strcat(parseList[boardIndex-1], yy_text);
8118             }
8119             continue;
8120           case Comment:
8121           case PGNTag:
8122           case NAG:
8123           default:
8124             /* ignore */
8125             continue;
8126           case WhiteWins:
8127           case BlackWins:
8128           case GameIsDrawn:
8129           case GameUnfinished:
8130             if (gameMode == IcsExamining) {
8131                 if (boardIndex < backwardMostMove) {
8132                     /* Oops, gap.  How did that happen? */
8133                     return;
8134                 }
8135                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8136                 return;
8137             }
8138             gameInfo.result = moveType;
8139             p = strchr(yy_text, '{');
8140             if (p == NULL) p = strchr(yy_text, '(');
8141             if (p == NULL) {
8142                 p = yy_text;
8143                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8144             } else {
8145                 q = strchr(p, *p == '{' ? '}' : ')');
8146                 if (q != NULL) *q = NULLCHAR;
8147                 p++;
8148             }
8149             gameInfo.resultDetails = StrSave(p);
8150             continue;
8151         }
8152         if (boardIndex >= forwardMostMove &&
8153             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8154             backwardMostMove = blackPlaysFirst ? 1 : 0;
8155             return;
8156         }
8157         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8158                                  fromY, fromX, toY, toX, promoChar,
8159                                  parseList[boardIndex]);
8160         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8161         /* currentMoveString is set as a side-effect of yylex */
8162         strcpy(moveList[boardIndex], currentMoveString);
8163         strcat(moveList[boardIndex], "\n");
8164         boardIndex++;
8165         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8166         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8167           case MT_NONE:
8168           case MT_STALEMATE:
8169           default:
8170             break;
8171           case MT_CHECK:
8172             if(gameInfo.variant != VariantShogi)
8173                 strcat(parseList[boardIndex - 1], "+");
8174             break;
8175           case MT_CHECKMATE:
8176           case MT_STAINMATE:
8177             strcat(parseList[boardIndex - 1], "#");
8178             break;
8179         }
8180     }
8181 }
8182
8183
8184 /* Apply a move to the given board  */
8185 void
8186 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8187      int fromX, fromY, toX, toY;
8188      int promoChar;
8189      Board board;
8190 {
8191   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8192   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8193
8194     /* [HGM] compute & store e.p. status and castling rights for new position */
8195     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8196     { int i;
8197
8198       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8199       oldEP = (signed char)board[EP_STATUS];
8200       board[EP_STATUS] = EP_NONE;
8201
8202       if( board[toY][toX] != EmptySquare ) 
8203            board[EP_STATUS] = EP_CAPTURE;  
8204
8205       if( board[fromY][fromX] == WhitePawn ) {
8206            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8207                board[EP_STATUS] = EP_PAWN_MOVE;
8208            if( toY-fromY==2) {
8209                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8210                         gameInfo.variant != VariantBerolina || toX < fromX)
8211                       board[EP_STATUS] = toX | berolina;
8212                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8213                         gameInfo.variant != VariantBerolina || toX > fromX) 
8214                       board[EP_STATUS] = toX;
8215            }
8216       } else 
8217       if( board[fromY][fromX] == BlackPawn ) {
8218            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8219                board[EP_STATUS] = EP_PAWN_MOVE; 
8220            if( toY-fromY== -2) {
8221                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8222                         gameInfo.variant != VariantBerolina || toX < fromX)
8223                       board[EP_STATUS] = toX | berolina;
8224                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8225                         gameInfo.variant != VariantBerolina || toX > fromX) 
8226                       board[EP_STATUS] = toX;
8227            }
8228        }
8229
8230        for(i=0; i<nrCastlingRights; i++) {
8231            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8232               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8233              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8234        }
8235
8236     }
8237
8238   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8239   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8240        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8241          
8242   if (fromX == toX && fromY == toY) return;
8243
8244   if (fromY == DROP_RANK) {
8245         /* must be first */
8246         piece = board[toY][toX] = (ChessSquare) fromX;
8247   } else {
8248      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8249      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8250      if(gameInfo.variant == VariantKnightmate)
8251          king += (int) WhiteUnicorn - (int) WhiteKing;
8252
8253     /* Code added by Tord: */
8254     /* FRC castling assumed when king captures friendly rook. */
8255     if (board[fromY][fromX] == WhiteKing &&
8256              board[toY][toX] == WhiteRook) {
8257       board[fromY][fromX] = EmptySquare;
8258       board[toY][toX] = EmptySquare;
8259       if(toX > fromX) {
8260         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8261       } else {
8262         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8263       }
8264     } else if (board[fromY][fromX] == BlackKing &&
8265                board[toY][toX] == BlackRook) {
8266       board[fromY][fromX] = EmptySquare;
8267       board[toY][toX] = EmptySquare;
8268       if(toX > fromX) {
8269         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8270       } else {
8271         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8272       }
8273     /* End of code added by Tord */
8274
8275     } else if (board[fromY][fromX] == king
8276         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8277         && toY == fromY && toX > fromX+1) {
8278         board[fromY][fromX] = EmptySquare;
8279         board[toY][toX] = king;
8280         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8281         board[fromY][BOARD_RGHT-1] = EmptySquare;
8282     } else if (board[fromY][fromX] == king
8283         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8284                && toY == fromY && toX < fromX-1) {
8285         board[fromY][fromX] = EmptySquare;
8286         board[toY][toX] = king;
8287         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8288         board[fromY][BOARD_LEFT] = EmptySquare;
8289     } else if (board[fromY][fromX] == WhitePawn
8290                && toY >= BOARD_HEIGHT-promoRank
8291                && gameInfo.variant != VariantXiangqi
8292                ) {
8293         /* white pawn promotion */
8294         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8295         if (board[toY][toX] == EmptySquare) {
8296             board[toY][toX] = WhiteQueen;
8297         }
8298         if(gameInfo.variant==VariantBughouse ||
8299            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8300             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8301         board[fromY][fromX] = EmptySquare;
8302     } else if ((fromY == BOARD_HEIGHT-4)
8303                && (toX != fromX)
8304                && gameInfo.variant != VariantXiangqi
8305                && gameInfo.variant != VariantBerolina
8306                && (board[fromY][fromX] == WhitePawn)
8307                && (board[toY][toX] == EmptySquare)) {
8308         board[fromY][fromX] = EmptySquare;
8309         board[toY][toX] = WhitePawn;
8310         captured = board[toY - 1][toX];
8311         board[toY - 1][toX] = EmptySquare;
8312     } else if ((fromY == BOARD_HEIGHT-4)
8313                && (toX == fromX)
8314                && gameInfo.variant == VariantBerolina
8315                && (board[fromY][fromX] == WhitePawn)
8316                && (board[toY][toX] == EmptySquare)) {
8317         board[fromY][fromX] = EmptySquare;
8318         board[toY][toX] = WhitePawn;
8319         if(oldEP & EP_BEROLIN_A) {
8320                 captured = board[fromY][fromX-1];
8321                 board[fromY][fromX-1] = EmptySquare;
8322         }else{  captured = board[fromY][fromX+1];
8323                 board[fromY][fromX+1] = EmptySquare;
8324         }
8325     } else if (board[fromY][fromX] == king
8326         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8327                && toY == fromY && toX > fromX+1) {
8328         board[fromY][fromX] = EmptySquare;
8329         board[toY][toX] = king;
8330         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8331         board[fromY][BOARD_RGHT-1] = EmptySquare;
8332     } else if (board[fromY][fromX] == king
8333         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8334                && toY == fromY && toX < fromX-1) {
8335         board[fromY][fromX] = EmptySquare;
8336         board[toY][toX] = king;
8337         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8338         board[fromY][BOARD_LEFT] = EmptySquare;
8339     } else if (fromY == 7 && fromX == 3
8340                && board[fromY][fromX] == BlackKing
8341                && toY == 7 && toX == 5) {
8342         board[fromY][fromX] = EmptySquare;
8343         board[toY][toX] = BlackKing;
8344         board[fromY][7] = EmptySquare;
8345         board[toY][4] = BlackRook;
8346     } else if (fromY == 7 && fromX == 3
8347                && board[fromY][fromX] == BlackKing
8348                && toY == 7 && toX == 1) {
8349         board[fromY][fromX] = EmptySquare;
8350         board[toY][toX] = BlackKing;
8351         board[fromY][0] = EmptySquare;
8352         board[toY][2] = BlackRook;
8353     } else if (board[fromY][fromX] == BlackPawn
8354                && toY < promoRank
8355                && gameInfo.variant != VariantXiangqi
8356                ) {
8357         /* black pawn promotion */
8358         board[toY][toX] = CharToPiece(ToLower(promoChar));
8359         if (board[toY][toX] == EmptySquare) {
8360             board[toY][toX] = BlackQueen;
8361         }
8362         if(gameInfo.variant==VariantBughouse ||
8363            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8364             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8365         board[fromY][fromX] = EmptySquare;
8366     } else if ((fromY == 3)
8367                && (toX != fromX)
8368                && gameInfo.variant != VariantXiangqi
8369                && gameInfo.variant != VariantBerolina
8370                && (board[fromY][fromX] == BlackPawn)
8371                && (board[toY][toX] == EmptySquare)) {
8372         board[fromY][fromX] = EmptySquare;
8373         board[toY][toX] = BlackPawn;
8374         captured = board[toY + 1][toX];
8375         board[toY + 1][toX] = EmptySquare;
8376     } else if ((fromY == 3)
8377                && (toX == fromX)
8378                && gameInfo.variant == VariantBerolina
8379                && (board[fromY][fromX] == BlackPawn)
8380                && (board[toY][toX] == EmptySquare)) {
8381         board[fromY][fromX] = EmptySquare;
8382         board[toY][toX] = BlackPawn;
8383         if(oldEP & EP_BEROLIN_A) {
8384                 captured = board[fromY][fromX-1];
8385                 board[fromY][fromX-1] = EmptySquare;
8386         }else{  captured = board[fromY][fromX+1];
8387                 board[fromY][fromX+1] = EmptySquare;
8388         }
8389     } else {
8390         board[toY][toX] = board[fromY][fromX];
8391         board[fromY][fromX] = EmptySquare;
8392     }
8393
8394     /* [HGM] now we promote for Shogi, if needed */
8395     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8396         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8397   }
8398
8399     if (gameInfo.holdingsWidth != 0) {
8400
8401       /* !!A lot more code needs to be written to support holdings  */
8402       /* [HGM] OK, so I have written it. Holdings are stored in the */
8403       /* penultimate board files, so they are automaticlly stored   */
8404       /* in the game history.                                       */
8405       if (fromY == DROP_RANK) {
8406         /* Delete from holdings, by decreasing count */
8407         /* and erasing image if necessary            */
8408         p = (int) fromX;
8409         if(p < (int) BlackPawn) { /* white drop */
8410              p -= (int)WhitePawn;
8411                  p = PieceToNumber((ChessSquare)p);
8412              if(p >= gameInfo.holdingsSize) p = 0;
8413              if(--board[p][BOARD_WIDTH-2] <= 0)
8414                   board[p][BOARD_WIDTH-1] = EmptySquare;
8415              if((int)board[p][BOARD_WIDTH-2] < 0)
8416                         board[p][BOARD_WIDTH-2] = 0;
8417         } else {                  /* black drop */
8418              p -= (int)BlackPawn;
8419                  p = PieceToNumber((ChessSquare)p);
8420              if(p >= gameInfo.holdingsSize) p = 0;
8421              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8422                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8423              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8424                         board[BOARD_HEIGHT-1-p][1] = 0;
8425         }
8426       }
8427       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8428           && gameInfo.variant != VariantBughouse        ) {
8429         /* [HGM] holdings: Add to holdings, if holdings exist */
8430         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8431                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8432                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8433         }
8434         p = (int) captured;
8435         if (p >= (int) BlackPawn) {
8436           p -= (int)BlackPawn;
8437           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8438                   /* in Shogi restore piece to its original  first */
8439                   captured = (ChessSquare) (DEMOTED captured);
8440                   p = DEMOTED p;
8441           }
8442           p = PieceToNumber((ChessSquare)p);
8443           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8444           board[p][BOARD_WIDTH-2]++;
8445           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8446         } else {
8447           p -= (int)WhitePawn;
8448           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8449                   captured = (ChessSquare) (DEMOTED captured);
8450                   p = DEMOTED p;
8451           }
8452           p = PieceToNumber((ChessSquare)p);
8453           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8454           board[BOARD_HEIGHT-1-p][1]++;
8455           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8456         }
8457       }
8458     } else if (gameInfo.variant == VariantAtomic) {
8459       if (captured != EmptySquare) {
8460         int y, x;
8461         for (y = toY-1; y <= toY+1; y++) {
8462           for (x = toX-1; x <= toX+1; x++) {
8463             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8464                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8465               board[y][x] = EmptySquare;
8466             }
8467           }
8468         }
8469         board[toY][toX] = EmptySquare;
8470       }
8471     }
8472     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8473         /* [HGM] Shogi promotions */
8474         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8475     }
8476
8477     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8478                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8479         // [HGM] superchess: take promotion piece out of holdings
8480         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8481         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8482             if(!--board[k][BOARD_WIDTH-2])
8483                 board[k][BOARD_WIDTH-1] = EmptySquare;
8484         } else {
8485             if(!--board[BOARD_HEIGHT-1-k][1])
8486                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8487         }
8488     }
8489
8490 }
8491
8492 /* Updates forwardMostMove */
8493 void
8494 MakeMove(fromX, fromY, toX, toY, promoChar)
8495      int fromX, fromY, toX, toY;
8496      int promoChar;
8497 {
8498 //    forwardMostMove++; // [HGM] bare: moved downstream
8499
8500     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8501         int timeLeft; static int lastLoadFlag=0; int king, piece;
8502         piece = boards[forwardMostMove][fromY][fromX];
8503         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8504         if(gameInfo.variant == VariantKnightmate)
8505             king += (int) WhiteUnicorn - (int) WhiteKing;
8506         if(forwardMostMove == 0) {
8507             if(blackPlaysFirst) 
8508                 fprintf(serverMoves, "%s;", second.tidy);
8509             fprintf(serverMoves, "%s;", first.tidy);
8510             if(!blackPlaysFirst) 
8511                 fprintf(serverMoves, "%s;", second.tidy);
8512         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8513         lastLoadFlag = loadFlag;
8514         // print base move
8515         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8516         // print castling suffix
8517         if( toY == fromY && piece == king ) {
8518             if(toX-fromX > 1)
8519                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8520             if(fromX-toX >1)
8521                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8522         }
8523         // e.p. suffix
8524         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8525              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8526              boards[forwardMostMove][toY][toX] == EmptySquare
8527              && fromX != toX )
8528                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8529         // promotion suffix
8530         if(promoChar != NULLCHAR)
8531                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8532         if(!loadFlag) {
8533             fprintf(serverMoves, "/%d/%d",
8534                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8535             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8536             else                      timeLeft = blackTimeRemaining/1000;
8537             fprintf(serverMoves, "/%d", timeLeft);
8538         }
8539         fflush(serverMoves);
8540     }
8541
8542     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8543       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8544                         0, 1);
8545       return;
8546     }
8547     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8548     if (commentList[forwardMostMove+1] != NULL) {
8549         free(commentList[forwardMostMove+1]);
8550         commentList[forwardMostMove+1] = NULL;
8551     }
8552     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8553     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8554     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8555     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8556     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8557     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8558     gameInfo.result = GameUnfinished;
8559     if (gameInfo.resultDetails != NULL) {
8560         free(gameInfo.resultDetails);
8561         gameInfo.resultDetails = NULL;
8562     }
8563     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8564                               moveList[forwardMostMove - 1]);
8565     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8566                              PosFlags(forwardMostMove - 1),
8567                              fromY, fromX, toY, toX, promoChar,
8568                              parseList[forwardMostMove - 1]);
8569     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8570       case MT_NONE:
8571       case MT_STALEMATE:
8572       default:
8573         break;
8574       case MT_CHECK:
8575         if(gameInfo.variant != VariantShogi)
8576             strcat(parseList[forwardMostMove - 1], "+");
8577         break;
8578       case MT_CHECKMATE:
8579       case MT_STAINMATE:
8580         strcat(parseList[forwardMostMove - 1], "#");
8581         break;
8582     }
8583     if (appData.debugMode) {
8584         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8585     }
8586
8587 }
8588
8589 /* Updates currentMove if not pausing */
8590 void
8591 ShowMove(fromX, fromY, toX, toY)
8592 {
8593     int instant = (gameMode == PlayFromGameFile) ?
8594         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8595     if(appData.noGUI) return;
8596     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8597         if (!instant) {
8598             if (forwardMostMove == currentMove + 1) {
8599                 AnimateMove(boards[forwardMostMove - 1],
8600                             fromX, fromY, toX, toY);
8601             }
8602             if (appData.highlightLastMove) {
8603                 SetHighlights(fromX, fromY, toX, toY);
8604             }
8605         }
8606         currentMove = forwardMostMove;
8607     }
8608
8609     if (instant) return;
8610
8611     DisplayMove(currentMove - 1);
8612     DrawPosition(FALSE, boards[currentMove]);
8613     DisplayBothClocks();
8614     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8615 }
8616
8617 void SendEgtPath(ChessProgramState *cps)
8618 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8619         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8620
8621         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8622
8623         while(*p) {
8624             char c, *q = name+1, *r, *s;
8625
8626             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8627             while(*p && *p != ',') *q++ = *p++;
8628             *q++ = ':'; *q = 0;
8629             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8630                 strcmp(name, ",nalimov:") == 0 ) {
8631                 // take nalimov path from the menu-changeable option first, if it is defined
8632                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8633                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8634             } else
8635             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8636                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8637                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8638                 s = r = StrStr(s, ":") + 1; // beginning of path info
8639                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8640                 c = *r; *r = 0;             // temporarily null-terminate path info
8641                     *--q = 0;               // strip of trailig ':' from name
8642                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8643                 *r = c;
8644                 SendToProgram(buf,cps);     // send egtbpath command for this format
8645             }
8646             if(*p == ',') p++; // read away comma to position for next format name
8647         }
8648 }
8649
8650 void
8651 InitChessProgram(cps, setup)
8652      ChessProgramState *cps;
8653      int setup; /* [HGM] needed to setup FRC opening position */
8654 {
8655     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8656     if (appData.noChessProgram) return;
8657     hintRequested = FALSE;
8658     bookRequested = FALSE;
8659
8660     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8661     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8662     if(cps->memSize) { /* [HGM] memory */
8663         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8664         SendToProgram(buf, cps);
8665     }
8666     SendEgtPath(cps); /* [HGM] EGT */
8667     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8668         sprintf(buf, "cores %d\n", appData.smpCores);
8669         SendToProgram(buf, cps);
8670     }
8671
8672     SendToProgram(cps->initString, cps);
8673     if (gameInfo.variant != VariantNormal &&
8674         gameInfo.variant != VariantLoadable
8675         /* [HGM] also send variant if board size non-standard */
8676         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8677                                             ) {
8678       char *v = VariantName(gameInfo.variant);
8679       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8680         /* [HGM] in protocol 1 we have to assume all variants valid */
8681         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8682         DisplayFatalError(buf, 0, 1);
8683         return;
8684       }
8685
8686       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8687       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8688       if( gameInfo.variant == VariantXiangqi )
8689            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8690       if( gameInfo.variant == VariantShogi )
8691            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8692       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8693            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8694       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8695                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8696            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8697       if( gameInfo.variant == VariantCourier )
8698            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8699       if( gameInfo.variant == VariantSuper )
8700            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8701       if( gameInfo.variant == VariantGreat )
8702            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8703
8704       if(overruled) {
8705            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8706                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8707            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8708            if(StrStr(cps->variants, b) == NULL) { 
8709                // specific sized variant not known, check if general sizing allowed
8710                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8711                    if(StrStr(cps->variants, "boardsize") == NULL) {
8712                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8713                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8714                        DisplayFatalError(buf, 0, 1);
8715                        return;
8716                    }
8717                    /* [HGM] here we really should compare with the maximum supported board size */
8718                }
8719            }
8720       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8721       sprintf(buf, "variant %s\n", b);
8722       SendToProgram(buf, cps);
8723     }
8724     currentlyInitializedVariant = gameInfo.variant;
8725
8726     /* [HGM] send opening position in FRC to first engine */
8727     if(setup) {
8728           SendToProgram("force\n", cps);
8729           SendBoard(cps, 0);
8730           /* engine is now in force mode! Set flag to wake it up after first move. */
8731           setboardSpoiledMachineBlack = 1;
8732     }
8733
8734     if (cps->sendICS) {
8735       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8736       SendToProgram(buf, cps);
8737     }
8738     cps->maybeThinking = FALSE;
8739     cps->offeredDraw = 0;
8740     if (!appData.icsActive) {
8741         SendTimeControl(cps, movesPerSession, timeControl,
8742                         timeIncrement, appData.searchDepth,
8743                         searchTime);
8744     }
8745     if (appData.showThinking 
8746         // [HGM] thinking: four options require thinking output to be sent
8747         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8748                                 ) {
8749         SendToProgram("post\n", cps);
8750     }
8751     SendToProgram("hard\n", cps);
8752     if (!appData.ponderNextMove) {
8753         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8754            it without being sure what state we are in first.  "hard"
8755            is not a toggle, so that one is OK.
8756          */
8757         SendToProgram("easy\n", cps);
8758     }
8759     if (cps->usePing) {
8760       sprintf(buf, "ping %d\n", ++cps->lastPing);
8761       SendToProgram(buf, cps);
8762     }
8763     cps->initDone = TRUE;
8764 }   
8765
8766
8767 void
8768 StartChessProgram(cps)
8769      ChessProgramState *cps;
8770 {
8771     char buf[MSG_SIZ];
8772     int err;
8773
8774     if (appData.noChessProgram) return;
8775     cps->initDone = FALSE;
8776
8777     if (strcmp(cps->host, "localhost") == 0) {
8778         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8779     } else if (*appData.remoteShell == NULLCHAR) {
8780         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8781     } else {
8782         if (*appData.remoteUser == NULLCHAR) {
8783           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8784                     cps->program);
8785         } else {
8786           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8787                     cps->host, appData.remoteUser, cps->program);
8788         }
8789         err = StartChildProcess(buf, "", &cps->pr);
8790     }
8791     
8792     if (err != 0) {
8793         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8794         DisplayFatalError(buf, err, 1);
8795         cps->pr = NoProc;
8796         cps->isr = NULL;
8797         return;
8798     }
8799     
8800     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8801     if (cps->protocolVersion > 1) {
8802       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8803       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8804       cps->comboCnt = 0;  //                and values of combo boxes
8805       SendToProgram(buf, cps);
8806     } else {
8807       SendToProgram("xboard\n", cps);
8808     }
8809 }
8810
8811
8812 void
8813 TwoMachinesEventIfReady P((void))
8814 {
8815   if (first.lastPing != first.lastPong) {
8816     DisplayMessage("", _("Waiting for first chess program"));
8817     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8818     return;
8819   }
8820   if (second.lastPing != second.lastPong) {
8821     DisplayMessage("", _("Waiting for second chess program"));
8822     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8823     return;
8824   }
8825   ThawUI();
8826   TwoMachinesEvent();
8827 }
8828
8829 void
8830 NextMatchGame P((void))
8831 {
8832     int index; /* [HGM] autoinc: step load index during match */
8833     Reset(FALSE, TRUE);
8834     if (*appData.loadGameFile != NULLCHAR) {
8835         index = appData.loadGameIndex;
8836         if(index < 0) { // [HGM] autoinc
8837             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8838             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8839         } 
8840         LoadGameFromFile(appData.loadGameFile,
8841                          index,
8842                          appData.loadGameFile, FALSE);
8843     } else if (*appData.loadPositionFile != NULLCHAR) {
8844         index = appData.loadPositionIndex;
8845         if(index < 0) { // [HGM] autoinc
8846             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8847             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8848         } 
8849         LoadPositionFromFile(appData.loadPositionFile,
8850                              index,
8851                              appData.loadPositionFile);
8852     }
8853     TwoMachinesEventIfReady();
8854 }
8855
8856 void UserAdjudicationEvent( int result )
8857 {
8858     ChessMove gameResult = GameIsDrawn;
8859
8860     if( result > 0 ) {
8861         gameResult = WhiteWins;
8862     }
8863     else if( result < 0 ) {
8864         gameResult = BlackWins;
8865     }
8866
8867     if( gameMode == TwoMachinesPlay ) {
8868         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8869     }
8870 }
8871
8872
8873 // [HGM] save: calculate checksum of game to make games easily identifiable
8874 int StringCheckSum(char *s)
8875 {
8876         int i = 0;
8877         if(s==NULL) return 0;
8878         while(*s) i = i*259 + *s++;
8879         return i;
8880 }
8881
8882 int GameCheckSum()
8883 {
8884         int i, sum=0;
8885         for(i=backwardMostMove; i<forwardMostMove; i++) {
8886                 sum += pvInfoList[i].depth;
8887                 sum += StringCheckSum(parseList[i]);
8888                 sum += StringCheckSum(commentList[i]);
8889                 sum *= 261;
8890         }
8891         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8892         return sum + StringCheckSum(commentList[i]);
8893 } // end of save patch
8894
8895 void
8896 GameEnds(result, resultDetails, whosays)
8897      ChessMove result;
8898      char *resultDetails;
8899      int whosays;
8900 {
8901     GameMode nextGameMode;
8902     int isIcsGame;
8903     char buf[MSG_SIZ];
8904
8905     if(endingGame) return; /* [HGM] crash: forbid recursion */
8906     endingGame = 1;
8907     if(twoBoards) { twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); } // [HGM] dual
8908
8909     if (appData.debugMode) {
8910       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8911               result, resultDetails ? resultDetails : "(null)", whosays);
8912     }
8913
8914     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8915
8916     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8917         /* If we are playing on ICS, the server decides when the
8918            game is over, but the engine can offer to draw, claim 
8919            a draw, or resign. 
8920          */
8921 #if ZIPPY
8922         if (appData.zippyPlay && first.initDone) {
8923             if (result == GameIsDrawn) {
8924                 /* In case draw still needs to be claimed */
8925                 SendToICS(ics_prefix);
8926                 SendToICS("draw\n");
8927             } else if (StrCaseStr(resultDetails, "resign")) {
8928                 SendToICS(ics_prefix);
8929                 SendToICS("resign\n");
8930             }
8931         }
8932 #endif
8933         endingGame = 0; /* [HGM] crash */
8934         return;
8935     }
8936
8937     /* If we're loading the game from a file, stop */
8938     if (whosays == GE_FILE) {
8939       (void) StopLoadGameTimer();
8940       gameFileFP = NULL;
8941     }
8942
8943     /* Cancel draw offers */
8944     first.offeredDraw = second.offeredDraw = 0;
8945
8946     /* If this is an ICS game, only ICS can really say it's done;
8947        if not, anyone can. */
8948     isIcsGame = (gameMode == IcsPlayingWhite || 
8949                  gameMode == IcsPlayingBlack || 
8950                  gameMode == IcsObserving    || 
8951                  gameMode == IcsExamining);
8952
8953     if (!isIcsGame || whosays == GE_ICS) {
8954         /* OK -- not an ICS game, or ICS said it was done */
8955         StopClocks();
8956         if (!isIcsGame && !appData.noChessProgram) 
8957           SetUserThinkingEnables();
8958     
8959         /* [HGM] if a machine claims the game end we verify this claim */
8960         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8961             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8962                 char claimer;
8963                 ChessMove trueResult = (ChessMove) -1;
8964
8965                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8966                                             first.twoMachinesColor[0] :
8967                                             second.twoMachinesColor[0] ;
8968
8969                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8970                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8971                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8972                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8973                 } else
8974                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8975                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8976                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8977                 } else
8978                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8979                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8980                 }
8981
8982                 // now verify win claims, but not in drop games, as we don't understand those yet
8983                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8984                                                  || gameInfo.variant == VariantGreat) &&
8985                     (result == WhiteWins && claimer == 'w' ||
8986                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8987                       if (appData.debugMode) {
8988                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8989                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8990                       }
8991                       if(result != trueResult) {
8992                               sprintf(buf, "False win claim: '%s'", resultDetails);
8993                               result = claimer == 'w' ? BlackWins : WhiteWins;
8994                               resultDetails = buf;
8995                       }
8996                 } else
8997                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8998                     && (forwardMostMove <= backwardMostMove ||
8999                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9000                         (claimer=='b')==(forwardMostMove&1))
9001                                                                                   ) {
9002                       /* [HGM] verify: draws that were not flagged are false claims */
9003                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9004                       result = claimer == 'w' ? BlackWins : WhiteWins;
9005                       resultDetails = buf;
9006                 }
9007                 /* (Claiming a loss is accepted no questions asked!) */
9008             }
9009             /* [HGM] bare: don't allow bare King to win */
9010             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9011                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9012                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9013                && result != GameIsDrawn)
9014             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9015                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9016                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9017                         if(p >= 0 && p <= (int)WhiteKing) k++;
9018                 }
9019                 if (appData.debugMode) {
9020                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9021                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9022                 }
9023                 if(k <= 1) {
9024                         result = GameIsDrawn;
9025                         sprintf(buf, "%s but bare king", resultDetails);
9026                         resultDetails = buf;
9027                 }
9028             }
9029         }
9030
9031
9032         if(serverMoves != NULL && !loadFlag) { char c = '=';
9033             if(result==WhiteWins) c = '+';
9034             if(result==BlackWins) c = '-';
9035             if(resultDetails != NULL)
9036                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9037         }
9038         if (resultDetails != NULL) {
9039             gameInfo.result = result;
9040             gameInfo.resultDetails = StrSave(resultDetails);
9041
9042             /* display last move only if game was not loaded from file */
9043             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9044                 DisplayMove(currentMove - 1);
9045     
9046             if (forwardMostMove != 0) {
9047                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9048                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9049                                                                 ) {
9050                     if (*appData.saveGameFile != NULLCHAR) {
9051                         SaveGameToFile(appData.saveGameFile, TRUE);
9052                     } else if (appData.autoSaveGames) {
9053                         AutoSaveGame();
9054                     }
9055                     if (*appData.savePositionFile != NULLCHAR) {
9056                         SavePositionToFile(appData.savePositionFile);
9057                     }
9058                 }
9059             }
9060
9061             /* Tell program how game ended in case it is learning */
9062             /* [HGM] Moved this to after saving the PGN, just in case */
9063             /* engine died and we got here through time loss. In that */
9064             /* case we will get a fatal error writing the pipe, which */
9065             /* would otherwise lose us the PGN.                       */
9066             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9067             /* output during GameEnds should never be fatal anymore   */
9068             if (gameMode == MachinePlaysWhite ||
9069                 gameMode == MachinePlaysBlack ||
9070                 gameMode == TwoMachinesPlay ||
9071                 gameMode == IcsPlayingWhite ||
9072                 gameMode == IcsPlayingBlack ||
9073                 gameMode == BeginningOfGame) {
9074                 char buf[MSG_SIZ];
9075                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9076                         resultDetails);
9077                 if (first.pr != NoProc) {
9078                     SendToProgram(buf, &first);
9079                 }
9080                 if (second.pr != NoProc &&
9081                     gameMode == TwoMachinesPlay) {
9082                     SendToProgram(buf, &second);
9083                 }
9084             }
9085         }
9086
9087         if (appData.icsActive) {
9088             if (appData.quietPlay &&
9089                 (gameMode == IcsPlayingWhite ||
9090                  gameMode == IcsPlayingBlack)) {
9091                 SendToICS(ics_prefix);
9092                 SendToICS("set shout 1\n");
9093             }
9094             nextGameMode = IcsIdle;
9095             ics_user_moved = FALSE;
9096             /* clean up premove.  It's ugly when the game has ended and the
9097              * premove highlights are still on the board.
9098              */
9099             if (gotPremove) {
9100               gotPremove = FALSE;
9101               ClearPremoveHighlights();
9102               DrawPosition(FALSE, boards[currentMove]);
9103             }
9104             if (whosays == GE_ICS) {
9105                 switch (result) {
9106                 case WhiteWins:
9107                     if (gameMode == IcsPlayingWhite)
9108                         PlayIcsWinSound();
9109                     else if(gameMode == IcsPlayingBlack)
9110                         PlayIcsLossSound();
9111                     break;
9112                 case BlackWins:
9113                     if (gameMode == IcsPlayingBlack)
9114                         PlayIcsWinSound();
9115                     else if(gameMode == IcsPlayingWhite)
9116                         PlayIcsLossSound();
9117                     break;
9118                 case GameIsDrawn:
9119                     PlayIcsDrawSound();
9120                     break;
9121                 default:
9122                     PlayIcsUnfinishedSound();
9123                 }
9124             }
9125         } else if (gameMode == EditGame ||
9126                    gameMode == PlayFromGameFile || 
9127                    gameMode == AnalyzeMode || 
9128                    gameMode == AnalyzeFile) {
9129             nextGameMode = gameMode;
9130         } else {
9131             nextGameMode = EndOfGame;
9132         }
9133         pausing = FALSE;
9134         ModeHighlight();
9135     } else {
9136         nextGameMode = gameMode;
9137     }
9138
9139     if (appData.noChessProgram) {
9140         gameMode = nextGameMode;
9141         ModeHighlight();
9142         endingGame = 0; /* [HGM] crash */
9143         return;
9144     }
9145
9146     if (first.reuse) {
9147         /* Put first chess program into idle state */
9148         if (first.pr != NoProc &&
9149             (gameMode == MachinePlaysWhite ||
9150              gameMode == MachinePlaysBlack ||
9151              gameMode == TwoMachinesPlay ||
9152              gameMode == IcsPlayingWhite ||
9153              gameMode == IcsPlayingBlack ||
9154              gameMode == BeginningOfGame)) {
9155             SendToProgram("force\n", &first);
9156             if (first.usePing) {
9157               char buf[MSG_SIZ];
9158               sprintf(buf, "ping %d\n", ++first.lastPing);
9159               SendToProgram(buf, &first);
9160             }
9161         }
9162     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9163         /* Kill off first chess program */
9164         if (first.isr != NULL)
9165           RemoveInputSource(first.isr);
9166         first.isr = NULL;
9167     
9168         if (first.pr != NoProc) {
9169             ExitAnalyzeMode();
9170             DoSleep( appData.delayBeforeQuit );
9171             SendToProgram("quit\n", &first);
9172             DoSleep( appData.delayAfterQuit );
9173             DestroyChildProcess(first.pr, first.useSigterm);
9174         }
9175         first.pr = NoProc;
9176     }
9177     if (second.reuse) {
9178         /* Put second chess program into idle state */
9179         if (second.pr != NoProc &&
9180             gameMode == TwoMachinesPlay) {
9181             SendToProgram("force\n", &second);
9182             if (second.usePing) {
9183               char buf[MSG_SIZ];
9184               sprintf(buf, "ping %d\n", ++second.lastPing);
9185               SendToProgram(buf, &second);
9186             }
9187         }
9188     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9189         /* Kill off second chess program */
9190         if (second.isr != NULL)
9191           RemoveInputSource(second.isr);
9192         second.isr = NULL;
9193     
9194         if (second.pr != NoProc) {
9195             DoSleep( appData.delayBeforeQuit );
9196             SendToProgram("quit\n", &second);
9197             DoSleep( appData.delayAfterQuit );
9198             DestroyChildProcess(second.pr, second.useSigterm);
9199         }
9200         second.pr = NoProc;
9201     }
9202
9203     if (matchMode && gameMode == TwoMachinesPlay) {
9204         switch (result) {
9205         case WhiteWins:
9206           if (first.twoMachinesColor[0] == 'w') {
9207             first.matchWins++;
9208           } else {
9209             second.matchWins++;
9210           }
9211           break;
9212         case BlackWins:
9213           if (first.twoMachinesColor[0] == 'b') {
9214             first.matchWins++;
9215           } else {
9216             second.matchWins++;
9217           }
9218           break;
9219         default:
9220           break;
9221         }
9222         if (matchGame < appData.matchGames) {
9223             char *tmp;
9224             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9225                 tmp = first.twoMachinesColor;
9226                 first.twoMachinesColor = second.twoMachinesColor;
9227                 second.twoMachinesColor = tmp;
9228             }
9229             gameMode = nextGameMode;
9230             matchGame++;
9231             if(appData.matchPause>10000 || appData.matchPause<10)
9232                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9233             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9234             endingGame = 0; /* [HGM] crash */
9235             return;
9236         } else {
9237             char buf[MSG_SIZ];
9238             gameMode = nextGameMode;
9239             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9240                     first.tidy, second.tidy,
9241                     first.matchWins, second.matchWins,
9242                     appData.matchGames - (first.matchWins + second.matchWins));
9243             DisplayFatalError(buf, 0, 0);
9244         }
9245     }
9246     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9247         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9248       ExitAnalyzeMode();
9249     gameMode = nextGameMode;
9250     ModeHighlight();
9251     endingGame = 0;  /* [HGM] crash */
9252 }
9253
9254 /* Assumes program was just initialized (initString sent).
9255    Leaves program in force mode. */
9256 void
9257 FeedMovesToProgram(cps, upto) 
9258      ChessProgramState *cps;
9259      int upto;
9260 {
9261     int i;
9262     
9263     if (appData.debugMode)
9264       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9265               startedFromSetupPosition ? "position and " : "",
9266               backwardMostMove, upto, cps->which);
9267     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9268         // [HGM] variantswitch: make engine aware of new variant
9269         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9270                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9271         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9272         SendToProgram(buf, cps);
9273         currentlyInitializedVariant = gameInfo.variant;
9274     }
9275     SendToProgram("force\n", cps);
9276     if (startedFromSetupPosition) {
9277         SendBoard(cps, backwardMostMove);
9278     if (appData.debugMode) {
9279         fprintf(debugFP, "feedMoves\n");
9280     }
9281     }
9282     for (i = backwardMostMove; i < upto; i++) {
9283         SendMoveToProgram(i, cps);
9284     }
9285 }
9286
9287
9288 void
9289 ResurrectChessProgram()
9290 {
9291      /* The chess program may have exited.
9292         If so, restart it and feed it all the moves made so far. */
9293
9294     if (appData.noChessProgram || first.pr != NoProc) return;
9295     
9296     StartChessProgram(&first);
9297     InitChessProgram(&first, FALSE);
9298     FeedMovesToProgram(&first, currentMove);
9299
9300     if (!first.sendTime) {
9301         /* can't tell gnuchess what its clock should read,
9302            so we bow to its notion. */
9303         ResetClocks();
9304         timeRemaining[0][currentMove] = whiteTimeRemaining;
9305         timeRemaining[1][currentMove] = blackTimeRemaining;
9306     }
9307
9308     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9309                 appData.icsEngineAnalyze) && first.analysisSupport) {
9310       SendToProgram("analyze\n", &first);
9311       first.analyzing = TRUE;
9312     }
9313 }
9314
9315 /*
9316  * Button procedures
9317  */
9318 void
9319 Reset(redraw, init)
9320      int redraw, init;
9321 {
9322     int i;
9323
9324     if (appData.debugMode) {
9325         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9326                 redraw, init, gameMode);
9327     }
9328     CleanupTail(); // [HGM] vari: delete any stored variations
9329     pausing = pauseExamInvalid = FALSE;
9330     startedFromSetupPosition = blackPlaysFirst = FALSE;
9331     firstMove = TRUE;
9332     whiteFlag = blackFlag = FALSE;
9333     userOfferedDraw = FALSE;
9334     hintRequested = bookRequested = FALSE;
9335     first.maybeThinking = FALSE;
9336     second.maybeThinking = FALSE;
9337     first.bookSuspend = FALSE; // [HGM] book
9338     second.bookSuspend = FALSE;
9339     thinkOutput[0] = NULLCHAR;
9340     lastHint[0] = NULLCHAR;
9341     ClearGameInfo(&gameInfo);
9342     gameInfo.variant = StringToVariant(appData.variant);
9343     ics_user_moved = ics_clock_paused = FALSE;
9344     ics_getting_history = H_FALSE;
9345     ics_gamenum = -1;
9346     white_holding[0] = black_holding[0] = NULLCHAR;
9347     ClearProgramStats();
9348     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9349     
9350     ResetFrontEnd();
9351     ClearHighlights();
9352     flipView = appData.flipView;
9353     ClearPremoveHighlights();
9354     gotPremove = FALSE;
9355     alarmSounded = FALSE;
9356
9357     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9358     if(appData.serverMovesName != NULL) {
9359         /* [HGM] prepare to make moves file for broadcasting */
9360         clock_t t = clock();
9361         if(serverMoves != NULL) fclose(serverMoves);
9362         serverMoves = fopen(appData.serverMovesName, "r");
9363         if(serverMoves != NULL) {
9364             fclose(serverMoves);
9365             /* delay 15 sec before overwriting, so all clients can see end */
9366             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9367         }
9368         serverMoves = fopen(appData.serverMovesName, "w");
9369     }
9370
9371     ExitAnalyzeMode();
9372     gameMode = BeginningOfGame;
9373     ModeHighlight();
9374     if(appData.icsActive) gameInfo.variant = VariantNormal;
9375     currentMove = forwardMostMove = backwardMostMove = 0;
9376     InitPosition(redraw);
9377     for (i = 0; i < MAX_MOVES; i++) {
9378         if (commentList[i] != NULL) {
9379             free(commentList[i]);
9380             commentList[i] = NULL;
9381         }
9382     }
9383     ResetClocks();
9384     timeRemaining[0][0] = whiteTimeRemaining;
9385     timeRemaining[1][0] = blackTimeRemaining;
9386     if (first.pr == NULL) {
9387         StartChessProgram(&first);
9388     }
9389     if (init) {
9390             InitChessProgram(&first, startedFromSetupPosition);
9391     }
9392     DisplayTitle("");
9393     DisplayMessage("", "");
9394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9395     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9396 }
9397
9398 void
9399 AutoPlayGameLoop()
9400 {
9401     for (;;) {
9402         if (!AutoPlayOneMove())
9403           return;
9404         if (matchMode || appData.timeDelay == 0)
9405           continue;
9406         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9407           return;
9408         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9409         break;
9410     }
9411 }
9412
9413
9414 int
9415 AutoPlayOneMove()
9416 {
9417     int fromX, fromY, toX, toY;
9418
9419     if (appData.debugMode) {
9420       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9421     }
9422
9423     if (gameMode != PlayFromGameFile)
9424       return FALSE;
9425
9426     if (currentMove >= forwardMostMove) {
9427       gameMode = EditGame;
9428       ModeHighlight();
9429
9430       /* [AS] Clear current move marker at the end of a game */
9431       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9432
9433       return FALSE;
9434     }
9435     
9436     toX = moveList[currentMove][2] - AAA;
9437     toY = moveList[currentMove][3] - ONE;
9438
9439     if (moveList[currentMove][1] == '@') {
9440         if (appData.highlightLastMove) {
9441             SetHighlights(-1, -1, toX, toY);
9442         }
9443     } else {
9444         fromX = moveList[currentMove][0] - AAA;
9445         fromY = moveList[currentMove][1] - ONE;
9446
9447         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9448
9449         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9450
9451         if (appData.highlightLastMove) {
9452             SetHighlights(fromX, fromY, toX, toY);
9453         }
9454     }
9455     DisplayMove(currentMove);
9456     SendMoveToProgram(currentMove++, &first);
9457     DisplayBothClocks();
9458     DrawPosition(FALSE, boards[currentMove]);
9459     // [HGM] PV info: always display, routine tests if empty
9460     DisplayComment(currentMove - 1, commentList[currentMove]);
9461     return TRUE;
9462 }
9463
9464
9465 int
9466 LoadGameOneMove(readAhead)
9467      ChessMove readAhead;
9468 {
9469     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9470     char promoChar = NULLCHAR;
9471     ChessMove moveType;
9472     char move[MSG_SIZ];
9473     char *p, *q;
9474     
9475     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9476         gameMode != AnalyzeMode && gameMode != Training) {
9477         gameFileFP = NULL;
9478         return FALSE;
9479     }
9480     
9481     yyboardindex = forwardMostMove;
9482     if (readAhead != (ChessMove)0) {
9483       moveType = readAhead;
9484     } else {
9485       if (gameFileFP == NULL)
9486           return FALSE;
9487       moveType = (ChessMove) yylex();
9488     }
9489     
9490     done = FALSE;
9491     switch (moveType) {
9492       case Comment:
9493         if (appData.debugMode) 
9494           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9495         p = yy_text;
9496
9497         /* append the comment but don't display it */
9498         AppendComment(currentMove, p, FALSE);
9499         return TRUE;
9500
9501       case WhiteCapturesEnPassant:
9502       case BlackCapturesEnPassant:
9503       case WhitePromotionChancellor:
9504       case BlackPromotionChancellor:
9505       case WhitePromotionArchbishop:
9506       case BlackPromotionArchbishop:
9507       case WhitePromotionCentaur:
9508       case BlackPromotionCentaur:
9509       case WhitePromotionQueen:
9510       case BlackPromotionQueen:
9511       case WhitePromotionRook:
9512       case BlackPromotionRook:
9513       case WhitePromotionBishop:
9514       case BlackPromotionBishop:
9515       case WhitePromotionKnight:
9516       case BlackPromotionKnight:
9517       case WhitePromotionKing:
9518       case BlackPromotionKing:
9519       case NormalMove:
9520       case WhiteKingSideCastle:
9521       case WhiteQueenSideCastle:
9522       case BlackKingSideCastle:
9523       case BlackQueenSideCastle:
9524       case WhiteKingSideCastleWild:
9525       case WhiteQueenSideCastleWild:
9526       case BlackKingSideCastleWild:
9527       case BlackQueenSideCastleWild:
9528       /* PUSH Fabien */
9529       case WhiteHSideCastleFR:
9530       case WhiteASideCastleFR:
9531       case BlackHSideCastleFR:
9532       case BlackASideCastleFR:
9533       /* POP Fabien */
9534         if (appData.debugMode)
9535           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9536         fromX = currentMoveString[0] - AAA;
9537         fromY = currentMoveString[1] - ONE;
9538         toX = currentMoveString[2] - AAA;
9539         toY = currentMoveString[3] - ONE;
9540         promoChar = currentMoveString[4];
9541         break;
9542
9543       case WhiteDrop:
9544       case BlackDrop:
9545         if (appData.debugMode)
9546           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9547         fromX = moveType == WhiteDrop ?
9548           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9549         (int) CharToPiece(ToLower(currentMoveString[0]));
9550         fromY = DROP_RANK;
9551         toX = currentMoveString[2] - AAA;
9552         toY = currentMoveString[3] - ONE;
9553         break;
9554
9555       case WhiteWins:
9556       case BlackWins:
9557       case GameIsDrawn:
9558       case GameUnfinished:
9559         if (appData.debugMode)
9560           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9561         p = strchr(yy_text, '{');
9562         if (p == NULL) p = strchr(yy_text, '(');
9563         if (p == NULL) {
9564             p = yy_text;
9565             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9566         } else {
9567             q = strchr(p, *p == '{' ? '}' : ')');
9568             if (q != NULL) *q = NULLCHAR;
9569             p++;
9570         }
9571         GameEnds(moveType, p, GE_FILE);
9572         done = TRUE;
9573         if (cmailMsgLoaded) {
9574             ClearHighlights();
9575             flipView = WhiteOnMove(currentMove);
9576             if (moveType == GameUnfinished) flipView = !flipView;
9577             if (appData.debugMode)
9578               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9579         }
9580         break;
9581
9582       case (ChessMove) 0:       /* end of file */
9583         if (appData.debugMode)
9584           fprintf(debugFP, "Parser hit end of file\n");
9585         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9586           case MT_NONE:
9587           case MT_CHECK:
9588             break;
9589           case MT_CHECKMATE:
9590           case MT_STAINMATE:
9591             if (WhiteOnMove(currentMove)) {
9592                 GameEnds(BlackWins, "Black mates", GE_FILE);
9593             } else {
9594                 GameEnds(WhiteWins, "White mates", GE_FILE);
9595             }
9596             break;
9597           case MT_STALEMATE:
9598             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9599             break;
9600         }
9601         done = TRUE;
9602         break;
9603
9604       case MoveNumberOne:
9605         if (lastLoadGameStart == GNUChessGame) {
9606             /* GNUChessGames have numbers, but they aren't move numbers */
9607             if (appData.debugMode)
9608               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9609                       yy_text, (int) moveType);
9610             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9611         }
9612         /* else fall thru */
9613
9614       case XBoardGame:
9615       case GNUChessGame:
9616       case PGNTag:
9617         /* Reached start of next game in file */
9618         if (appData.debugMode)
9619           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9620         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9621           case MT_NONE:
9622           case MT_CHECK:
9623             break;
9624           case MT_CHECKMATE:
9625           case MT_STAINMATE:
9626             if (WhiteOnMove(currentMove)) {
9627                 GameEnds(BlackWins, "Black mates", GE_FILE);
9628             } else {
9629                 GameEnds(WhiteWins, "White mates", GE_FILE);
9630             }
9631             break;
9632           case MT_STALEMATE:
9633             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9634             break;
9635         }
9636         done = TRUE;
9637         break;
9638
9639       case PositionDiagram:     /* should not happen; ignore */
9640       case ElapsedTime:         /* ignore */
9641       case NAG:                 /* ignore */
9642         if (appData.debugMode)
9643           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9644                   yy_text, (int) moveType);
9645         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9646
9647       case IllegalMove:
9648         if (appData.testLegality) {
9649             if (appData.debugMode)
9650               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9651             sprintf(move, _("Illegal move: %d.%s%s"),
9652                     (forwardMostMove / 2) + 1,
9653                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9654             DisplayError(move, 0);
9655             done = TRUE;
9656         } else {
9657             if (appData.debugMode)
9658               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9659                       yy_text, currentMoveString);
9660             fromX = currentMoveString[0] - AAA;
9661             fromY = currentMoveString[1] - ONE;
9662             toX = currentMoveString[2] - AAA;
9663             toY = currentMoveString[3] - ONE;
9664             promoChar = currentMoveString[4];
9665         }
9666         break;
9667
9668       case AmbiguousMove:
9669         if (appData.debugMode)
9670           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9671         sprintf(move, _("Ambiguous move: %d.%s%s"),
9672                 (forwardMostMove / 2) + 1,
9673                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9674         DisplayError(move, 0);
9675         done = TRUE;
9676         break;
9677
9678       default:
9679       case ImpossibleMove:
9680         if (appData.debugMode)
9681           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9682         sprintf(move, _("Illegal move: %d.%s%s"),
9683                 (forwardMostMove / 2) + 1,
9684                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9685         DisplayError(move, 0);
9686         done = TRUE;
9687         break;
9688     }
9689
9690     if (done) {
9691         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9692             DrawPosition(FALSE, boards[currentMove]);
9693             DisplayBothClocks();
9694             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9695               DisplayComment(currentMove - 1, commentList[currentMove]);
9696         }
9697         (void) StopLoadGameTimer();
9698         gameFileFP = NULL;
9699         cmailOldMove = forwardMostMove;
9700         return FALSE;
9701     } else {
9702         /* currentMoveString is set as a side-effect of yylex */
9703         strcat(currentMoveString, "\n");
9704         strcpy(moveList[forwardMostMove], currentMoveString);
9705         
9706         thinkOutput[0] = NULLCHAR;
9707         MakeMove(fromX, fromY, toX, toY, promoChar);
9708         currentMove = forwardMostMove;
9709         return TRUE;
9710     }
9711 }
9712
9713 /* Load the nth game from the given file */
9714 int
9715 LoadGameFromFile(filename, n, title, useList)
9716      char *filename;
9717      int n;
9718      char *title;
9719      /*Boolean*/ int useList;
9720 {
9721     FILE *f;
9722     char buf[MSG_SIZ];
9723
9724     if (strcmp(filename, "-") == 0) {
9725         f = stdin;
9726         title = "stdin";
9727     } else {
9728         f = fopen(filename, "rb");
9729         if (f == NULL) {
9730           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9731             DisplayError(buf, errno);
9732             return FALSE;
9733         }
9734     }
9735     if (fseek(f, 0, 0) == -1) {
9736         /* f is not seekable; probably a pipe */
9737         useList = FALSE;
9738     }
9739     if (useList && n == 0) {
9740         int error = GameListBuild(f);
9741         if (error) {
9742             DisplayError(_("Cannot build game list"), error);
9743         } else if (!ListEmpty(&gameList) &&
9744                    ((ListGame *) gameList.tailPred)->number > 1) {
9745             GameListPopUp(f, title);
9746             return TRUE;
9747         }
9748         GameListDestroy();
9749         n = 1;
9750     }
9751     if (n == 0) n = 1;
9752     return LoadGame(f, n, title, FALSE);
9753 }
9754
9755
9756 void
9757 MakeRegisteredMove()
9758 {
9759     int fromX, fromY, toX, toY;
9760     char promoChar;
9761     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9762         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9763           case CMAIL_MOVE:
9764           case CMAIL_DRAW:
9765             if (appData.debugMode)
9766               fprintf(debugFP, "Restoring %s for game %d\n",
9767                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9768     
9769             thinkOutput[0] = NULLCHAR;
9770             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9771             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9772             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9773             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9774             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9775             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9776             MakeMove(fromX, fromY, toX, toY, promoChar);
9777             ShowMove(fromX, fromY, toX, toY);
9778               
9779             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9780               case MT_NONE:
9781               case MT_CHECK:
9782                 break;
9783                 
9784               case MT_CHECKMATE:
9785               case MT_STAINMATE:
9786                 if (WhiteOnMove(currentMove)) {
9787                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9788                 } else {
9789                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9790                 }
9791                 break;
9792                 
9793               case MT_STALEMATE:
9794                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9795                 break;
9796             }
9797
9798             break;
9799             
9800           case CMAIL_RESIGN:
9801             if (WhiteOnMove(currentMove)) {
9802                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9803             } else {
9804                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9805             }
9806             break;
9807             
9808           case CMAIL_ACCEPT:
9809             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9810             break;
9811               
9812           default:
9813             break;
9814         }
9815     }
9816
9817     return;
9818 }
9819
9820 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9821 int
9822 CmailLoadGame(f, gameNumber, title, useList)
9823      FILE *f;
9824      int gameNumber;
9825      char *title;
9826      int useList;
9827 {
9828     int retVal;
9829
9830     if (gameNumber > nCmailGames) {
9831         DisplayError(_("No more games in this message"), 0);
9832         return FALSE;
9833     }
9834     if (f == lastLoadGameFP) {
9835         int offset = gameNumber - lastLoadGameNumber;
9836         if (offset == 0) {
9837             cmailMsg[0] = NULLCHAR;
9838             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9839                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9840                 nCmailMovesRegistered--;
9841             }
9842             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9843             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9844                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9845             }
9846         } else {
9847             if (! RegisterMove()) return FALSE;
9848         }
9849     }
9850
9851     retVal = LoadGame(f, gameNumber, title, useList);
9852
9853     /* Make move registered during previous look at this game, if any */
9854     MakeRegisteredMove();
9855
9856     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9857         commentList[currentMove]
9858           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9859         DisplayComment(currentMove - 1, commentList[currentMove]);
9860     }
9861
9862     return retVal;
9863 }
9864
9865 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9866 int
9867 ReloadGame(offset)
9868      int offset;
9869 {
9870     int gameNumber = lastLoadGameNumber + offset;
9871     if (lastLoadGameFP == NULL) {
9872         DisplayError(_("No game has been loaded yet"), 0);
9873         return FALSE;
9874     }
9875     if (gameNumber <= 0) {
9876         DisplayError(_("Can't back up any further"), 0);
9877         return FALSE;
9878     }
9879     if (cmailMsgLoaded) {
9880         return CmailLoadGame(lastLoadGameFP, gameNumber,
9881                              lastLoadGameTitle, lastLoadGameUseList);
9882     } else {
9883         return LoadGame(lastLoadGameFP, gameNumber,
9884                         lastLoadGameTitle, lastLoadGameUseList);
9885     }
9886 }
9887
9888
9889
9890 /* Load the nth game from open file f */
9891 int
9892 LoadGame(f, gameNumber, title, useList)
9893      FILE *f;
9894      int gameNumber;
9895      char *title;
9896      int useList;
9897 {
9898     ChessMove cm;
9899     char buf[MSG_SIZ];
9900     int gn = gameNumber;
9901     ListGame *lg = NULL;
9902     int numPGNTags = 0;
9903     int err;
9904     GameMode oldGameMode;
9905     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9906
9907     if (appData.debugMode) 
9908         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9909
9910     if (gameMode == Training )
9911         SetTrainingModeOff();
9912
9913     oldGameMode = gameMode;
9914     if (gameMode != BeginningOfGame) {
9915       Reset(FALSE, TRUE);
9916     }
9917
9918     gameFileFP = f;
9919     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9920         fclose(lastLoadGameFP);
9921     }
9922
9923     if (useList) {
9924         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9925         
9926         if (lg) {
9927             fseek(f, lg->offset, 0);
9928             GameListHighlight(gameNumber);
9929             gn = 1;
9930         }
9931         else {
9932             DisplayError(_("Game number out of range"), 0);
9933             return FALSE;
9934         }
9935     } else {
9936         GameListDestroy();
9937         if (fseek(f, 0, 0) == -1) {
9938             if (f == lastLoadGameFP ?
9939                 gameNumber == lastLoadGameNumber + 1 :
9940                 gameNumber == 1) {
9941                 gn = 1;
9942             } else {
9943                 DisplayError(_("Can't seek on game file"), 0);
9944                 return FALSE;
9945             }
9946         }
9947     }
9948     lastLoadGameFP = f;
9949     lastLoadGameNumber = gameNumber;
9950     strcpy(lastLoadGameTitle, title);
9951     lastLoadGameUseList = useList;
9952
9953     yynewfile(f);
9954
9955     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9956       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9957                 lg->gameInfo.black);
9958             DisplayTitle(buf);
9959     } else if (*title != NULLCHAR) {
9960         if (gameNumber > 1) {
9961             sprintf(buf, "%s %d", title, gameNumber);
9962             DisplayTitle(buf);
9963         } else {
9964             DisplayTitle(title);
9965         }
9966     }
9967
9968     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9969         gameMode = PlayFromGameFile;
9970         ModeHighlight();
9971     }
9972
9973     currentMove = forwardMostMove = backwardMostMove = 0;
9974     CopyBoard(boards[0], initialPosition);
9975     StopClocks();
9976
9977     /*
9978      * Skip the first gn-1 games in the file.
9979      * Also skip over anything that precedes an identifiable 
9980      * start of game marker, to avoid being confused by 
9981      * garbage at the start of the file.  Currently 
9982      * recognized start of game markers are the move number "1",
9983      * the pattern "gnuchess .* game", the pattern
9984      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9985      * A game that starts with one of the latter two patterns
9986      * will also have a move number 1, possibly
9987      * following a position diagram.
9988      * 5-4-02: Let's try being more lenient and allowing a game to
9989      * start with an unnumbered move.  Does that break anything?
9990      */
9991     cm = lastLoadGameStart = (ChessMove) 0;
9992     while (gn > 0) {
9993         yyboardindex = forwardMostMove;
9994         cm = (ChessMove) yylex();
9995         switch (cm) {
9996           case (ChessMove) 0:
9997             if (cmailMsgLoaded) {
9998                 nCmailGames = CMAIL_MAX_GAMES - gn;
9999             } else {
10000                 Reset(TRUE, TRUE);
10001                 DisplayError(_("Game not found in file"), 0);
10002             }
10003             return FALSE;
10004
10005           case GNUChessGame:
10006           case XBoardGame:
10007             gn--;
10008             lastLoadGameStart = cm;
10009             break;
10010             
10011           case MoveNumberOne:
10012             switch (lastLoadGameStart) {
10013               case GNUChessGame:
10014               case XBoardGame:
10015               case PGNTag:
10016                 break;
10017               case MoveNumberOne:
10018               case (ChessMove) 0:
10019                 gn--;           /* count this game */
10020                 lastLoadGameStart = cm;
10021                 break;
10022               default:
10023                 /* impossible */
10024                 break;
10025             }
10026             break;
10027
10028           case PGNTag:
10029             switch (lastLoadGameStart) {
10030               case GNUChessGame:
10031               case PGNTag:
10032               case MoveNumberOne:
10033               case (ChessMove) 0:
10034                 gn--;           /* count this game */
10035                 lastLoadGameStart = cm;
10036                 break;
10037               case XBoardGame:
10038                 lastLoadGameStart = cm; /* game counted already */
10039                 break;
10040               default:
10041                 /* impossible */
10042                 break;
10043             }
10044             if (gn > 0) {
10045                 do {
10046                     yyboardindex = forwardMostMove;
10047                     cm = (ChessMove) yylex();
10048                 } while (cm == PGNTag || cm == Comment);
10049             }
10050             break;
10051
10052           case WhiteWins:
10053           case BlackWins:
10054           case GameIsDrawn:
10055             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10056                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10057                     != CMAIL_OLD_RESULT) {
10058                     nCmailResults ++ ;
10059                     cmailResult[  CMAIL_MAX_GAMES
10060                                 - gn - 1] = CMAIL_OLD_RESULT;
10061                 }
10062             }
10063             break;
10064
10065           case NormalMove:
10066             /* Only a NormalMove can be at the start of a game
10067              * without a position diagram. */
10068             if (lastLoadGameStart == (ChessMove) 0) {
10069               gn--;
10070               lastLoadGameStart = MoveNumberOne;
10071             }
10072             break;
10073
10074           default:
10075             break;
10076         }
10077     }
10078     
10079     if (appData.debugMode)
10080       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10081
10082     if (cm == XBoardGame) {
10083         /* Skip any header junk before position diagram and/or move 1 */
10084         for (;;) {
10085             yyboardindex = forwardMostMove;
10086             cm = (ChessMove) yylex();
10087
10088             if (cm == (ChessMove) 0 ||
10089                 cm == GNUChessGame || cm == XBoardGame) {
10090                 /* Empty game; pretend end-of-file and handle later */
10091                 cm = (ChessMove) 0;
10092                 break;
10093             }
10094
10095             if (cm == MoveNumberOne || cm == PositionDiagram ||
10096                 cm == PGNTag || cm == Comment)
10097               break;
10098         }
10099     } else if (cm == GNUChessGame) {
10100         if (gameInfo.event != NULL) {
10101             free(gameInfo.event);
10102         }
10103         gameInfo.event = StrSave(yy_text);
10104     }   
10105
10106     startedFromSetupPosition = FALSE;
10107     while (cm == PGNTag) {
10108         if (appData.debugMode) 
10109           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10110         err = ParsePGNTag(yy_text, &gameInfo);
10111         if (!err) numPGNTags++;
10112
10113         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10114         if(gameInfo.variant != oldVariant) {
10115             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10116             InitPosition(TRUE);
10117             oldVariant = gameInfo.variant;
10118             if (appData.debugMode) 
10119               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10120         }
10121
10122
10123         if (gameInfo.fen != NULL) {
10124           Board initial_position;
10125           startedFromSetupPosition = TRUE;
10126           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10127             Reset(TRUE, TRUE);
10128             DisplayError(_("Bad FEN position in file"), 0);
10129             return FALSE;
10130           }
10131           CopyBoard(boards[0], initial_position);
10132           if (blackPlaysFirst) {
10133             currentMove = forwardMostMove = backwardMostMove = 1;
10134             CopyBoard(boards[1], initial_position);
10135             strcpy(moveList[0], "");
10136             strcpy(parseList[0], "");
10137             timeRemaining[0][1] = whiteTimeRemaining;
10138             timeRemaining[1][1] = blackTimeRemaining;
10139             if (commentList[0] != NULL) {
10140               commentList[1] = commentList[0];
10141               commentList[0] = NULL;
10142             }
10143           } else {
10144             currentMove = forwardMostMove = backwardMostMove = 0;
10145           }
10146           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10147           {   int i;
10148               initialRulePlies = FENrulePlies;
10149               for( i=0; i< nrCastlingRights; i++ )
10150                   initialRights[i] = initial_position[CASTLING][i];
10151           }
10152           yyboardindex = forwardMostMove;
10153           free(gameInfo.fen);
10154           gameInfo.fen = NULL;
10155         }
10156
10157         yyboardindex = forwardMostMove;
10158         cm = (ChessMove) yylex();
10159
10160         /* Handle comments interspersed among the tags */
10161         while (cm == Comment) {
10162             char *p;
10163             if (appData.debugMode) 
10164               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10165             p = yy_text;
10166             AppendComment(currentMove, p, FALSE);
10167             yyboardindex = forwardMostMove;
10168             cm = (ChessMove) yylex();
10169         }
10170     }
10171
10172     /* don't rely on existence of Event tag since if game was
10173      * pasted from clipboard the Event tag may not exist
10174      */
10175     if (numPGNTags > 0){
10176         char *tags;
10177         if (gameInfo.variant == VariantNormal) {
10178           gameInfo.variant = StringToVariant(gameInfo.event);
10179         }
10180         if (!matchMode) {
10181           if( appData.autoDisplayTags ) {
10182             tags = PGNTags(&gameInfo);
10183             TagsPopUp(tags, CmailMsg());
10184             free(tags);
10185           }
10186         }
10187     } else {
10188         /* Make something up, but don't display it now */
10189         SetGameInfo();
10190         TagsPopDown();
10191     }
10192
10193     if (cm == PositionDiagram) {
10194         int i, j;
10195         char *p;
10196         Board initial_position;
10197
10198         if (appData.debugMode)
10199           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10200
10201         if (!startedFromSetupPosition) {
10202             p = yy_text;
10203             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10204               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10205                 switch (*p) {
10206                   case '[':
10207                   case '-':
10208                   case ' ':
10209                   case '\t':
10210                   case '\n':
10211                   case '\r':
10212                     break;
10213                   default:
10214                     initial_position[i][j++] = CharToPiece(*p);
10215                     break;
10216                 }
10217             while (*p == ' ' || *p == '\t' ||
10218                    *p == '\n' || *p == '\r') p++;
10219         
10220             if (strncmp(p, "black", strlen("black"))==0)
10221               blackPlaysFirst = TRUE;
10222             else
10223               blackPlaysFirst = FALSE;
10224             startedFromSetupPosition = TRUE;
10225         
10226             CopyBoard(boards[0], initial_position);
10227             if (blackPlaysFirst) {
10228                 currentMove = forwardMostMove = backwardMostMove = 1;
10229                 CopyBoard(boards[1], initial_position);
10230                 strcpy(moveList[0], "");
10231                 strcpy(parseList[0], "");
10232                 timeRemaining[0][1] = whiteTimeRemaining;
10233                 timeRemaining[1][1] = blackTimeRemaining;
10234                 if (commentList[0] != NULL) {
10235                     commentList[1] = commentList[0];
10236                     commentList[0] = NULL;
10237                 }
10238             } else {
10239                 currentMove = forwardMostMove = backwardMostMove = 0;
10240             }
10241         }
10242         yyboardindex = forwardMostMove;
10243         cm = (ChessMove) yylex();
10244     }
10245
10246     if (first.pr == NoProc) {
10247         StartChessProgram(&first);
10248     }
10249     InitChessProgram(&first, FALSE);
10250     SendToProgram("force\n", &first);
10251     if (startedFromSetupPosition) {
10252         SendBoard(&first, forwardMostMove);
10253     if (appData.debugMode) {
10254         fprintf(debugFP, "Load Game\n");
10255     }
10256         DisplayBothClocks();
10257     }      
10258
10259     /* [HGM] server: flag to write setup moves in broadcast file as one */
10260     loadFlag = appData.suppressLoadMoves;
10261
10262     while (cm == Comment) {
10263         char *p;
10264         if (appData.debugMode) 
10265           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10266         p = yy_text;
10267         AppendComment(currentMove, p, FALSE);
10268         yyboardindex = forwardMostMove;
10269         cm = (ChessMove) yylex();
10270     }
10271
10272     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10273         cm == WhiteWins || cm == BlackWins ||
10274         cm == GameIsDrawn || cm == GameUnfinished) {
10275         DisplayMessage("", _("No moves in game"));
10276         if (cmailMsgLoaded) {
10277             if (appData.debugMode)
10278               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10279             ClearHighlights();
10280             flipView = FALSE;
10281         }
10282         DrawPosition(FALSE, boards[currentMove]);
10283         DisplayBothClocks();
10284         gameMode = EditGame;
10285         ModeHighlight();
10286         gameFileFP = NULL;
10287         cmailOldMove = 0;
10288         return TRUE;
10289     }
10290
10291     // [HGM] PV info: routine tests if comment empty
10292     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10293         DisplayComment(currentMove - 1, commentList[currentMove]);
10294     }
10295     if (!matchMode && appData.timeDelay != 0) 
10296       DrawPosition(FALSE, boards[currentMove]);
10297
10298     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10299       programStats.ok_to_send = 1;
10300     }
10301
10302     /* if the first token after the PGN tags is a move
10303      * and not move number 1, retrieve it from the parser 
10304      */
10305     if (cm != MoveNumberOne)
10306         LoadGameOneMove(cm);
10307
10308     /* load the remaining moves from the file */
10309     while (LoadGameOneMove((ChessMove)0)) {
10310       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10311       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10312     }
10313
10314     /* rewind to the start of the game */
10315     currentMove = backwardMostMove;
10316
10317     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10318
10319     if (oldGameMode == AnalyzeFile ||
10320         oldGameMode == AnalyzeMode) {
10321       AnalyzeFileEvent();
10322     }
10323
10324     if (matchMode || appData.timeDelay == 0) {
10325       ToEndEvent();
10326       gameMode = EditGame;
10327       ModeHighlight();
10328     } else if (appData.timeDelay > 0) {
10329       AutoPlayGameLoop();
10330     }
10331
10332     if (appData.debugMode) 
10333         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10334
10335     loadFlag = 0; /* [HGM] true game starts */
10336     return TRUE;
10337 }
10338
10339 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10340 int
10341 ReloadPosition(offset)
10342      int offset;
10343 {
10344     int positionNumber = lastLoadPositionNumber + offset;
10345     if (lastLoadPositionFP == NULL) {
10346         DisplayError(_("No position has been loaded yet"), 0);
10347         return FALSE;
10348     }
10349     if (positionNumber <= 0) {
10350         DisplayError(_("Can't back up any further"), 0);
10351         return FALSE;
10352     }
10353     return LoadPosition(lastLoadPositionFP, positionNumber,
10354                         lastLoadPositionTitle);
10355 }
10356
10357 /* Load the nth position from the given file */
10358 int
10359 LoadPositionFromFile(filename, n, title)
10360      char *filename;
10361      int n;
10362      char *title;
10363 {
10364     FILE *f;
10365     char buf[MSG_SIZ];
10366
10367     if (strcmp(filename, "-") == 0) {
10368         return LoadPosition(stdin, n, "stdin");
10369     } else {
10370         f = fopen(filename, "rb");
10371         if (f == NULL) {
10372             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10373             DisplayError(buf, errno);
10374             return FALSE;
10375         } else {
10376             return LoadPosition(f, n, title);
10377         }
10378     }
10379 }
10380
10381 /* Load the nth position from the given open file, and close it */
10382 int
10383 LoadPosition(f, positionNumber, title)
10384      FILE *f;
10385      int positionNumber;
10386      char *title;
10387 {
10388     char *p, line[MSG_SIZ];
10389     Board initial_position;
10390     int i, j, fenMode, pn;
10391     
10392     if (gameMode == Training )
10393         SetTrainingModeOff();
10394
10395     if (gameMode != BeginningOfGame) {
10396         Reset(FALSE, TRUE);
10397     }
10398     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10399         fclose(lastLoadPositionFP);
10400     }
10401     if (positionNumber == 0) positionNumber = 1;
10402     lastLoadPositionFP = f;
10403     lastLoadPositionNumber = positionNumber;
10404     strcpy(lastLoadPositionTitle, title);
10405     if (first.pr == NoProc) {
10406       StartChessProgram(&first);
10407       InitChessProgram(&first, FALSE);
10408     }    
10409     pn = positionNumber;
10410     if (positionNumber < 0) {
10411         /* Negative position number means to seek to that byte offset */
10412         if (fseek(f, -positionNumber, 0) == -1) {
10413             DisplayError(_("Can't seek on position file"), 0);
10414             return FALSE;
10415         };
10416         pn = 1;
10417     } else {
10418         if (fseek(f, 0, 0) == -1) {
10419             if (f == lastLoadPositionFP ?
10420                 positionNumber == lastLoadPositionNumber + 1 :
10421                 positionNumber == 1) {
10422                 pn = 1;
10423             } else {
10424                 DisplayError(_("Can't seek on position file"), 0);
10425                 return FALSE;
10426             }
10427         }
10428     }
10429     /* See if this file is FEN or old-style xboard */
10430     if (fgets(line, MSG_SIZ, f) == NULL) {
10431         DisplayError(_("Position not found in file"), 0);
10432         return FALSE;
10433     }
10434     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10435     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10436
10437     if (pn >= 2) {
10438         if (fenMode || line[0] == '#') pn--;
10439         while (pn > 0) {
10440             /* skip positions before number pn */
10441             if (fgets(line, MSG_SIZ, f) == NULL) {
10442                 Reset(TRUE, TRUE);
10443                 DisplayError(_("Position not found in file"), 0);
10444                 return FALSE;
10445             }
10446             if (fenMode || line[0] == '#') pn--;
10447         }
10448     }
10449
10450     if (fenMode) {
10451         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10452             DisplayError(_("Bad FEN position in file"), 0);
10453             return FALSE;
10454         }
10455     } else {
10456         (void) fgets(line, MSG_SIZ, f);
10457         (void) fgets(line, MSG_SIZ, f);
10458     
10459         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10460             (void) fgets(line, MSG_SIZ, f);
10461             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10462                 if (*p == ' ')
10463                   continue;
10464                 initial_position[i][j++] = CharToPiece(*p);
10465             }
10466         }
10467     
10468         blackPlaysFirst = FALSE;
10469         if (!feof(f)) {
10470             (void) fgets(line, MSG_SIZ, f);
10471             if (strncmp(line, "black", strlen("black"))==0)
10472               blackPlaysFirst = TRUE;
10473         }
10474     }
10475     startedFromSetupPosition = TRUE;
10476     
10477     SendToProgram("force\n", &first);
10478     CopyBoard(boards[0], initial_position);
10479     if (blackPlaysFirst) {
10480         currentMove = forwardMostMove = backwardMostMove = 1;
10481         strcpy(moveList[0], "");
10482         strcpy(parseList[0], "");
10483         CopyBoard(boards[1], initial_position);
10484         DisplayMessage("", _("Black to play"));
10485     } else {
10486         currentMove = forwardMostMove = backwardMostMove = 0;
10487         DisplayMessage("", _("White to play"));
10488     }
10489     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10490     SendBoard(&first, forwardMostMove);
10491     if (appData.debugMode) {
10492 int i, j;
10493   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10494   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10495         fprintf(debugFP, "Load Position\n");
10496     }
10497
10498     if (positionNumber > 1) {
10499         sprintf(line, "%s %d", title, positionNumber);
10500         DisplayTitle(line);
10501     } else {
10502         DisplayTitle(title);
10503     }
10504     gameMode = EditGame;
10505     ModeHighlight();
10506     ResetClocks();
10507     timeRemaining[0][1] = whiteTimeRemaining;
10508     timeRemaining[1][1] = blackTimeRemaining;
10509     DrawPosition(FALSE, boards[currentMove]);
10510    
10511     return TRUE;
10512 }
10513
10514
10515 void
10516 CopyPlayerNameIntoFileName(dest, src)
10517      char **dest, *src;
10518 {
10519     while (*src != NULLCHAR && *src != ',') {
10520         if (*src == ' ') {
10521             *(*dest)++ = '_';
10522             src++;
10523         } else {
10524             *(*dest)++ = *src++;
10525         }
10526     }
10527 }
10528
10529 char *DefaultFileName(ext)
10530      char *ext;
10531 {
10532     static char def[MSG_SIZ];
10533     char *p;
10534
10535     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10536         p = def;
10537         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10538         *p++ = '-';
10539         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10540         *p++ = '.';
10541         strcpy(p, ext);
10542     } else {
10543         def[0] = NULLCHAR;
10544     }
10545     return def;
10546 }
10547
10548 /* Save the current game to the given file */
10549 int
10550 SaveGameToFile(filename, append)
10551      char *filename;
10552      int append;
10553 {
10554     FILE *f;
10555     char buf[MSG_SIZ];
10556
10557     if (strcmp(filename, "-") == 0) {
10558         return SaveGame(stdout, 0, NULL);
10559     } else {
10560         f = fopen(filename, append ? "a" : "w");
10561         if (f == NULL) {
10562             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10563             DisplayError(buf, errno);
10564             return FALSE;
10565         } else {
10566             return SaveGame(f, 0, NULL);
10567         }
10568     }
10569 }
10570
10571 char *
10572 SavePart(str)
10573      char *str;
10574 {
10575     static char buf[MSG_SIZ];
10576     char *p;
10577     
10578     p = strchr(str, ' ');
10579     if (p == NULL) return str;
10580     strncpy(buf, str, p - str);
10581     buf[p - str] = NULLCHAR;
10582     return buf;
10583 }
10584
10585 #define PGN_MAX_LINE 75
10586
10587 #define PGN_SIDE_WHITE  0
10588 #define PGN_SIDE_BLACK  1
10589
10590 /* [AS] */
10591 static int FindFirstMoveOutOfBook( int side )
10592 {
10593     int result = -1;
10594
10595     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10596         int index = backwardMostMove;
10597         int has_book_hit = 0;
10598
10599         if( (index % 2) != side ) {
10600             index++;
10601         }
10602
10603         while( index < forwardMostMove ) {
10604             /* Check to see if engine is in book */
10605             int depth = pvInfoList[index].depth;
10606             int score = pvInfoList[index].score;
10607             int in_book = 0;
10608
10609             if( depth <= 2 ) {
10610                 in_book = 1;
10611             }
10612             else if( score == 0 && depth == 63 ) {
10613                 in_book = 1; /* Zappa */
10614             }
10615             else if( score == 2 && depth == 99 ) {
10616                 in_book = 1; /* Abrok */
10617             }
10618
10619             has_book_hit += in_book;
10620
10621             if( ! in_book ) {
10622                 result = index;
10623
10624                 break;
10625             }
10626
10627             index += 2;
10628         }
10629     }
10630
10631     return result;
10632 }
10633
10634 /* [AS] */
10635 void GetOutOfBookInfo( char * buf )
10636 {
10637     int oob[2];
10638     int i;
10639     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10640
10641     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10642     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10643
10644     *buf = '\0';
10645
10646     if( oob[0] >= 0 || oob[1] >= 0 ) {
10647         for( i=0; i<2; i++ ) {
10648             int idx = oob[i];
10649
10650             if( idx >= 0 ) {
10651                 if( i > 0 && oob[0] >= 0 ) {
10652                     strcat( buf, "   " );
10653                 }
10654
10655                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10656                 sprintf( buf+strlen(buf), "%s%.2f", 
10657                     pvInfoList[idx].score >= 0 ? "+" : "",
10658                     pvInfoList[idx].score / 100.0 );
10659             }
10660         }
10661     }
10662 }
10663
10664 /* Save game in PGN style and close the file */
10665 int
10666 SaveGamePGN(f)
10667      FILE *f;
10668 {
10669     int i, offset, linelen, newblock;
10670     time_t tm;
10671 //    char *movetext;
10672     char numtext[32];
10673     int movelen, numlen, blank;
10674     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10675
10676     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10677     
10678     tm = time((time_t *) NULL);
10679     
10680     PrintPGNTags(f, &gameInfo);
10681     
10682     if (backwardMostMove > 0 || startedFromSetupPosition) {
10683         char *fen = PositionToFEN(backwardMostMove, NULL);
10684         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10685         fprintf(f, "\n{--------------\n");
10686         PrintPosition(f, backwardMostMove);
10687         fprintf(f, "--------------}\n");
10688         free(fen);
10689     }
10690     else {
10691         /* [AS] Out of book annotation */
10692         if( appData.saveOutOfBookInfo ) {
10693             char buf[64];
10694
10695             GetOutOfBookInfo( buf );
10696
10697             if( buf[0] != '\0' ) {
10698                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10699             }
10700         }
10701
10702         fprintf(f, "\n");
10703     }
10704
10705     i = backwardMostMove;
10706     linelen = 0;
10707     newblock = TRUE;
10708
10709     while (i < forwardMostMove) {
10710         /* Print comments preceding this move */
10711         if (commentList[i] != NULL) {
10712             if (linelen > 0) fprintf(f, "\n");
10713             fprintf(f, "%s", commentList[i]);
10714             linelen = 0;
10715             newblock = TRUE;
10716         }
10717
10718         /* Format move number */
10719         if ((i % 2) == 0) {
10720             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10721         } else {
10722             if (newblock) {
10723                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10724             } else {
10725                 numtext[0] = NULLCHAR;
10726             }
10727         }
10728         numlen = strlen(numtext);
10729         newblock = FALSE;
10730
10731         /* Print move number */
10732         blank = linelen > 0 && numlen > 0;
10733         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10734             fprintf(f, "\n");
10735             linelen = 0;
10736             blank = 0;
10737         }
10738         if (blank) {
10739             fprintf(f, " ");
10740             linelen++;
10741         }
10742         fprintf(f, "%s", numtext);
10743         linelen += numlen;
10744
10745         /* Get move */
10746         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10747         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10748
10749         /* Print move */
10750         blank = linelen > 0 && movelen > 0;
10751         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10752             fprintf(f, "\n");
10753             linelen = 0;
10754             blank = 0;
10755         }
10756         if (blank) {
10757             fprintf(f, " ");
10758             linelen++;
10759         }
10760         fprintf(f, "%s", move_buffer);
10761         linelen += movelen;
10762
10763         /* [AS] Add PV info if present */
10764         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10765             /* [HGM] add time */
10766             char buf[MSG_SIZ]; int seconds;
10767
10768             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10769
10770             if( seconds <= 0) buf[0] = 0; else
10771             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10772                 seconds = (seconds + 4)/10; // round to full seconds
10773                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10774                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10775             }
10776
10777             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10778                 pvInfoList[i].score >= 0 ? "+" : "",
10779                 pvInfoList[i].score / 100.0,
10780                 pvInfoList[i].depth,
10781                 buf );
10782
10783             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10784
10785             /* Print score/depth */
10786             blank = linelen > 0 && movelen > 0;
10787             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10788                 fprintf(f, "\n");
10789                 linelen = 0;
10790                 blank = 0;
10791             }
10792             if (blank) {
10793                 fprintf(f, " ");
10794                 linelen++;
10795             }
10796             fprintf(f, "%s", move_buffer);
10797             linelen += movelen;
10798         }
10799
10800         i++;
10801     }
10802     
10803     /* Start a new line */
10804     if (linelen > 0) fprintf(f, "\n");
10805
10806     /* Print comments after last move */
10807     if (commentList[i] != NULL) {
10808         fprintf(f, "%s\n", commentList[i]);
10809     }
10810
10811     /* Print result */
10812     if (gameInfo.resultDetails != NULL &&
10813         gameInfo.resultDetails[0] != NULLCHAR) {
10814         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10815                 PGNResult(gameInfo.result));
10816     } else {
10817         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10818     }
10819
10820     fclose(f);
10821     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10822     return TRUE;
10823 }
10824
10825 /* Save game in old style and close the file */
10826 int
10827 SaveGameOldStyle(f)
10828      FILE *f;
10829 {
10830     int i, offset;
10831     time_t tm;
10832     
10833     tm = time((time_t *) NULL);
10834     
10835     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10836     PrintOpponents(f);
10837     
10838     if (backwardMostMove > 0 || startedFromSetupPosition) {
10839         fprintf(f, "\n[--------------\n");
10840         PrintPosition(f, backwardMostMove);
10841         fprintf(f, "--------------]\n");
10842     } else {
10843         fprintf(f, "\n");
10844     }
10845
10846     i = backwardMostMove;
10847     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10848
10849     while (i < forwardMostMove) {
10850         if (commentList[i] != NULL) {
10851             fprintf(f, "[%s]\n", commentList[i]);
10852         }
10853
10854         if ((i % 2) == 1) {
10855             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10856             i++;
10857         } else {
10858             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10859             i++;
10860             if (commentList[i] != NULL) {
10861                 fprintf(f, "\n");
10862                 continue;
10863             }
10864             if (i >= forwardMostMove) {
10865                 fprintf(f, "\n");
10866                 break;
10867             }
10868             fprintf(f, "%s\n", parseList[i]);
10869             i++;
10870         }
10871     }
10872     
10873     if (commentList[i] != NULL) {
10874         fprintf(f, "[%s]\n", commentList[i]);
10875     }
10876
10877     /* This isn't really the old style, but it's close enough */
10878     if (gameInfo.resultDetails != NULL &&
10879         gameInfo.resultDetails[0] != NULLCHAR) {
10880         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10881                 gameInfo.resultDetails);
10882     } else {
10883         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10884     }
10885
10886     fclose(f);
10887     return TRUE;
10888 }
10889
10890 /* Save the current game to open file f and close the file */
10891 int
10892 SaveGame(f, dummy, dummy2)
10893      FILE *f;
10894      int dummy;
10895      char *dummy2;
10896 {
10897     if (gameMode == EditPosition) EditPositionDone(TRUE);
10898     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10899     if (appData.oldSaveStyle)
10900       return SaveGameOldStyle(f);
10901     else
10902       return SaveGamePGN(f);
10903 }
10904
10905 /* Save the current position to the given file */
10906 int
10907 SavePositionToFile(filename)
10908      char *filename;
10909 {
10910     FILE *f;
10911     char buf[MSG_SIZ];
10912
10913     if (strcmp(filename, "-") == 0) {
10914         return SavePosition(stdout, 0, NULL);
10915     } else {
10916         f = fopen(filename, "a");
10917         if (f == NULL) {
10918             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10919             DisplayError(buf, errno);
10920             return FALSE;
10921         } else {
10922             SavePosition(f, 0, NULL);
10923             return TRUE;
10924         }
10925     }
10926 }
10927
10928 /* Save the current position to the given open file and close the file */
10929 int
10930 SavePosition(f, dummy, dummy2)
10931      FILE *f;
10932      int dummy;
10933      char *dummy2;
10934 {
10935     time_t tm;
10936     char *fen;
10937     
10938     if (gameMode == EditPosition) EditPositionDone(TRUE);
10939     if (appData.oldSaveStyle) {
10940         tm = time((time_t *) NULL);
10941     
10942         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10943         PrintOpponents(f);
10944         fprintf(f, "[--------------\n");
10945         PrintPosition(f, currentMove);
10946         fprintf(f, "--------------]\n");
10947     } else {
10948         fen = PositionToFEN(currentMove, NULL);
10949         fprintf(f, "%s\n", fen);
10950         free(fen);
10951     }
10952     fclose(f);
10953     return TRUE;
10954 }
10955
10956 void
10957 ReloadCmailMsgEvent(unregister)
10958      int unregister;
10959 {
10960 #if !WIN32
10961     static char *inFilename = NULL;
10962     static char *outFilename;
10963     int i;
10964     struct stat inbuf, outbuf;
10965     int status;
10966     
10967     /* Any registered moves are unregistered if unregister is set, */
10968     /* i.e. invoked by the signal handler */
10969     if (unregister) {
10970         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10971             cmailMoveRegistered[i] = FALSE;
10972             if (cmailCommentList[i] != NULL) {
10973                 free(cmailCommentList[i]);
10974                 cmailCommentList[i] = NULL;
10975             }
10976         }
10977         nCmailMovesRegistered = 0;
10978     }
10979
10980     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10981         cmailResult[i] = CMAIL_NOT_RESULT;
10982     }
10983     nCmailResults = 0;
10984
10985     if (inFilename == NULL) {
10986         /* Because the filenames are static they only get malloced once  */
10987         /* and they never get freed                                      */
10988         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10989         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10990
10991         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10992         sprintf(outFilename, "%s.out", appData.cmailGameName);
10993     }
10994     
10995     status = stat(outFilename, &outbuf);
10996     if (status < 0) {
10997         cmailMailedMove = FALSE;
10998     } else {
10999         status = stat(inFilename, &inbuf);
11000         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11001     }
11002     
11003     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11004        counts the games, notes how each one terminated, etc.
11005        
11006        It would be nice to remove this kludge and instead gather all
11007        the information while building the game list.  (And to keep it
11008        in the game list nodes instead of having a bunch of fixed-size
11009        parallel arrays.)  Note this will require getting each game's
11010        termination from the PGN tags, as the game list builder does
11011        not process the game moves.  --mann
11012        */
11013     cmailMsgLoaded = TRUE;
11014     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11015     
11016     /* Load first game in the file or popup game menu */
11017     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11018
11019 #endif /* !WIN32 */
11020     return;
11021 }
11022
11023 int
11024 RegisterMove()
11025 {
11026     FILE *f;
11027     char string[MSG_SIZ];
11028
11029     if (   cmailMailedMove
11030         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11031         return TRUE;            /* Allow free viewing  */
11032     }
11033
11034     /* Unregister move to ensure that we don't leave RegisterMove        */
11035     /* with the move registered when the conditions for registering no   */
11036     /* longer hold                                                       */
11037     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11038         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11039         nCmailMovesRegistered --;
11040
11041         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11042           {
11043               free(cmailCommentList[lastLoadGameNumber - 1]);
11044               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11045           }
11046     }
11047
11048     if (cmailOldMove == -1) {
11049         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11050         return FALSE;
11051     }
11052
11053     if (currentMove > cmailOldMove + 1) {
11054         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11055         return FALSE;
11056     }
11057
11058     if (currentMove < cmailOldMove) {
11059         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11060         return FALSE;
11061     }
11062
11063     if (forwardMostMove > currentMove) {
11064         /* Silently truncate extra moves */
11065         TruncateGame();
11066     }
11067
11068     if (   (currentMove == cmailOldMove + 1)
11069         || (   (currentMove == cmailOldMove)
11070             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11071                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11072         if (gameInfo.result != GameUnfinished) {
11073             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11074         }
11075
11076         if (commentList[currentMove] != NULL) {
11077             cmailCommentList[lastLoadGameNumber - 1]
11078               = StrSave(commentList[currentMove]);
11079         }
11080         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11081
11082         if (appData.debugMode)
11083           fprintf(debugFP, "Saving %s for game %d\n",
11084                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11085
11086         sprintf(string,
11087                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11088         
11089         f = fopen(string, "w");
11090         if (appData.oldSaveStyle) {
11091             SaveGameOldStyle(f); /* also closes the file */
11092             
11093             sprintf(string, "%s.pos.out", appData.cmailGameName);
11094             f = fopen(string, "w");
11095             SavePosition(f, 0, NULL); /* also closes the file */
11096         } else {
11097             fprintf(f, "{--------------\n");
11098             PrintPosition(f, currentMove);
11099             fprintf(f, "--------------}\n\n");
11100             
11101             SaveGame(f, 0, NULL); /* also closes the file*/
11102         }
11103         
11104         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11105         nCmailMovesRegistered ++;
11106     } else if (nCmailGames == 1) {
11107         DisplayError(_("You have not made a move yet"), 0);
11108         return FALSE;
11109     }
11110
11111     return TRUE;
11112 }
11113
11114 void
11115 MailMoveEvent()
11116 {
11117 #if !WIN32
11118     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11119     FILE *commandOutput;
11120     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11121     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11122     int nBuffers;
11123     int i;
11124     int archived;
11125     char *arcDir;
11126
11127     if (! cmailMsgLoaded) {
11128         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11129         return;
11130     }
11131
11132     if (nCmailGames == nCmailResults) {
11133         DisplayError(_("No unfinished games"), 0);
11134         return;
11135     }
11136
11137 #if CMAIL_PROHIBIT_REMAIL
11138     if (cmailMailedMove) {
11139         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);
11140         DisplayError(msg, 0);
11141         return;
11142     }
11143 #endif
11144
11145     if (! (cmailMailedMove || RegisterMove())) return;
11146     
11147     if (   cmailMailedMove
11148         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11149         sprintf(string, partCommandString,
11150                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11151         commandOutput = popen(string, "r");
11152
11153         if (commandOutput == NULL) {
11154             DisplayError(_("Failed to invoke cmail"), 0);
11155         } else {
11156             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11157                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11158             }
11159             if (nBuffers > 1) {
11160                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11161                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11162                 nBytes = MSG_SIZ - 1;
11163             } else {
11164                 (void) memcpy(msg, buffer, nBytes);
11165             }
11166             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11167
11168             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11169                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11170
11171                 archived = TRUE;
11172                 for (i = 0; i < nCmailGames; i ++) {
11173                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11174                         archived = FALSE;
11175                     }
11176                 }
11177                 if (   archived
11178                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11179                         != NULL)) {
11180                     sprintf(buffer, "%s/%s.%s.archive",
11181                             arcDir,
11182                             appData.cmailGameName,
11183                             gameInfo.date);
11184                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11185                     cmailMsgLoaded = FALSE;
11186                 }
11187             }
11188
11189             DisplayInformation(msg);
11190             pclose(commandOutput);
11191         }
11192     } else {
11193         if ((*cmailMsg) != '\0') {
11194             DisplayInformation(cmailMsg);
11195         }
11196     }
11197
11198     return;
11199 #endif /* !WIN32 */
11200 }
11201
11202 char *
11203 CmailMsg()
11204 {
11205 #if WIN32
11206     return NULL;
11207 #else
11208     int  prependComma = 0;
11209     char number[5];
11210     char string[MSG_SIZ];       /* Space for game-list */
11211     int  i;
11212     
11213     if (!cmailMsgLoaded) return "";
11214
11215     if (cmailMailedMove) {
11216         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11217     } else {
11218         /* Create a list of games left */
11219         sprintf(string, "[");
11220         for (i = 0; i < nCmailGames; i ++) {
11221             if (! (   cmailMoveRegistered[i]
11222                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11223                 if (prependComma) {
11224                     sprintf(number, ",%d", i + 1);
11225                 } else {
11226                     sprintf(number, "%d", i + 1);
11227                     prependComma = 1;
11228                 }
11229                 
11230                 strcat(string, number);
11231             }
11232         }
11233         strcat(string, "]");
11234
11235         if (nCmailMovesRegistered + nCmailResults == 0) {
11236             switch (nCmailGames) {
11237               case 1:
11238                 sprintf(cmailMsg,
11239                         _("Still need to make move for game\n"));
11240                 break;
11241                 
11242               case 2:
11243                 sprintf(cmailMsg,
11244                         _("Still need to make moves for both games\n"));
11245                 break;
11246                 
11247               default:
11248                 sprintf(cmailMsg,
11249                         _("Still need to make moves for all %d games\n"),
11250                         nCmailGames);
11251                 break;
11252             }
11253         } else {
11254             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11255               case 1:
11256                 sprintf(cmailMsg,
11257                         _("Still need to make a move for game %s\n"),
11258                         string);
11259                 break;
11260                 
11261               case 0:
11262                 if (nCmailResults == nCmailGames) {
11263                     sprintf(cmailMsg, _("No unfinished games\n"));
11264                 } else {
11265                     sprintf(cmailMsg, _("Ready to send mail\n"));
11266                 }
11267                 break;
11268                 
11269               default:
11270                 sprintf(cmailMsg,
11271                         _("Still need to make moves for games %s\n"),
11272                         string);
11273             }
11274         }
11275     }
11276     return cmailMsg;
11277 #endif /* WIN32 */
11278 }
11279
11280 void
11281 ResetGameEvent()
11282 {
11283     if (gameMode == Training)
11284       SetTrainingModeOff();
11285
11286     Reset(TRUE, TRUE);
11287     cmailMsgLoaded = FALSE;
11288     if (appData.icsActive) {
11289       SendToICS(ics_prefix);
11290       SendToICS("refresh\n");
11291     }
11292 }
11293
11294 void
11295 ExitEvent(status)
11296      int status;
11297 {
11298     exiting++;
11299     if (exiting > 2) {
11300       /* Give up on clean exit */
11301       exit(status);
11302     }
11303     if (exiting > 1) {
11304       /* Keep trying for clean exit */
11305       return;
11306     }
11307
11308     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11309
11310     if (telnetISR != NULL) {
11311       RemoveInputSource(telnetISR);
11312     }
11313     if (icsPR != NoProc) {
11314       DestroyChildProcess(icsPR, TRUE);
11315     }
11316
11317     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11318     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11319
11320     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11321     /* make sure this other one finishes before killing it!                  */
11322     if(endingGame) { int count = 0;
11323         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11324         while(endingGame && count++ < 10) DoSleep(1);
11325         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11326     }
11327
11328     /* Kill off chess programs */
11329     if (first.pr != NoProc) {
11330         ExitAnalyzeMode();
11331         
11332         DoSleep( appData.delayBeforeQuit );
11333         SendToProgram("quit\n", &first);
11334         DoSleep( appData.delayAfterQuit );
11335         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11336     }
11337     if (second.pr != NoProc) {
11338         DoSleep( appData.delayBeforeQuit );
11339         SendToProgram("quit\n", &second);
11340         DoSleep( appData.delayAfterQuit );
11341         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11342     }
11343     if (first.isr != NULL) {
11344         RemoveInputSource(first.isr);
11345     }
11346     if (second.isr != NULL) {
11347         RemoveInputSource(second.isr);
11348     }
11349
11350     ShutDownFrontEnd();
11351     exit(status);
11352 }
11353
11354 void
11355 PauseEvent()
11356 {
11357     if (appData.debugMode)
11358         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11359     if (pausing) {
11360         pausing = FALSE;
11361         ModeHighlight();
11362         if (gameMode == MachinePlaysWhite ||
11363             gameMode == MachinePlaysBlack) {
11364             StartClocks();
11365         } else {
11366             DisplayBothClocks();
11367         }
11368         if (gameMode == PlayFromGameFile) {
11369             if (appData.timeDelay >= 0) 
11370                 AutoPlayGameLoop();
11371         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11372             Reset(FALSE, TRUE);
11373             SendToICS(ics_prefix);
11374             SendToICS("refresh\n");
11375         } else if (currentMove < forwardMostMove) {
11376             ForwardInner(forwardMostMove);
11377         }
11378         pauseExamInvalid = FALSE;
11379     } else {
11380         switch (gameMode) {
11381           default:
11382             return;
11383           case IcsExamining:
11384             pauseExamForwardMostMove = forwardMostMove;
11385             pauseExamInvalid = FALSE;
11386             /* fall through */
11387           case IcsObserving:
11388           case IcsPlayingWhite:
11389           case IcsPlayingBlack:
11390             pausing = TRUE;
11391             ModeHighlight();
11392             return;
11393           case PlayFromGameFile:
11394             (void) StopLoadGameTimer();
11395             pausing = TRUE;
11396             ModeHighlight();
11397             break;
11398           case BeginningOfGame:
11399             if (appData.icsActive) return;
11400             /* else fall through */
11401           case MachinePlaysWhite:
11402           case MachinePlaysBlack:
11403           case TwoMachinesPlay:
11404             if (forwardMostMove == 0)
11405               return;           /* don't pause if no one has moved */
11406             if ((gameMode == MachinePlaysWhite &&
11407                  !WhiteOnMove(forwardMostMove)) ||
11408                 (gameMode == MachinePlaysBlack &&
11409                  WhiteOnMove(forwardMostMove))) {
11410                 StopClocks();
11411             }
11412             pausing = TRUE;
11413             ModeHighlight();
11414             break;
11415         }
11416     }
11417 }
11418
11419 void
11420 EditCommentEvent()
11421 {
11422     char title[MSG_SIZ];
11423
11424     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11425         strcpy(title, _("Edit comment"));
11426     } else {
11427         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11428                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11429                 parseList[currentMove - 1]);
11430     }
11431
11432     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11433 }
11434
11435
11436 void
11437 EditTagsEvent()
11438 {
11439     char *tags = PGNTags(&gameInfo);
11440     EditTagsPopUp(tags);
11441     free(tags);
11442 }
11443
11444 void
11445 AnalyzeModeEvent()
11446 {
11447     if (appData.noChessProgram || gameMode == AnalyzeMode)
11448       return;
11449
11450     if (gameMode != AnalyzeFile) {
11451         if (!appData.icsEngineAnalyze) {
11452                EditGameEvent();
11453                if (gameMode != EditGame) return;
11454         }
11455         ResurrectChessProgram();
11456         SendToProgram("analyze\n", &first);
11457         first.analyzing = TRUE;
11458         /*first.maybeThinking = TRUE;*/
11459         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11460         EngineOutputPopUp();
11461     }
11462     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11463     pausing = FALSE;
11464     ModeHighlight();
11465     SetGameInfo();
11466
11467     StartAnalysisClock();
11468     GetTimeMark(&lastNodeCountTime);
11469     lastNodeCount = 0;
11470 }
11471
11472 void
11473 AnalyzeFileEvent()
11474 {
11475     if (appData.noChessProgram || gameMode == AnalyzeFile)
11476       return;
11477
11478     if (gameMode != AnalyzeMode) {
11479         EditGameEvent();
11480         if (gameMode != EditGame) return;
11481         ResurrectChessProgram();
11482         SendToProgram("analyze\n", &first);
11483         first.analyzing = TRUE;
11484         /*first.maybeThinking = TRUE;*/
11485         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11486         EngineOutputPopUp();
11487     }
11488     gameMode = AnalyzeFile;
11489     pausing = FALSE;
11490     ModeHighlight();
11491     SetGameInfo();
11492
11493     StartAnalysisClock();
11494     GetTimeMark(&lastNodeCountTime);
11495     lastNodeCount = 0;
11496 }
11497
11498 void
11499 MachineWhiteEvent()
11500 {
11501     char buf[MSG_SIZ];
11502     char *bookHit = NULL;
11503
11504     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11505       return;
11506
11507
11508     if (gameMode == PlayFromGameFile || 
11509         gameMode == TwoMachinesPlay  || 
11510         gameMode == Training         || 
11511         gameMode == AnalyzeMode      || 
11512         gameMode == EndOfGame)
11513         EditGameEvent();
11514
11515     if (gameMode == EditPosition) 
11516         EditPositionDone(TRUE);
11517
11518     if (!WhiteOnMove(currentMove)) {
11519         DisplayError(_("It is not White's turn"), 0);
11520         return;
11521     }
11522   
11523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11524       ExitAnalyzeMode();
11525
11526     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11527         gameMode == AnalyzeFile)
11528         TruncateGame();
11529
11530     ResurrectChessProgram();    /* in case it isn't running */
11531     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11532         gameMode = MachinePlaysWhite;
11533         ResetClocks();
11534     } else
11535     gameMode = MachinePlaysWhite;
11536     pausing = FALSE;
11537     ModeHighlight();
11538     SetGameInfo();
11539     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11540     DisplayTitle(buf);
11541     if (first.sendName) {
11542       sprintf(buf, "name %s\n", gameInfo.black);
11543       SendToProgram(buf, &first);
11544     }
11545     if (first.sendTime) {
11546       if (first.useColors) {
11547         SendToProgram("black\n", &first); /*gnu kludge*/
11548       }
11549       SendTimeRemaining(&first, TRUE);
11550     }
11551     if (first.useColors) {
11552       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11553     }
11554     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11555     SetMachineThinkingEnables();
11556     first.maybeThinking = TRUE;
11557     StartClocks();
11558     firstMove = FALSE;
11559
11560     if (appData.autoFlipView && !flipView) {
11561       flipView = !flipView;
11562       DrawPosition(FALSE, NULL);
11563       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11564     }
11565
11566     if(bookHit) { // [HGM] book: simulate book reply
11567         static char bookMove[MSG_SIZ]; // a bit generous?
11568
11569         programStats.nodes = programStats.depth = programStats.time = 
11570         programStats.score = programStats.got_only_move = 0;
11571         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11572
11573         strcpy(bookMove, "move ");
11574         strcat(bookMove, bookHit);
11575         HandleMachineMove(bookMove, &first);
11576     }
11577 }
11578
11579 void
11580 MachineBlackEvent()
11581 {
11582     char buf[MSG_SIZ];
11583    char *bookHit = NULL;
11584
11585     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11586         return;
11587
11588
11589     if (gameMode == PlayFromGameFile || 
11590         gameMode == TwoMachinesPlay  || 
11591         gameMode == Training         || 
11592         gameMode == AnalyzeMode      || 
11593         gameMode == EndOfGame)
11594         EditGameEvent();
11595
11596     if (gameMode == EditPosition) 
11597         EditPositionDone(TRUE);
11598
11599     if (WhiteOnMove(currentMove)) {
11600         DisplayError(_("It is not Black's turn"), 0);
11601         return;
11602     }
11603     
11604     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11605       ExitAnalyzeMode();
11606
11607     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11608         gameMode == AnalyzeFile)
11609         TruncateGame();
11610
11611     ResurrectChessProgram();    /* in case it isn't running */
11612     gameMode = MachinePlaysBlack;
11613     pausing = FALSE;
11614     ModeHighlight();
11615     SetGameInfo();
11616     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11617     DisplayTitle(buf);
11618     if (first.sendName) {
11619       sprintf(buf, "name %s\n", gameInfo.white);
11620       SendToProgram(buf, &first);
11621     }
11622     if (first.sendTime) {
11623       if (first.useColors) {
11624         SendToProgram("white\n", &first); /*gnu kludge*/
11625       }
11626       SendTimeRemaining(&first, FALSE);
11627     }
11628     if (first.useColors) {
11629       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11630     }
11631     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11632     SetMachineThinkingEnables();
11633     first.maybeThinking = TRUE;
11634     StartClocks();
11635
11636     if (appData.autoFlipView && flipView) {
11637       flipView = !flipView;
11638       DrawPosition(FALSE, NULL);
11639       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11640     }
11641     if(bookHit) { // [HGM] book: simulate book reply
11642         static char bookMove[MSG_SIZ]; // a bit generous?
11643
11644         programStats.nodes = programStats.depth = programStats.time = 
11645         programStats.score = programStats.got_only_move = 0;
11646         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11647
11648         strcpy(bookMove, "move ");
11649         strcat(bookMove, bookHit);
11650         HandleMachineMove(bookMove, &first);
11651     }
11652 }
11653
11654
11655 void
11656 DisplayTwoMachinesTitle()
11657 {
11658     char buf[MSG_SIZ];
11659     if (appData.matchGames > 0) {
11660         if (first.twoMachinesColor[0] == 'w') {
11661             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11662                     gameInfo.white, gameInfo.black,
11663                     first.matchWins, second.matchWins,
11664                     matchGame - 1 - (first.matchWins + second.matchWins));
11665         } else {
11666             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11667                     gameInfo.white, gameInfo.black,
11668                     second.matchWins, first.matchWins,
11669                     matchGame - 1 - (first.matchWins + second.matchWins));
11670         }
11671     } else {
11672         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11673     }
11674     DisplayTitle(buf);
11675 }
11676
11677 void
11678 TwoMachinesEvent P((void))
11679 {
11680     int i;
11681     char buf[MSG_SIZ];
11682     ChessProgramState *onmove;
11683     char *bookHit = NULL;
11684     
11685     if (appData.noChessProgram) return;
11686
11687     switch (gameMode) {
11688       case TwoMachinesPlay:
11689         return;
11690       case MachinePlaysWhite:
11691       case MachinePlaysBlack:
11692         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11693             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11694             return;
11695         }
11696         /* fall through */
11697       case BeginningOfGame:
11698       case PlayFromGameFile:
11699       case EndOfGame:
11700         EditGameEvent();
11701         if (gameMode != EditGame) return;
11702         break;
11703       case EditPosition:
11704         EditPositionDone(TRUE);
11705         break;
11706       case AnalyzeMode:
11707       case AnalyzeFile:
11708         ExitAnalyzeMode();
11709         break;
11710       case EditGame:
11711       default:
11712         break;
11713     }
11714
11715 //    forwardMostMove = currentMove;
11716     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11717     ResurrectChessProgram();    /* in case first program isn't running */
11718
11719     if (second.pr == NULL) {
11720         StartChessProgram(&second);
11721         if (second.protocolVersion == 1) {
11722           TwoMachinesEventIfReady();
11723         } else {
11724           /* kludge: allow timeout for initial "feature" command */
11725           FreezeUI();
11726           DisplayMessage("", _("Starting second chess program"));
11727           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11728         }
11729         return;
11730     }
11731     DisplayMessage("", "");
11732     InitChessProgram(&second, FALSE);
11733     SendToProgram("force\n", &second);
11734     if (startedFromSetupPosition) {
11735         SendBoard(&second, backwardMostMove);
11736     if (appData.debugMode) {
11737         fprintf(debugFP, "Two Machines\n");
11738     }
11739     }
11740     for (i = backwardMostMove; i < forwardMostMove; i++) {
11741         SendMoveToProgram(i, &second);
11742     }
11743
11744     gameMode = TwoMachinesPlay;
11745     pausing = FALSE;
11746     ModeHighlight();
11747     SetGameInfo();
11748     DisplayTwoMachinesTitle();
11749     firstMove = TRUE;
11750     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11751         onmove = &first;
11752     } else {
11753         onmove = &second;
11754     }
11755
11756     SendToProgram(first.computerString, &first);
11757     if (first.sendName) {
11758       sprintf(buf, "name %s\n", second.tidy);
11759       SendToProgram(buf, &first);
11760     }
11761     SendToProgram(second.computerString, &second);
11762     if (second.sendName) {
11763       sprintf(buf, "name %s\n", first.tidy);
11764       SendToProgram(buf, &second);
11765     }
11766
11767     ResetClocks();
11768     if (!first.sendTime || !second.sendTime) {
11769         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11770         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11771     }
11772     if (onmove->sendTime) {
11773       if (onmove->useColors) {
11774         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11775       }
11776       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11777     }
11778     if (onmove->useColors) {
11779       SendToProgram(onmove->twoMachinesColor, onmove);
11780     }
11781     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11782 //    SendToProgram("go\n", onmove);
11783     onmove->maybeThinking = TRUE;
11784     SetMachineThinkingEnables();
11785
11786     StartClocks();
11787
11788     if(bookHit) { // [HGM] book: simulate book reply
11789         static char bookMove[MSG_SIZ]; // a bit generous?
11790
11791         programStats.nodes = programStats.depth = programStats.time = 
11792         programStats.score = programStats.got_only_move = 0;
11793         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11794
11795         strcpy(bookMove, "move ");
11796         strcat(bookMove, bookHit);
11797         savedMessage = bookMove; // args for deferred call
11798         savedState = onmove;
11799         ScheduleDelayedEvent(DeferredBookMove, 1);
11800     }
11801 }
11802
11803 void
11804 TrainingEvent()
11805 {
11806     if (gameMode == Training) {
11807       SetTrainingModeOff();
11808       gameMode = PlayFromGameFile;
11809       DisplayMessage("", _("Training mode off"));
11810     } else {
11811       gameMode = Training;
11812       animateTraining = appData.animate;
11813
11814       /* make sure we are not already at the end of the game */
11815       if (currentMove < forwardMostMove) {
11816         SetTrainingModeOn();
11817         DisplayMessage("", _("Training mode on"));
11818       } else {
11819         gameMode = PlayFromGameFile;
11820         DisplayError(_("Already at end of game"), 0);
11821       }
11822     }
11823     ModeHighlight();
11824 }
11825
11826 void
11827 IcsClientEvent()
11828 {
11829     if (!appData.icsActive) return;
11830     switch (gameMode) {
11831       case IcsPlayingWhite:
11832       case IcsPlayingBlack:
11833       case IcsObserving:
11834       case IcsIdle:
11835       case BeginningOfGame:
11836       case IcsExamining:
11837         return;
11838
11839       case EditGame:
11840         break;
11841
11842       case EditPosition:
11843         EditPositionDone(TRUE);
11844         break;
11845
11846       case AnalyzeMode:
11847       case AnalyzeFile:
11848         ExitAnalyzeMode();
11849         break;
11850         
11851       default:
11852         EditGameEvent();
11853         break;
11854     }
11855
11856     gameMode = IcsIdle;
11857     ModeHighlight();
11858     return;
11859 }
11860
11861
11862 void
11863 EditGameEvent()
11864 {
11865     int i;
11866
11867     switch (gameMode) {
11868       case Training:
11869         SetTrainingModeOff();
11870         break;
11871       case MachinePlaysWhite:
11872       case MachinePlaysBlack:
11873       case BeginningOfGame:
11874         SendToProgram("force\n", &first);
11875         SetUserThinkingEnables();
11876         break;
11877       case PlayFromGameFile:
11878         (void) StopLoadGameTimer();
11879         if (gameFileFP != NULL) {
11880             gameFileFP = NULL;
11881         }
11882         break;
11883       case EditPosition:
11884         EditPositionDone(TRUE);
11885         break;
11886       case AnalyzeMode:
11887       case AnalyzeFile:
11888         ExitAnalyzeMode();
11889         SendToProgram("force\n", &first);
11890         break;
11891       case TwoMachinesPlay:
11892         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11893         ResurrectChessProgram();
11894         SetUserThinkingEnables();
11895         break;
11896       case EndOfGame:
11897         ResurrectChessProgram();
11898         break;
11899       case IcsPlayingBlack:
11900       case IcsPlayingWhite:
11901         DisplayError(_("Warning: You are still playing a game"), 0);
11902         break;
11903       case IcsObserving:
11904         DisplayError(_("Warning: You are still observing a game"), 0);
11905         break;
11906       case IcsExamining:
11907         DisplayError(_("Warning: You are still examining a game"), 0);
11908         break;
11909       case IcsIdle:
11910         break;
11911       case EditGame:
11912       default:
11913         return;
11914     }
11915     
11916     pausing = FALSE;
11917     StopClocks();
11918     first.offeredDraw = second.offeredDraw = 0;
11919
11920     if (gameMode == PlayFromGameFile) {
11921         whiteTimeRemaining = timeRemaining[0][currentMove];
11922         blackTimeRemaining = timeRemaining[1][currentMove];
11923         DisplayTitle("");
11924     }
11925
11926     if (gameMode == MachinePlaysWhite ||
11927         gameMode == MachinePlaysBlack ||
11928         gameMode == TwoMachinesPlay ||
11929         gameMode == EndOfGame) {
11930         i = forwardMostMove;
11931         while (i > currentMove) {
11932             SendToProgram("undo\n", &first);
11933             i--;
11934         }
11935         whiteTimeRemaining = timeRemaining[0][currentMove];
11936         blackTimeRemaining = timeRemaining[1][currentMove];
11937         DisplayBothClocks();
11938         if (whiteFlag || blackFlag) {
11939             whiteFlag = blackFlag = 0;
11940         }
11941         DisplayTitle("");
11942     }           
11943     
11944     gameMode = EditGame;
11945     ModeHighlight();
11946     SetGameInfo();
11947 }
11948
11949
11950 void
11951 EditPositionEvent()
11952 {
11953     if (gameMode == EditPosition) {
11954         EditGameEvent();
11955         return;
11956     }
11957     
11958     EditGameEvent();
11959     if (gameMode != EditGame) return;
11960     
11961     gameMode = EditPosition;
11962     ModeHighlight();
11963     SetGameInfo();
11964     if (currentMove > 0)
11965       CopyBoard(boards[0], boards[currentMove]);
11966     
11967     blackPlaysFirst = !WhiteOnMove(currentMove);
11968     ResetClocks();
11969     currentMove = forwardMostMove = backwardMostMove = 0;
11970     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11971     DisplayMove(-1);
11972 }
11973
11974 void
11975 ExitAnalyzeMode()
11976 {
11977     /* [DM] icsEngineAnalyze - possible call from other functions */
11978     if (appData.icsEngineAnalyze) {
11979         appData.icsEngineAnalyze = FALSE;
11980
11981         DisplayMessage("",_("Close ICS engine analyze..."));
11982     }
11983     if (first.analysisSupport && first.analyzing) {
11984       SendToProgram("exit\n", &first);
11985       first.analyzing = FALSE;
11986     }
11987     thinkOutput[0] = NULLCHAR;
11988 }
11989
11990 void
11991 EditPositionDone(Boolean fakeRights)
11992 {
11993     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11994
11995     startedFromSetupPosition = TRUE;
11996     InitChessProgram(&first, FALSE);
11997     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11998       boards[0][EP_STATUS] = EP_NONE;
11999       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12000     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12001         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12002         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12003       } else boards[0][CASTLING][2] = NoRights;
12004     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12005         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12006         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12007       } else boards[0][CASTLING][5] = NoRights;
12008     }
12009     SendToProgram("force\n", &first);
12010     if (blackPlaysFirst) {
12011         strcpy(moveList[0], "");
12012         strcpy(parseList[0], "");
12013         currentMove = forwardMostMove = backwardMostMove = 1;
12014         CopyBoard(boards[1], boards[0]);
12015     } else {
12016         currentMove = forwardMostMove = backwardMostMove = 0;
12017     }
12018     SendBoard(&first, forwardMostMove);
12019     if (appData.debugMode) {
12020         fprintf(debugFP, "EditPosDone\n");
12021     }
12022     DisplayTitle("");
12023     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12024     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12025     gameMode = EditGame;
12026     ModeHighlight();
12027     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12028     ClearHighlights(); /* [AS] */
12029 }
12030
12031 /* Pause for `ms' milliseconds */
12032 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12033 void
12034 TimeDelay(ms)
12035      long ms;
12036 {
12037     TimeMark m1, m2;
12038
12039     GetTimeMark(&m1);
12040     do {
12041         GetTimeMark(&m2);
12042     } while (SubtractTimeMarks(&m2, &m1) < ms);
12043 }
12044
12045 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12046 void
12047 SendMultiLineToICS(buf)
12048      char *buf;
12049 {
12050     char temp[MSG_SIZ+1], *p;
12051     int len;
12052
12053     len = strlen(buf);
12054     if (len > MSG_SIZ)
12055       len = MSG_SIZ;
12056   
12057     strncpy(temp, buf, len);
12058     temp[len] = 0;
12059
12060     p = temp;
12061     while (*p) {
12062         if (*p == '\n' || *p == '\r')
12063           *p = ' ';
12064         ++p;
12065     }
12066
12067     strcat(temp, "\n");
12068     SendToICS(temp);
12069     SendToPlayer(temp, strlen(temp));
12070 }
12071
12072 void
12073 SetWhiteToPlayEvent()
12074 {
12075     if (gameMode == EditPosition) {
12076         blackPlaysFirst = FALSE;
12077         DisplayBothClocks();    /* works because currentMove is 0 */
12078     } else if (gameMode == IcsExamining) {
12079         SendToICS(ics_prefix);
12080         SendToICS("tomove white\n");
12081     }
12082 }
12083
12084 void
12085 SetBlackToPlayEvent()
12086 {
12087     if (gameMode == EditPosition) {
12088         blackPlaysFirst = TRUE;
12089         currentMove = 1;        /* kludge */
12090         DisplayBothClocks();
12091         currentMove = 0;
12092     } else if (gameMode == IcsExamining) {
12093         SendToICS(ics_prefix);
12094         SendToICS("tomove black\n");
12095     }
12096 }
12097
12098 void
12099 EditPositionMenuEvent(selection, x, y)
12100      ChessSquare selection;
12101      int x, y;
12102 {
12103     char buf[MSG_SIZ];
12104     ChessSquare piece = boards[0][y][x];
12105
12106     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12107
12108     switch (selection) {
12109       case ClearBoard:
12110         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12111             SendToICS(ics_prefix);
12112             SendToICS("bsetup clear\n");
12113         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12114             SendToICS(ics_prefix);
12115             SendToICS("clearboard\n");
12116         } else {
12117             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12118                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12119                 for (y = 0; y < BOARD_HEIGHT; y++) {
12120                     if (gameMode == IcsExamining) {
12121                         if (boards[currentMove][y][x] != EmptySquare) {
12122                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12123                                     AAA + x, ONE + y);
12124                             SendToICS(buf);
12125                         }
12126                     } else {
12127                         boards[0][y][x] = p;
12128                     }
12129                 }
12130             }
12131         }
12132         if (gameMode == EditPosition) {
12133             DrawPosition(FALSE, boards[0]);
12134         }
12135         break;
12136
12137       case WhitePlay:
12138         SetWhiteToPlayEvent();
12139         break;
12140
12141       case BlackPlay:
12142         SetBlackToPlayEvent();
12143         break;
12144
12145       case EmptySquare:
12146         if (gameMode == IcsExamining) {
12147             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12148             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12149             SendToICS(buf);
12150         } else {
12151             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12152                 if(x == BOARD_LEFT-2) {
12153                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12154                     boards[0][y][1] = 0;
12155                 } else
12156                 if(x == BOARD_RGHT+1) {
12157                     if(y >= gameInfo.holdingsSize) break;
12158                     boards[0][y][BOARD_WIDTH-2] = 0;
12159                 } else break;
12160             }
12161             boards[0][y][x] = EmptySquare;
12162             DrawPosition(FALSE, boards[0]);
12163         }
12164         break;
12165
12166       case PromotePiece:
12167         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12168            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12169             selection = (ChessSquare) (PROMOTED piece);
12170         } else if(piece == EmptySquare) selection = WhiteSilver;
12171         else selection = (ChessSquare)((int)piece - 1);
12172         goto defaultlabel;
12173
12174       case DemotePiece:
12175         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12176            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12177             selection = (ChessSquare) (DEMOTED piece);
12178         } else if(piece == EmptySquare) selection = BlackSilver;
12179         else selection = (ChessSquare)((int)piece + 1);       
12180         goto defaultlabel;
12181
12182       case WhiteQueen:
12183       case BlackQueen:
12184         if(gameInfo.variant == VariantShatranj ||
12185            gameInfo.variant == VariantXiangqi  ||
12186            gameInfo.variant == VariantCourier  ||
12187            gameInfo.variant == VariantMakruk     )
12188             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12189         goto defaultlabel;
12190
12191       case WhiteKing:
12192       case BlackKing:
12193         if(gameInfo.variant == VariantXiangqi)
12194             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12195         if(gameInfo.variant == VariantKnightmate)
12196             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12197       default:
12198         defaultlabel:
12199         if (gameMode == IcsExamining) {
12200             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12201             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12202                     PieceToChar(selection), AAA + x, ONE + y);
12203             SendToICS(buf);
12204         } else {
12205             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12206                 int n;
12207                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12208                     n = PieceToNumber(selection - BlackPawn);
12209                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12210                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12211                     boards[0][BOARD_HEIGHT-1-n][1]++;
12212                 } else
12213                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12214                     n = PieceToNumber(selection);
12215                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12216                     boards[0][n][BOARD_WIDTH-1] = selection;
12217                     boards[0][n][BOARD_WIDTH-2]++;
12218                 }
12219             } else
12220             boards[0][y][x] = selection;
12221             DrawPosition(TRUE, boards[0]);
12222         }
12223         break;
12224     }
12225 }
12226
12227
12228 void
12229 DropMenuEvent(selection, x, y)
12230      ChessSquare selection;
12231      int x, y;
12232 {
12233     ChessMove moveType;
12234
12235     switch (gameMode) {
12236       case IcsPlayingWhite:
12237       case MachinePlaysBlack:
12238         if (!WhiteOnMove(currentMove)) {
12239             DisplayMoveError(_("It is Black's turn"));
12240             return;
12241         }
12242         moveType = WhiteDrop;
12243         break;
12244       case IcsPlayingBlack:
12245       case MachinePlaysWhite:
12246         if (WhiteOnMove(currentMove)) {
12247             DisplayMoveError(_("It is White's turn"));
12248             return;
12249         }
12250         moveType = BlackDrop;
12251         break;
12252       case EditGame:
12253         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12254         break;
12255       default:
12256         return;
12257     }
12258
12259     if (moveType == BlackDrop && selection < BlackPawn) {
12260       selection = (ChessSquare) ((int) selection
12261                                  + (int) BlackPawn - (int) WhitePawn);
12262     }
12263     if (boards[currentMove][y][x] != EmptySquare) {
12264         DisplayMoveError(_("That square is occupied"));
12265         return;
12266     }
12267
12268     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12269 }
12270
12271 void
12272 AcceptEvent()
12273 {
12274     /* Accept a pending offer of any kind from opponent */
12275     
12276     if (appData.icsActive) {
12277         SendToICS(ics_prefix);
12278         SendToICS("accept\n");
12279     } else if (cmailMsgLoaded) {
12280         if (currentMove == cmailOldMove &&
12281             commentList[cmailOldMove] != NULL &&
12282             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12283                    "Black offers a draw" : "White offers a draw")) {
12284             TruncateGame();
12285             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12287         } else {
12288             DisplayError(_("There is no pending offer on this move"), 0);
12289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12290         }
12291     } else {
12292         /* Not used for offers from chess program */
12293     }
12294 }
12295
12296 void
12297 DeclineEvent()
12298 {
12299     /* Decline a pending offer of any kind from opponent */
12300     
12301     if (appData.icsActive) {
12302         SendToICS(ics_prefix);
12303         SendToICS("decline\n");
12304     } else if (cmailMsgLoaded) {
12305         if (currentMove == cmailOldMove &&
12306             commentList[cmailOldMove] != NULL &&
12307             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12308                    "Black offers a draw" : "White offers a draw")) {
12309 #ifdef NOTDEF
12310             AppendComment(cmailOldMove, "Draw declined", TRUE);
12311             DisplayComment(cmailOldMove - 1, "Draw declined");
12312 #endif /*NOTDEF*/
12313         } else {
12314             DisplayError(_("There is no pending offer on this move"), 0);
12315         }
12316     } else {
12317         /* Not used for offers from chess program */
12318     }
12319 }
12320
12321 void
12322 RematchEvent()
12323 {
12324     /* Issue ICS rematch command */
12325     if (appData.icsActive) {
12326         SendToICS(ics_prefix);
12327         SendToICS("rematch\n");
12328     }
12329 }
12330
12331 void
12332 CallFlagEvent()
12333 {
12334     /* Call your opponent's flag (claim a win on time) */
12335     if (appData.icsActive) {
12336         SendToICS(ics_prefix);
12337         SendToICS("flag\n");
12338     } else {
12339         switch (gameMode) {
12340           default:
12341             return;
12342           case MachinePlaysWhite:
12343             if (whiteFlag) {
12344                 if (blackFlag)
12345                   GameEnds(GameIsDrawn, "Both players ran out of time",
12346                            GE_PLAYER);
12347                 else
12348                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12349             } else {
12350                 DisplayError(_("Your opponent is not out of time"), 0);
12351             }
12352             break;
12353           case MachinePlaysBlack:
12354             if (blackFlag) {
12355                 if (whiteFlag)
12356                   GameEnds(GameIsDrawn, "Both players ran out of time",
12357                            GE_PLAYER);
12358                 else
12359                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12360             } else {
12361                 DisplayError(_("Your opponent is not out of time"), 0);
12362             }
12363             break;
12364         }
12365     }
12366 }
12367
12368 void
12369 DrawEvent()
12370 {
12371     /* Offer draw or accept pending draw offer from opponent */
12372     
12373     if (appData.icsActive) {
12374         /* Note: tournament rules require draw offers to be
12375            made after you make your move but before you punch
12376            your clock.  Currently ICS doesn't let you do that;
12377            instead, you immediately punch your clock after making
12378            a move, but you can offer a draw at any time. */
12379         
12380         SendToICS(ics_prefix);
12381         SendToICS("draw\n");
12382         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12383     } else if (cmailMsgLoaded) {
12384         if (currentMove == cmailOldMove &&
12385             commentList[cmailOldMove] != NULL &&
12386             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12387                    "Black offers a draw" : "White offers a draw")) {
12388             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12390         } else if (currentMove == cmailOldMove + 1) {
12391             char *offer = WhiteOnMove(cmailOldMove) ?
12392               "White offers a draw" : "Black offers a draw";
12393             AppendComment(currentMove, offer, TRUE);
12394             DisplayComment(currentMove - 1, offer);
12395             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12396         } else {
12397             DisplayError(_("You must make your move before offering a draw"), 0);
12398             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12399         }
12400     } else if (first.offeredDraw) {
12401         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12402     } else {
12403         if (first.sendDrawOffers) {
12404             SendToProgram("draw\n", &first);
12405             userOfferedDraw = TRUE;
12406         }
12407     }
12408 }
12409
12410 void
12411 AdjournEvent()
12412 {
12413     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12414     
12415     if (appData.icsActive) {
12416         SendToICS(ics_prefix);
12417         SendToICS("adjourn\n");
12418     } else {
12419         /* Currently GNU Chess doesn't offer or accept Adjourns */
12420     }
12421 }
12422
12423
12424 void
12425 AbortEvent()
12426 {
12427     /* Offer Abort or accept pending Abort offer from opponent */
12428     
12429     if (appData.icsActive) {
12430         SendToICS(ics_prefix);
12431         SendToICS("abort\n");
12432     } else {
12433         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12434     }
12435 }
12436
12437 void
12438 ResignEvent()
12439 {
12440     /* Resign.  You can do this even if it's not your turn. */
12441     
12442     if (appData.icsActive) {
12443         SendToICS(ics_prefix);
12444         SendToICS("resign\n");
12445     } else {
12446         switch (gameMode) {
12447           case MachinePlaysWhite:
12448             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12449             break;
12450           case MachinePlaysBlack:
12451             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12452             break;
12453           case EditGame:
12454             if (cmailMsgLoaded) {
12455                 TruncateGame();
12456                 if (WhiteOnMove(cmailOldMove)) {
12457                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12458                 } else {
12459                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12460                 }
12461                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12462             }
12463             break;
12464           default:
12465             break;
12466         }
12467     }
12468 }
12469
12470
12471 void
12472 StopObservingEvent()
12473 {
12474     /* Stop observing current games */
12475     SendToICS(ics_prefix);
12476     SendToICS("unobserve\n");
12477 }
12478
12479 void
12480 StopExaminingEvent()
12481 {
12482     /* Stop observing current game */
12483     SendToICS(ics_prefix);
12484     SendToICS("unexamine\n");
12485 }
12486
12487 void
12488 ForwardInner(target)
12489      int target;
12490 {
12491     int limit;
12492
12493     if (appData.debugMode)
12494         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12495                 target, currentMove, forwardMostMove);
12496
12497     if (gameMode == EditPosition)
12498       return;
12499
12500     if (gameMode == PlayFromGameFile && !pausing)
12501       PauseEvent();
12502     
12503     if (gameMode == IcsExamining && pausing)
12504       limit = pauseExamForwardMostMove;
12505     else
12506       limit = forwardMostMove;
12507     
12508     if (target > limit) target = limit;
12509
12510     if (target > 0 && moveList[target - 1][0]) {
12511         int fromX, fromY, toX, toY;
12512         toX = moveList[target - 1][2] - AAA;
12513         toY = moveList[target - 1][3] - ONE;
12514         if (moveList[target - 1][1] == '@') {
12515             if (appData.highlightLastMove) {
12516                 SetHighlights(-1, -1, toX, toY);
12517             }
12518         } else {
12519             fromX = moveList[target - 1][0] - AAA;
12520             fromY = moveList[target - 1][1] - ONE;
12521             if (target == currentMove + 1) {
12522                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12523             }
12524             if (appData.highlightLastMove) {
12525                 SetHighlights(fromX, fromY, toX, toY);
12526             }
12527         }
12528     }
12529     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12530         gameMode == Training || gameMode == PlayFromGameFile || 
12531         gameMode == AnalyzeFile) {
12532         while (currentMove < target) {
12533             SendMoveToProgram(currentMove++, &first);
12534         }
12535     } else {
12536         currentMove = target;
12537     }
12538     
12539     if (gameMode == EditGame || gameMode == EndOfGame) {
12540         whiteTimeRemaining = timeRemaining[0][currentMove];
12541         blackTimeRemaining = timeRemaining[1][currentMove];
12542     }
12543     DisplayBothClocks();
12544     DisplayMove(currentMove - 1);
12545     DrawPosition(FALSE, boards[currentMove]);
12546     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12547     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12548         DisplayComment(currentMove - 1, commentList[currentMove]);
12549     }
12550 }
12551
12552
12553 void
12554 ForwardEvent()
12555 {
12556     if (gameMode == IcsExamining && !pausing) {
12557         SendToICS(ics_prefix);
12558         SendToICS("forward\n");
12559     } else {
12560         ForwardInner(currentMove + 1);
12561     }
12562 }
12563
12564 void
12565 ToEndEvent()
12566 {
12567     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12568         /* to optimze, we temporarily turn off analysis mode while we feed
12569          * the remaining moves to the engine. Otherwise we get analysis output
12570          * after each move.
12571          */ 
12572         if (first.analysisSupport) {
12573           SendToProgram("exit\nforce\n", &first);
12574           first.analyzing = FALSE;
12575         }
12576     }
12577         
12578     if (gameMode == IcsExamining && !pausing) {
12579         SendToICS(ics_prefix);
12580         SendToICS("forward 999999\n");
12581     } else {
12582         ForwardInner(forwardMostMove);
12583     }
12584
12585     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12586         /* we have fed all the moves, so reactivate analysis mode */
12587         SendToProgram("analyze\n", &first);
12588         first.analyzing = TRUE;
12589         /*first.maybeThinking = TRUE;*/
12590         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12591     }
12592 }
12593
12594 void
12595 BackwardInner(target)
12596      int target;
12597 {
12598     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12599
12600     if (appData.debugMode)
12601         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12602                 target, currentMove, forwardMostMove);
12603
12604     if (gameMode == EditPosition) return;
12605     if (currentMove <= backwardMostMove) {
12606         ClearHighlights();
12607         DrawPosition(full_redraw, boards[currentMove]);
12608         return;
12609     }
12610     if (gameMode == PlayFromGameFile && !pausing)
12611       PauseEvent();
12612     
12613     if (moveList[target][0]) {
12614         int fromX, fromY, toX, toY;
12615         toX = moveList[target][2] - AAA;
12616         toY = moveList[target][3] - ONE;
12617         if (moveList[target][1] == '@') {
12618             if (appData.highlightLastMove) {
12619                 SetHighlights(-1, -1, toX, toY);
12620             }
12621         } else {
12622             fromX = moveList[target][0] - AAA;
12623             fromY = moveList[target][1] - ONE;
12624             if (target == currentMove - 1) {
12625                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12626             }
12627             if (appData.highlightLastMove) {
12628                 SetHighlights(fromX, fromY, toX, toY);
12629             }
12630         }
12631     }
12632     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12633         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12634         while (currentMove > target) {
12635             SendToProgram("undo\n", &first);
12636             currentMove--;
12637         }
12638     } else {
12639         currentMove = target;
12640     }
12641     
12642     if (gameMode == EditGame || gameMode == EndOfGame) {
12643         whiteTimeRemaining = timeRemaining[0][currentMove];
12644         blackTimeRemaining = timeRemaining[1][currentMove];
12645     }
12646     DisplayBothClocks();
12647     DisplayMove(currentMove - 1);
12648     DrawPosition(full_redraw, boards[currentMove]);
12649     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12650     // [HGM] PV info: routine tests if comment empty
12651     DisplayComment(currentMove - 1, commentList[currentMove]);
12652 }
12653
12654 void
12655 BackwardEvent()
12656 {
12657     if (gameMode == IcsExamining && !pausing) {
12658         SendToICS(ics_prefix);
12659         SendToICS("backward\n");
12660     } else {
12661         BackwardInner(currentMove - 1);
12662     }
12663 }
12664
12665 void
12666 ToStartEvent()
12667 {
12668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12669         /* to optimize, we temporarily turn off analysis mode while we undo
12670          * all the moves. Otherwise we get analysis output after each undo.
12671          */ 
12672         if (first.analysisSupport) {
12673           SendToProgram("exit\nforce\n", &first);
12674           first.analyzing = FALSE;
12675         }
12676     }
12677
12678     if (gameMode == IcsExamining && !pausing) {
12679         SendToICS(ics_prefix);
12680         SendToICS("backward 999999\n");
12681     } else {
12682         BackwardInner(backwardMostMove);
12683     }
12684
12685     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12686         /* we have fed all the moves, so reactivate analysis mode */
12687         SendToProgram("analyze\n", &first);
12688         first.analyzing = TRUE;
12689         /*first.maybeThinking = TRUE;*/
12690         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12691     }
12692 }
12693
12694 void
12695 ToNrEvent(int to)
12696 {
12697   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12698   if (to >= forwardMostMove) to = forwardMostMove;
12699   if (to <= backwardMostMove) to = backwardMostMove;
12700   if (to < currentMove) {
12701     BackwardInner(to);
12702   } else {
12703     ForwardInner(to);
12704   }
12705 }
12706
12707 void
12708 RevertEvent(Boolean annotate)
12709 {
12710     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12711         return;
12712     }
12713     if (gameMode != IcsExamining) {
12714         DisplayError(_("You are not examining a game"), 0);
12715         return;
12716     }
12717     if (pausing) {
12718         DisplayError(_("You can't revert while pausing"), 0);
12719         return;
12720     }
12721     SendToICS(ics_prefix);
12722     SendToICS("revert\n");
12723 }
12724
12725 void
12726 RetractMoveEvent()
12727 {
12728     switch (gameMode) {
12729       case MachinePlaysWhite:
12730       case MachinePlaysBlack:
12731         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12732             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12733             return;
12734         }
12735         if (forwardMostMove < 2) return;
12736         currentMove = forwardMostMove = forwardMostMove - 2;
12737         whiteTimeRemaining = timeRemaining[0][currentMove];
12738         blackTimeRemaining = timeRemaining[1][currentMove];
12739         DisplayBothClocks();
12740         DisplayMove(currentMove - 1);
12741         ClearHighlights();/*!! could figure this out*/
12742         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12743         SendToProgram("remove\n", &first);
12744         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12745         break;
12746
12747       case BeginningOfGame:
12748       default:
12749         break;
12750
12751       case IcsPlayingWhite:
12752       case IcsPlayingBlack:
12753         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12754             SendToICS(ics_prefix);
12755             SendToICS("takeback 2\n");
12756         } else {
12757             SendToICS(ics_prefix);
12758             SendToICS("takeback 1\n");
12759         }
12760         break;
12761     }
12762 }
12763
12764 void
12765 MoveNowEvent()
12766 {
12767     ChessProgramState *cps;
12768
12769     switch (gameMode) {
12770       case MachinePlaysWhite:
12771         if (!WhiteOnMove(forwardMostMove)) {
12772             DisplayError(_("It is your turn"), 0);
12773             return;
12774         }
12775         cps = &first;
12776         break;
12777       case MachinePlaysBlack:
12778         if (WhiteOnMove(forwardMostMove)) {
12779             DisplayError(_("It is your turn"), 0);
12780             return;
12781         }
12782         cps = &first;
12783         break;
12784       case TwoMachinesPlay:
12785         if (WhiteOnMove(forwardMostMove) ==
12786             (first.twoMachinesColor[0] == 'w')) {
12787             cps = &first;
12788         } else {
12789             cps = &second;
12790         }
12791         break;
12792       case BeginningOfGame:
12793       default:
12794         return;
12795     }
12796     SendToProgram("?\n", cps);
12797 }
12798
12799 void
12800 TruncateGameEvent()
12801 {
12802     EditGameEvent();
12803     if (gameMode != EditGame) return;
12804     TruncateGame();
12805 }
12806
12807 void
12808 TruncateGame()
12809 {
12810     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12811     if (forwardMostMove > currentMove) {
12812         if (gameInfo.resultDetails != NULL) {
12813             free(gameInfo.resultDetails);
12814             gameInfo.resultDetails = NULL;
12815             gameInfo.result = GameUnfinished;
12816         }
12817         forwardMostMove = currentMove;
12818         HistorySet(parseList, backwardMostMove, forwardMostMove,
12819                    currentMove-1);
12820     }
12821 }
12822
12823 void
12824 HintEvent()
12825 {
12826     if (appData.noChessProgram) return;
12827     switch (gameMode) {
12828       case MachinePlaysWhite:
12829         if (WhiteOnMove(forwardMostMove)) {
12830             DisplayError(_("Wait until your turn"), 0);
12831             return;
12832         }
12833         break;
12834       case BeginningOfGame:
12835       case MachinePlaysBlack:
12836         if (!WhiteOnMove(forwardMostMove)) {
12837             DisplayError(_("Wait until your turn"), 0);
12838             return;
12839         }
12840         break;
12841       default:
12842         DisplayError(_("No hint available"), 0);
12843         return;
12844     }
12845     SendToProgram("hint\n", &first);
12846     hintRequested = TRUE;
12847 }
12848
12849 void
12850 BookEvent()
12851 {
12852     if (appData.noChessProgram) return;
12853     switch (gameMode) {
12854       case MachinePlaysWhite:
12855         if (WhiteOnMove(forwardMostMove)) {
12856             DisplayError(_("Wait until your turn"), 0);
12857             return;
12858         }
12859         break;
12860       case BeginningOfGame:
12861       case MachinePlaysBlack:
12862         if (!WhiteOnMove(forwardMostMove)) {
12863             DisplayError(_("Wait until your turn"), 0);
12864             return;
12865         }
12866         break;
12867       case EditPosition:
12868         EditPositionDone(TRUE);
12869         break;
12870       case TwoMachinesPlay:
12871         return;
12872       default:
12873         break;
12874     }
12875     SendToProgram("bk\n", &first);
12876     bookOutput[0] = NULLCHAR;
12877     bookRequested = TRUE;
12878 }
12879
12880 void
12881 AboutGameEvent()
12882 {
12883     char *tags = PGNTags(&gameInfo);
12884     TagsPopUp(tags, CmailMsg());
12885     free(tags);
12886 }
12887
12888 /* end button procedures */
12889
12890 void
12891 PrintPosition(fp, move)
12892      FILE *fp;
12893      int move;
12894 {
12895     int i, j;
12896     
12897     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12899             char c = PieceToChar(boards[move][i][j]);
12900             fputc(c == 'x' ? '.' : c, fp);
12901             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12902         }
12903     }
12904     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12905       fprintf(fp, "white to play\n");
12906     else
12907       fprintf(fp, "black to play\n");
12908 }
12909
12910 void
12911 PrintOpponents(fp)
12912      FILE *fp;
12913 {
12914     if (gameInfo.white != NULL) {
12915         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12916     } else {
12917         fprintf(fp, "\n");
12918     }
12919 }
12920
12921 /* Find last component of program's own name, using some heuristics */
12922 void
12923 TidyProgramName(prog, host, buf)
12924      char *prog, *host, buf[MSG_SIZ];
12925 {
12926     char *p, *q;
12927     int local = (strcmp(host, "localhost") == 0);
12928     while (!local && (p = strchr(prog, ';')) != NULL) {
12929         p++;
12930         while (*p == ' ') p++;
12931         prog = p;
12932     }
12933     if (*prog == '"' || *prog == '\'') {
12934         q = strchr(prog + 1, *prog);
12935     } else {
12936         q = strchr(prog, ' ');
12937     }
12938     if (q == NULL) q = prog + strlen(prog);
12939     p = q;
12940     while (p >= prog && *p != '/' && *p != '\\') p--;
12941     p++;
12942     if(p == prog && *p == '"') p++;
12943     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12944     memcpy(buf, p, q - p);
12945     buf[q - p] = NULLCHAR;
12946     if (!local) {
12947         strcat(buf, "@");
12948         strcat(buf, host);
12949     }
12950 }
12951
12952 char *
12953 TimeControlTagValue()
12954 {
12955     char buf[MSG_SIZ];
12956     if (!appData.clockMode) {
12957         strcpy(buf, "-");
12958     } else if (movesPerSession > 0) {
12959         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12960     } else if (timeIncrement == 0) {
12961         sprintf(buf, "%ld", timeControl/1000);
12962     } else {
12963         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12964     }
12965     return StrSave(buf);
12966 }
12967
12968 void
12969 SetGameInfo()
12970 {
12971     /* This routine is used only for certain modes */
12972     VariantClass v = gameInfo.variant;
12973     ChessMove r = GameUnfinished;
12974     char *p = NULL;
12975
12976     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12977         r = gameInfo.result; 
12978         p = gameInfo.resultDetails; 
12979         gameInfo.resultDetails = NULL;
12980     }
12981     ClearGameInfo(&gameInfo);
12982     gameInfo.variant = v;
12983
12984     switch (gameMode) {
12985       case MachinePlaysWhite:
12986         gameInfo.event = StrSave( appData.pgnEventHeader );
12987         gameInfo.site = StrSave(HostName());
12988         gameInfo.date = PGNDate();
12989         gameInfo.round = StrSave("-");
12990         gameInfo.white = StrSave(first.tidy);
12991         gameInfo.black = StrSave(UserName());
12992         gameInfo.timeControl = TimeControlTagValue();
12993         break;
12994
12995       case MachinePlaysBlack:
12996         gameInfo.event = StrSave( appData.pgnEventHeader );
12997         gameInfo.site = StrSave(HostName());
12998         gameInfo.date = PGNDate();
12999         gameInfo.round = StrSave("-");
13000         gameInfo.white = StrSave(UserName());
13001         gameInfo.black = StrSave(first.tidy);
13002         gameInfo.timeControl = TimeControlTagValue();
13003         break;
13004
13005       case TwoMachinesPlay:
13006         gameInfo.event = StrSave( appData.pgnEventHeader );
13007         gameInfo.site = StrSave(HostName());
13008         gameInfo.date = PGNDate();
13009         if (matchGame > 0) {
13010             char buf[MSG_SIZ];
13011             sprintf(buf, "%d", matchGame);
13012             gameInfo.round = StrSave(buf);
13013         } else {
13014             gameInfo.round = StrSave("-");
13015         }
13016         if (first.twoMachinesColor[0] == 'w') {
13017             gameInfo.white = StrSave(first.tidy);
13018             gameInfo.black = StrSave(second.tidy);
13019         } else {
13020             gameInfo.white = StrSave(second.tidy);
13021             gameInfo.black = StrSave(first.tidy);
13022         }
13023         gameInfo.timeControl = TimeControlTagValue();
13024         break;
13025
13026       case EditGame:
13027         gameInfo.event = StrSave("Edited game");
13028         gameInfo.site = StrSave(HostName());
13029         gameInfo.date = PGNDate();
13030         gameInfo.round = StrSave("-");
13031         gameInfo.white = StrSave("-");
13032         gameInfo.black = StrSave("-");
13033         gameInfo.result = r;
13034         gameInfo.resultDetails = p;
13035         break;
13036
13037       case EditPosition:
13038         gameInfo.event = StrSave("Edited position");
13039         gameInfo.site = StrSave(HostName());
13040         gameInfo.date = PGNDate();
13041         gameInfo.round = StrSave("-");
13042         gameInfo.white = StrSave("-");
13043         gameInfo.black = StrSave("-");
13044         break;
13045
13046       case IcsPlayingWhite:
13047       case IcsPlayingBlack:
13048       case IcsObserving:
13049       case IcsExamining:
13050         break;
13051
13052       case PlayFromGameFile:
13053         gameInfo.event = StrSave("Game from non-PGN file");
13054         gameInfo.site = StrSave(HostName());
13055         gameInfo.date = PGNDate();
13056         gameInfo.round = StrSave("-");
13057         gameInfo.white = StrSave("?");
13058         gameInfo.black = StrSave("?");
13059         break;
13060
13061       default:
13062         break;
13063     }
13064 }
13065
13066 void
13067 ReplaceComment(index, text)
13068      int index;
13069      char *text;
13070 {
13071     int len;
13072
13073     while (*text == '\n') text++;
13074     len = strlen(text);
13075     while (len > 0 && text[len - 1] == '\n') len--;
13076
13077     if (commentList[index] != NULL)
13078       free(commentList[index]);
13079
13080     if (len == 0) {
13081         commentList[index] = NULL;
13082         return;
13083     }
13084   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13085       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13086       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13087     commentList[index] = (char *) malloc(len + 2);
13088     strncpy(commentList[index], text, len);
13089     commentList[index][len] = '\n';
13090     commentList[index][len + 1] = NULLCHAR;
13091   } else { 
13092     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13093     char *p;
13094     commentList[index] = (char *) malloc(len + 6);
13095     strcpy(commentList[index], "{\n");
13096     strncpy(commentList[index]+2, text, len);
13097     commentList[index][len+2] = NULLCHAR;
13098     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13099     strcat(commentList[index], "\n}\n");
13100   }
13101 }
13102
13103 void
13104 CrushCRs(text)
13105      char *text;
13106 {
13107   char *p = text;
13108   char *q = text;
13109   char ch;
13110
13111   do {
13112     ch = *p++;
13113     if (ch == '\r') continue;
13114     *q++ = ch;
13115   } while (ch != '\0');
13116 }
13117
13118 void
13119 AppendComment(index, text, addBraces)
13120      int index;
13121      char *text;
13122      Boolean addBraces; // [HGM] braces: tells if we should add {}
13123 {
13124     int oldlen, len;
13125     char *old;
13126
13127 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13128     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13129
13130     CrushCRs(text);
13131     while (*text == '\n') text++;
13132     len = strlen(text);
13133     while (len > 0 && text[len - 1] == '\n') len--;
13134
13135     if (len == 0) return;
13136
13137     if (commentList[index] != NULL) {
13138         old = commentList[index];
13139         oldlen = strlen(old);
13140         while(commentList[index][oldlen-1] ==  '\n')
13141           commentList[index][--oldlen] = NULLCHAR;
13142         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13143         strcpy(commentList[index], old);
13144         free(old);
13145         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13146         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13147           if(addBraces) addBraces = FALSE; else { text++; len--; }
13148           while (*text == '\n') { text++; len--; }
13149           commentList[index][--oldlen] = NULLCHAR;
13150       }
13151         if(addBraces) strcat(commentList[index], "\n{\n");
13152         else          strcat(commentList[index], "\n");
13153         strcat(commentList[index], text);
13154         if(addBraces) strcat(commentList[index], "\n}\n");
13155         else          strcat(commentList[index], "\n");
13156     } else {
13157         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13158         if(addBraces)
13159              strcpy(commentList[index], "{\n");
13160         else commentList[index][0] = NULLCHAR;
13161         strcat(commentList[index], text);
13162         strcat(commentList[index], "\n");
13163         if(addBraces) strcat(commentList[index], "}\n");
13164     }
13165 }
13166
13167 static char * FindStr( char * text, char * sub_text )
13168 {
13169     char * result = strstr( text, sub_text );
13170
13171     if( result != NULL ) {
13172         result += strlen( sub_text );
13173     }
13174
13175     return result;
13176 }
13177
13178 /* [AS] Try to extract PV info from PGN comment */
13179 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13180 char *GetInfoFromComment( int index, char * text )
13181 {
13182     char * sep = text;
13183
13184     if( text != NULL && index > 0 ) {
13185         int score = 0;
13186         int depth = 0;
13187         int time = -1, sec = 0, deci;
13188         char * s_eval = FindStr( text, "[%eval " );
13189         char * s_emt = FindStr( text, "[%emt " );
13190
13191         if( s_eval != NULL || s_emt != NULL ) {
13192             /* New style */
13193             char delim;
13194
13195             if( s_eval != NULL ) {
13196                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13197                     return text;
13198                 }
13199
13200                 if( delim != ']' ) {
13201                     return text;
13202                 }
13203             }
13204
13205             if( s_emt != NULL ) {
13206             }
13207                 return text;
13208         }
13209         else {
13210             /* We expect something like: [+|-]nnn.nn/dd */
13211             int score_lo = 0;
13212
13213             if(*text != '{') return text; // [HGM] braces: must be normal comment
13214
13215             sep = strchr( text, '/' );
13216             if( sep == NULL || sep < (text+4) ) {
13217                 return text;
13218             }
13219
13220             time = -1; sec = -1; deci = -1;
13221             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13222                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13223                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13224                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13225                 return text;
13226             }
13227
13228             if( score_lo < 0 || score_lo >= 100 ) {
13229                 return text;
13230             }
13231
13232             if(sec >= 0) time = 600*time + 10*sec; else
13233             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13234
13235             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13236
13237             /* [HGM] PV time: now locate end of PV info */
13238             while( *++sep >= '0' && *sep <= '9'); // strip depth
13239             if(time >= 0)
13240             while( *++sep >= '0' && *sep <= '9'); // strip time
13241             if(sec >= 0)
13242             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13243             if(deci >= 0)
13244             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13245             while(*sep == ' ') sep++;
13246         }
13247
13248         if( depth <= 0 ) {
13249             return text;
13250         }
13251
13252         if( time < 0 ) {
13253             time = -1;
13254         }
13255
13256         pvInfoList[index-1].depth = depth;
13257         pvInfoList[index-1].score = score;
13258         pvInfoList[index-1].time  = 10*time; // centi-sec
13259         if(*sep == '}') *sep = 0; else *--sep = '{';
13260     }
13261     return sep;
13262 }
13263
13264 void
13265 SendToProgram(message, cps)
13266      char *message;
13267      ChessProgramState *cps;
13268 {
13269     int count, outCount, error;
13270     char buf[MSG_SIZ];
13271
13272     if (cps->pr == NULL) return;
13273     Attention(cps);
13274     
13275     if (appData.debugMode) {
13276         TimeMark now;
13277         GetTimeMark(&now);
13278         fprintf(debugFP, "%ld >%-6s: %s", 
13279                 SubtractTimeMarks(&now, &programStartTime),
13280                 cps->which, message);
13281     }
13282     
13283     count = strlen(message);
13284     outCount = OutputToProcess(cps->pr, message, count, &error);
13285     if (outCount < count && !exiting 
13286                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13287         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13288         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13289             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13290                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13291                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13292             } else {
13293                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13294             }
13295             gameInfo.resultDetails = StrSave(buf);
13296         }
13297         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13298     }
13299 }
13300
13301 void
13302 ReceiveFromProgram(isr, closure, message, count, error)
13303      InputSourceRef isr;
13304      VOIDSTAR closure;
13305      char *message;
13306      int count;
13307      int error;
13308 {
13309     char *end_str;
13310     char buf[MSG_SIZ];
13311     ChessProgramState *cps = (ChessProgramState *)closure;
13312
13313     if (isr != cps->isr) return; /* Killed intentionally */
13314     if (count <= 0) {
13315         if (count == 0) {
13316             sprintf(buf,
13317                     _("Error: %s chess program (%s) exited unexpectedly"),
13318                     cps->which, cps->program);
13319         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13320                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13321                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13322                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13323                 } else {
13324                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13325                 }
13326                 gameInfo.resultDetails = StrSave(buf);
13327             }
13328             RemoveInputSource(cps->isr);
13329             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13330         } else {
13331             sprintf(buf,
13332                     _("Error reading from %s chess program (%s)"),
13333                     cps->which, cps->program);
13334             RemoveInputSource(cps->isr);
13335
13336             /* [AS] Program is misbehaving badly... kill it */
13337             if( count == -2 ) {
13338                 DestroyChildProcess( cps->pr, 9 );
13339                 cps->pr = NoProc;
13340             }
13341
13342             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13343         }
13344         return;
13345     }
13346     
13347     if ((end_str = strchr(message, '\r')) != NULL)
13348       *end_str = NULLCHAR;
13349     if ((end_str = strchr(message, '\n')) != NULL)
13350       *end_str = NULLCHAR;
13351     
13352     if (appData.debugMode) {
13353         TimeMark now; int print = 1;
13354         char *quote = ""; char c; int i;
13355
13356         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13357                 char start = message[0];
13358                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13359                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13360                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13361                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13362                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13363                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13364                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13365                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13366                         { quote = "# "; print = (appData.engineComments == 2); }
13367                 message[0] = start; // restore original message
13368         }
13369         if(print) {
13370                 GetTimeMark(&now);
13371                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13372                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13373                         quote,
13374                         message);
13375         }
13376     }
13377
13378     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13379     if (appData.icsEngineAnalyze) {
13380         if (strstr(message, "whisper") != NULL ||
13381              strstr(message, "kibitz") != NULL || 
13382             strstr(message, "tellics") != NULL) return;
13383     }
13384
13385     HandleMachineMove(message, cps);
13386 }
13387
13388
13389 void
13390 SendTimeControl(cps, mps, tc, inc, sd, st)
13391      ChessProgramState *cps;
13392      int mps, inc, sd, st;
13393      long tc;
13394 {
13395     char buf[MSG_SIZ];
13396     int seconds;
13397
13398     if( timeControl_2 > 0 ) {
13399         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13400             tc = timeControl_2;
13401         }
13402     }
13403     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13404     inc /= cps->timeOdds;
13405     st  /= cps->timeOdds;
13406
13407     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13408
13409     if (st > 0) {
13410       /* Set exact time per move, normally using st command */
13411       if (cps->stKludge) {
13412         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13413         seconds = st % 60;
13414         if (seconds == 0) {
13415           sprintf(buf, "level 1 %d\n", st/60);
13416         } else {
13417           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13418         }
13419       } else {
13420         sprintf(buf, "st %d\n", st);
13421       }
13422     } else {
13423       /* Set conventional or incremental time control, using level command */
13424       if (seconds == 0) {
13425         /* Note old gnuchess bug -- minutes:seconds used to not work.
13426            Fixed in later versions, but still avoid :seconds
13427            when seconds is 0. */
13428         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13429       } else {
13430         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13431                 seconds, inc/1000);
13432       }
13433     }
13434     SendToProgram(buf, cps);
13435
13436     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13437     /* Orthogonally, limit search to given depth */
13438     if (sd > 0) {
13439       if (cps->sdKludge) {
13440         sprintf(buf, "depth\n%d\n", sd);
13441       } else {
13442         sprintf(buf, "sd %d\n", sd);
13443       }
13444       SendToProgram(buf, cps);
13445     }
13446
13447     if(cps->nps > 0) { /* [HGM] nps */
13448         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13449         else {
13450                 sprintf(buf, "nps %d\n", cps->nps);
13451               SendToProgram(buf, cps);
13452         }
13453     }
13454 }
13455
13456 ChessProgramState *WhitePlayer()
13457 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13458 {
13459     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13460        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13461         return &second;
13462     return &first;
13463 }
13464
13465 void
13466 SendTimeRemaining(cps, machineWhite)
13467      ChessProgramState *cps;
13468      int /*boolean*/ machineWhite;
13469 {
13470     char message[MSG_SIZ];
13471     long time, otime;
13472
13473     /* Note: this routine must be called when the clocks are stopped
13474        or when they have *just* been set or switched; otherwise
13475        it will be off by the time since the current tick started.
13476     */
13477     if (machineWhite) {
13478         time = whiteTimeRemaining / 10;
13479         otime = blackTimeRemaining / 10;
13480     } else {
13481         time = blackTimeRemaining / 10;
13482         otime = whiteTimeRemaining / 10;
13483     }
13484     /* [HGM] translate opponent's time by time-odds factor */
13485     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13486     if (appData.debugMode) {
13487         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13488     }
13489
13490     if (time <= 0) time = 1;
13491     if (otime <= 0) otime = 1;
13492     
13493     sprintf(message, "time %ld\n", time);
13494     SendToProgram(message, cps);
13495
13496     sprintf(message, "otim %ld\n", otime);
13497     SendToProgram(message, cps);
13498 }
13499
13500 int
13501 BoolFeature(p, name, loc, cps)
13502      char **p;
13503      char *name;
13504      int *loc;
13505      ChessProgramState *cps;
13506 {
13507   char buf[MSG_SIZ];
13508   int len = strlen(name);
13509   int val;
13510   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13511     (*p) += len + 1;
13512     sscanf(*p, "%d", &val);
13513     *loc = (val != 0);
13514     while (**p && **p != ' ') (*p)++;
13515     sprintf(buf, "accepted %s\n", name);
13516     SendToProgram(buf, cps);
13517     return TRUE;
13518   }
13519   return FALSE;
13520 }
13521
13522 int
13523 IntFeature(p, name, loc, cps)
13524      char **p;
13525      char *name;
13526      int *loc;
13527      ChessProgramState *cps;
13528 {
13529   char buf[MSG_SIZ];
13530   int len = strlen(name);
13531   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13532     (*p) += len + 1;
13533     sscanf(*p, "%d", loc);
13534     while (**p && **p != ' ') (*p)++;
13535     sprintf(buf, "accepted %s\n", name);
13536     SendToProgram(buf, cps);
13537     return TRUE;
13538   }
13539   return FALSE;
13540 }
13541
13542 int
13543 StringFeature(p, name, loc, cps)
13544      char **p;
13545      char *name;
13546      char loc[];
13547      ChessProgramState *cps;
13548 {
13549   char buf[MSG_SIZ];
13550   int len = strlen(name);
13551   if (strncmp((*p), name, len) == 0
13552       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13553     (*p) += len + 2;
13554     sscanf(*p, "%[^\"]", loc);
13555     while (**p && **p != '\"') (*p)++;
13556     if (**p == '\"') (*p)++;
13557     sprintf(buf, "accepted %s\n", name);
13558     SendToProgram(buf, cps);
13559     return TRUE;
13560   }
13561   return FALSE;
13562 }
13563
13564 int 
13565 ParseOption(Option *opt, ChessProgramState *cps)
13566 // [HGM] options: process the string that defines an engine option, and determine
13567 // name, type, default value, and allowed value range
13568 {
13569         char *p, *q, buf[MSG_SIZ];
13570         int n, min = (-1)<<31, max = 1<<31, def;
13571
13572         if(p = strstr(opt->name, " -spin ")) {
13573             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13574             if(max < min) max = min; // enforce consistency
13575             if(def < min) def = min;
13576             if(def > max) def = max;
13577             opt->value = def;
13578             opt->min = min;
13579             opt->max = max;
13580             opt->type = Spin;
13581         } else if((p = strstr(opt->name, " -slider "))) {
13582             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13583             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13584             if(max < min) max = min; // enforce consistency
13585             if(def < min) def = min;
13586             if(def > max) def = max;
13587             opt->value = def;
13588             opt->min = min;
13589             opt->max = max;
13590             opt->type = Spin; // Slider;
13591         } else if((p = strstr(opt->name, " -string "))) {
13592             opt->textValue = p+9;
13593             opt->type = TextBox;
13594         } else if((p = strstr(opt->name, " -file "))) {
13595             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13596             opt->textValue = p+7;
13597             opt->type = TextBox; // FileName;
13598         } else if((p = strstr(opt->name, " -path "))) {
13599             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13600             opt->textValue = p+7;
13601             opt->type = TextBox; // PathName;
13602         } else if(p = strstr(opt->name, " -check ")) {
13603             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13604             opt->value = (def != 0);
13605             opt->type = CheckBox;
13606         } else if(p = strstr(opt->name, " -combo ")) {
13607             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13608             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13609             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13610             opt->value = n = 0;
13611             while(q = StrStr(q, " /// ")) {
13612                 n++; *q = 0;    // count choices, and null-terminate each of them
13613                 q += 5;
13614                 if(*q == '*') { // remember default, which is marked with * prefix
13615                     q++;
13616                     opt->value = n;
13617                 }
13618                 cps->comboList[cps->comboCnt++] = q;
13619             }
13620             cps->comboList[cps->comboCnt++] = NULL;
13621             opt->max = n + 1;
13622             opt->type = ComboBox;
13623         } else if(p = strstr(opt->name, " -button")) {
13624             opt->type = Button;
13625         } else if(p = strstr(opt->name, " -save")) {
13626             opt->type = SaveButton;
13627         } else return FALSE;
13628         *p = 0; // terminate option name
13629         // now look if the command-line options define a setting for this engine option.
13630         if(cps->optionSettings && cps->optionSettings[0])
13631             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13632         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13633                 sprintf(buf, "option %s", p);
13634                 if(p = strstr(buf, ",")) *p = 0;
13635                 strcat(buf, "\n");
13636                 SendToProgram(buf, cps);
13637         }
13638         return TRUE;
13639 }
13640
13641 void
13642 FeatureDone(cps, val)
13643      ChessProgramState* cps;
13644      int val;
13645 {
13646   DelayedEventCallback cb = GetDelayedEvent();
13647   if ((cb == InitBackEnd3 && cps == &first) ||
13648       (cb == TwoMachinesEventIfReady && cps == &second)) {
13649     CancelDelayedEvent();
13650     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13651   }
13652   cps->initDone = val;
13653 }
13654
13655 /* Parse feature command from engine */
13656 void
13657 ParseFeatures(args, cps)
13658      char* args;
13659      ChessProgramState *cps;  
13660 {
13661   char *p = args;
13662   char *q;
13663   int val;
13664   char buf[MSG_SIZ];
13665
13666   for (;;) {
13667     while (*p == ' ') p++;
13668     if (*p == NULLCHAR) return;
13669
13670     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13671     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13672     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13673     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13674     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13675     if (BoolFeature(&p, "reuse", &val, cps)) {
13676       /* Engine can disable reuse, but can't enable it if user said no */
13677       if (!val) cps->reuse = FALSE;
13678       continue;
13679     }
13680     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13681     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13682       if (gameMode == TwoMachinesPlay) {
13683         DisplayTwoMachinesTitle();
13684       } else {
13685         DisplayTitle("");
13686       }
13687       continue;
13688     }
13689     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13690     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13691     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13692     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13693     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13694     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13695     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13696     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13697     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13698     if (IntFeature(&p, "done", &val, cps)) {
13699       FeatureDone(cps, val);
13700       continue;
13701     }
13702     /* Added by Tord: */
13703     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13704     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13705     /* End of additions by Tord */
13706
13707     /* [HGM] added features: */
13708     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13709     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13710     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13711     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13712     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13713     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13714     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13715         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13716             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13717             SendToProgram(buf, cps);
13718             continue;
13719         }
13720         if(cps->nrOptions >= MAX_OPTIONS) {
13721             cps->nrOptions--;
13722             sprintf(buf, "%s engine has too many options\n", cps->which);
13723             DisplayError(buf, 0);
13724         }
13725         continue;
13726     }
13727     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13728     /* End of additions by HGM */
13729
13730     /* unknown feature: complain and skip */
13731     q = p;
13732     while (*q && *q != '=') q++;
13733     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13734     SendToProgram(buf, cps);
13735     p = q;
13736     if (*p == '=') {
13737       p++;
13738       if (*p == '\"') {
13739         p++;
13740         while (*p && *p != '\"') p++;
13741         if (*p == '\"') p++;
13742       } else {
13743         while (*p && *p != ' ') p++;
13744       }
13745     }
13746   }
13747
13748 }
13749
13750 void
13751 PeriodicUpdatesEvent(newState)
13752      int newState;
13753 {
13754     if (newState == appData.periodicUpdates)
13755       return;
13756
13757     appData.periodicUpdates=newState;
13758
13759     /* Display type changes, so update it now */
13760 //    DisplayAnalysis();
13761
13762     /* Get the ball rolling again... */
13763     if (newState) {
13764         AnalysisPeriodicEvent(1);
13765         StartAnalysisClock();
13766     }
13767 }
13768
13769 void
13770 PonderNextMoveEvent(newState)
13771      int newState;
13772 {
13773     if (newState == appData.ponderNextMove) return;
13774     if (gameMode == EditPosition) EditPositionDone(TRUE);
13775     if (newState) {
13776         SendToProgram("hard\n", &first);
13777         if (gameMode == TwoMachinesPlay) {
13778             SendToProgram("hard\n", &second);
13779         }
13780     } else {
13781         SendToProgram("easy\n", &first);
13782         thinkOutput[0] = NULLCHAR;
13783         if (gameMode == TwoMachinesPlay) {
13784             SendToProgram("easy\n", &second);
13785         }
13786     }
13787     appData.ponderNextMove = newState;
13788 }
13789
13790 void
13791 NewSettingEvent(option, command, value)
13792      char *command;
13793      int option, value;
13794 {
13795     char buf[MSG_SIZ];
13796
13797     if (gameMode == EditPosition) EditPositionDone(TRUE);
13798     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13799     SendToProgram(buf, &first);
13800     if (gameMode == TwoMachinesPlay) {
13801         SendToProgram(buf, &second);
13802     }
13803 }
13804
13805 void
13806 ShowThinkingEvent()
13807 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13808 {
13809     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13810     int newState = appData.showThinking
13811         // [HGM] thinking: other features now need thinking output as well
13812         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13813     
13814     if (oldState == newState) return;
13815     oldState = newState;
13816     if (gameMode == EditPosition) EditPositionDone(TRUE);
13817     if (oldState) {
13818         SendToProgram("post\n", &first);
13819         if (gameMode == TwoMachinesPlay) {
13820             SendToProgram("post\n", &second);
13821         }
13822     } else {
13823         SendToProgram("nopost\n", &first);
13824         thinkOutput[0] = NULLCHAR;
13825         if (gameMode == TwoMachinesPlay) {
13826             SendToProgram("nopost\n", &second);
13827         }
13828     }
13829 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13830 }
13831
13832 void
13833 AskQuestionEvent(title, question, replyPrefix, which)
13834      char *title; char *question; char *replyPrefix; char *which;
13835 {
13836   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13837   if (pr == NoProc) return;
13838   AskQuestion(title, question, replyPrefix, pr);
13839 }
13840
13841 void
13842 DisplayMove(moveNumber)
13843      int moveNumber;
13844 {
13845     char message[MSG_SIZ];
13846     char res[MSG_SIZ];
13847     char cpThinkOutput[MSG_SIZ];
13848
13849     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13850     
13851     if (moveNumber == forwardMostMove - 1 || 
13852         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13853
13854         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13855
13856         if (strchr(cpThinkOutput, '\n')) {
13857             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13858         }
13859     } else {
13860         *cpThinkOutput = NULLCHAR;
13861     }
13862
13863     /* [AS] Hide thinking from human user */
13864     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13865         *cpThinkOutput = NULLCHAR;
13866         if( thinkOutput[0] != NULLCHAR ) {
13867             int i;
13868
13869             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13870                 cpThinkOutput[i] = '.';
13871             }
13872             cpThinkOutput[i] = NULLCHAR;
13873             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13874         }
13875     }
13876
13877     if (moveNumber == forwardMostMove - 1 &&
13878         gameInfo.resultDetails != NULL) {
13879         if (gameInfo.resultDetails[0] == NULLCHAR) {
13880             sprintf(res, " %s", PGNResult(gameInfo.result));
13881         } else {
13882             sprintf(res, " {%s} %s",
13883                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13884         }
13885     } else {
13886         res[0] = NULLCHAR;
13887     }
13888
13889     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13890         DisplayMessage(res, cpThinkOutput);
13891     } else {
13892         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13893                 WhiteOnMove(moveNumber) ? " " : ".. ",
13894                 parseList[moveNumber], res);
13895         DisplayMessage(message, cpThinkOutput);
13896     }
13897 }
13898
13899 void
13900 DisplayComment(moveNumber, text)
13901      int moveNumber;
13902      char *text;
13903 {
13904     char title[MSG_SIZ];
13905     char buf[8000]; // comment can be long!
13906     int score, depth;
13907     
13908     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13909       strcpy(title, "Comment");
13910     } else {
13911       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13912               WhiteOnMove(moveNumber) ? " " : ".. ",
13913               parseList[moveNumber]);
13914     }
13915     // [HGM] PV info: display PV info together with (or as) comment
13916     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13917       if(text == NULL) text = "";                                           
13918       score = pvInfoList[moveNumber].score;
13919       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13920               depth, (pvInfoList[moveNumber].time+50)/100, text);
13921       text = buf;
13922     }
13923     if (text != NULL && (appData.autoDisplayComment || commentUp))
13924         CommentPopUp(title, text);
13925 }
13926
13927 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13928  * might be busy thinking or pondering.  It can be omitted if your
13929  * gnuchess is configured to stop thinking immediately on any user
13930  * input.  However, that gnuchess feature depends on the FIONREAD
13931  * ioctl, which does not work properly on some flavors of Unix.
13932  */
13933 void
13934 Attention(cps)
13935      ChessProgramState *cps;
13936 {
13937 #if ATTENTION
13938     if (!cps->useSigint) return;
13939     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13940     switch (gameMode) {
13941       case MachinePlaysWhite:
13942       case MachinePlaysBlack:
13943       case TwoMachinesPlay:
13944       case IcsPlayingWhite:
13945       case IcsPlayingBlack:
13946       case AnalyzeMode:
13947       case AnalyzeFile:
13948         /* Skip if we know it isn't thinking */
13949         if (!cps->maybeThinking) return;
13950         if (appData.debugMode)
13951           fprintf(debugFP, "Interrupting %s\n", cps->which);
13952         InterruptChildProcess(cps->pr);
13953         cps->maybeThinking = FALSE;
13954         break;
13955       default:
13956         break;
13957     }
13958 #endif /*ATTENTION*/
13959 }
13960
13961 int
13962 CheckFlags()
13963 {
13964     if (whiteTimeRemaining <= 0) {
13965         if (!whiteFlag) {
13966             whiteFlag = TRUE;
13967             if (appData.icsActive) {
13968                 if (appData.autoCallFlag &&
13969                     gameMode == IcsPlayingBlack && !blackFlag) {
13970                   SendToICS(ics_prefix);
13971                   SendToICS("flag\n");
13972                 }
13973             } else {
13974                 if (blackFlag) {
13975                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13976                 } else {
13977                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13978                     if (appData.autoCallFlag) {
13979                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13980                         return TRUE;
13981                     }
13982                 }
13983             }
13984         }
13985     }
13986     if (blackTimeRemaining <= 0) {
13987         if (!blackFlag) {
13988             blackFlag = TRUE;
13989             if (appData.icsActive) {
13990                 if (appData.autoCallFlag &&
13991                     gameMode == IcsPlayingWhite && !whiteFlag) {
13992                   SendToICS(ics_prefix);
13993                   SendToICS("flag\n");
13994                 }
13995             } else {
13996                 if (whiteFlag) {
13997                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13998                 } else {
13999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14000                     if (appData.autoCallFlag) {
14001                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14002                         return TRUE;
14003                     }
14004                 }
14005             }
14006         }
14007     }
14008     return FALSE;
14009 }
14010
14011 void
14012 CheckTimeControl()
14013 {
14014     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14015         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14016
14017     /*
14018      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14019      */
14020     if ( !WhiteOnMove(forwardMostMove) )
14021         /* White made time control */
14022         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14023         /* [HGM] time odds: correct new time quota for time odds! */
14024                                             / WhitePlayer()->timeOdds;
14025       else
14026         /* Black made time control */
14027         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14028                                             / WhitePlayer()->other->timeOdds;
14029 }
14030
14031 void
14032 DisplayBothClocks()
14033 {
14034     int wom = gameMode == EditPosition ?
14035       !blackPlaysFirst : WhiteOnMove(currentMove);
14036     DisplayWhiteClock(whiteTimeRemaining, wom);
14037     DisplayBlackClock(blackTimeRemaining, !wom);
14038 }
14039
14040
14041 /* Timekeeping seems to be a portability nightmare.  I think everyone
14042    has ftime(), but I'm really not sure, so I'm including some ifdefs
14043    to use other calls if you don't.  Clocks will be less accurate if
14044    you have neither ftime nor gettimeofday.
14045 */
14046
14047 /* VS 2008 requires the #include outside of the function */
14048 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14049 #include <sys/timeb.h>
14050 #endif
14051
14052 /* Get the current time as a TimeMark */
14053 void
14054 GetTimeMark(tm)
14055      TimeMark *tm;
14056 {
14057 #if HAVE_GETTIMEOFDAY
14058
14059     struct timeval timeVal;
14060     struct timezone timeZone;
14061
14062     gettimeofday(&timeVal, &timeZone);
14063     tm->sec = (long) timeVal.tv_sec; 
14064     tm->ms = (int) (timeVal.tv_usec / 1000L);
14065
14066 #else /*!HAVE_GETTIMEOFDAY*/
14067 #if HAVE_FTIME
14068
14069 // include <sys/timeb.h> / moved to just above start of function
14070     struct timeb timeB;
14071
14072     ftime(&timeB);
14073     tm->sec = (long) timeB.time;
14074     tm->ms = (int) timeB.millitm;
14075
14076 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14077     tm->sec = (long) time(NULL);
14078     tm->ms = 0;
14079 #endif
14080 #endif
14081 }
14082
14083 /* Return the difference in milliseconds between two
14084    time marks.  We assume the difference will fit in a long!
14085 */
14086 long
14087 SubtractTimeMarks(tm2, tm1)
14088      TimeMark *tm2, *tm1;
14089 {
14090     return 1000L*(tm2->sec - tm1->sec) +
14091            (long) (tm2->ms - tm1->ms);
14092 }
14093
14094
14095 /*
14096  * Code to manage the game clocks.
14097  *
14098  * In tournament play, black starts the clock and then white makes a move.
14099  * We give the human user a slight advantage if he is playing white---the
14100  * clocks don't run until he makes his first move, so it takes zero time.
14101  * Also, we don't account for network lag, so we could get out of sync
14102  * with GNU Chess's clock -- but then, referees are always right.  
14103  */
14104
14105 static TimeMark tickStartTM;
14106 static long intendedTickLength;
14107
14108 long
14109 NextTickLength(timeRemaining)
14110      long timeRemaining;
14111 {
14112     long nominalTickLength, nextTickLength;
14113
14114     if (timeRemaining > 0L && timeRemaining <= 10000L)
14115       nominalTickLength = 100L;
14116     else
14117       nominalTickLength = 1000L;
14118     nextTickLength = timeRemaining % nominalTickLength;
14119     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14120
14121     return nextTickLength;
14122 }
14123
14124 /* Adjust clock one minute up or down */
14125 void
14126 AdjustClock(Boolean which, int dir)
14127 {
14128     if(which) blackTimeRemaining += 60000*dir;
14129     else      whiteTimeRemaining += 60000*dir;
14130     DisplayBothClocks();
14131 }
14132
14133 /* Stop clocks and reset to a fresh time control */
14134 void
14135 ResetClocks() 
14136 {
14137     (void) StopClockTimer();
14138     if (appData.icsActive) {
14139         whiteTimeRemaining = blackTimeRemaining = 0;
14140     } else if (searchTime) {
14141         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14142         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14143     } else { /* [HGM] correct new time quote for time odds */
14144         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14145         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14146     }
14147     if (whiteFlag || blackFlag) {
14148         DisplayTitle("");
14149         whiteFlag = blackFlag = FALSE;
14150     }
14151     DisplayBothClocks();
14152 }
14153
14154 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14155
14156 /* Decrement running clock by amount of time that has passed */
14157 void
14158 DecrementClocks()
14159 {
14160     long timeRemaining;
14161     long lastTickLength, fudge;
14162     TimeMark now;
14163
14164     if (!appData.clockMode) return;
14165     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14166         
14167     GetTimeMark(&now);
14168
14169     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14170
14171     /* Fudge if we woke up a little too soon */
14172     fudge = intendedTickLength - lastTickLength;
14173     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14174
14175     if (WhiteOnMove(forwardMostMove)) {
14176         if(whiteNPS >= 0) lastTickLength = 0;
14177         timeRemaining = whiteTimeRemaining -= lastTickLength;
14178         DisplayWhiteClock(whiteTimeRemaining - fudge,
14179                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14180     } else {
14181         if(blackNPS >= 0) lastTickLength = 0;
14182         timeRemaining = blackTimeRemaining -= lastTickLength;
14183         DisplayBlackClock(blackTimeRemaining - fudge,
14184                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14185     }
14186
14187     if (CheckFlags()) return;
14188         
14189     tickStartTM = now;
14190     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14191     StartClockTimer(intendedTickLength);
14192
14193     /* if the time remaining has fallen below the alarm threshold, sound the
14194      * alarm. if the alarm has sounded and (due to a takeback or time control
14195      * with increment) the time remaining has increased to a level above the
14196      * threshold, reset the alarm so it can sound again. 
14197      */
14198     
14199     if (appData.icsActive && appData.icsAlarm) {
14200
14201         /* make sure we are dealing with the user's clock */
14202         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14203                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14204            )) return;
14205
14206         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14207             alarmSounded = FALSE;
14208         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14209             PlayAlarmSound();
14210             alarmSounded = TRUE;
14211         }
14212     }
14213 }
14214
14215
14216 /* A player has just moved, so stop the previously running
14217    clock and (if in clock mode) start the other one.
14218    We redisplay both clocks in case we're in ICS mode, because
14219    ICS gives us an update to both clocks after every move.
14220    Note that this routine is called *after* forwardMostMove
14221    is updated, so the last fractional tick must be subtracted
14222    from the color that is *not* on move now.
14223 */
14224 void
14225 SwitchClocks(int newMoveNr)
14226 {
14227     long lastTickLength;
14228     TimeMark now;
14229     int flagged = FALSE;
14230
14231     GetTimeMark(&now);
14232
14233     if (StopClockTimer() && appData.clockMode) {
14234         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14235         if (!WhiteOnMove(forwardMostMove)) {
14236             if(blackNPS >= 0) lastTickLength = 0;
14237             blackTimeRemaining -= lastTickLength;
14238            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14239 //         if(pvInfoList[forwardMostMove-1].time == -1)
14240                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14241                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14242         } else {
14243            if(whiteNPS >= 0) lastTickLength = 0;
14244            whiteTimeRemaining -= lastTickLength;
14245            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14246 //         if(pvInfoList[forwardMostMove-1].time == -1)
14247                  pvInfoList[forwardMostMove-1].time = 
14248                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14249         }
14250         flagged = CheckFlags();
14251     }
14252     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14253     CheckTimeControl();
14254
14255     if (flagged || !appData.clockMode) return;
14256
14257     switch (gameMode) {
14258       case MachinePlaysBlack:
14259       case MachinePlaysWhite:
14260       case BeginningOfGame:
14261         if (pausing) return;
14262         break;
14263
14264       case EditGame:
14265       case PlayFromGameFile:
14266       case IcsExamining:
14267         return;
14268
14269       default:
14270         break;
14271     }
14272
14273     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14274         if(WhiteOnMove(forwardMostMove))
14275              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14276         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14277     }
14278
14279     tickStartTM = now;
14280     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14281       whiteTimeRemaining : blackTimeRemaining);
14282     StartClockTimer(intendedTickLength);
14283 }
14284         
14285
14286 /* Stop both clocks */
14287 void
14288 StopClocks()
14289 {       
14290     long lastTickLength;
14291     TimeMark now;
14292
14293     if (!StopClockTimer()) return;
14294     if (!appData.clockMode) return;
14295
14296     GetTimeMark(&now);
14297
14298     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14299     if (WhiteOnMove(forwardMostMove)) {
14300         if(whiteNPS >= 0) lastTickLength = 0;
14301         whiteTimeRemaining -= lastTickLength;
14302         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14303     } else {
14304         if(blackNPS >= 0) lastTickLength = 0;
14305         blackTimeRemaining -= lastTickLength;
14306         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14307     }
14308     CheckFlags();
14309 }
14310         
14311 /* Start clock of player on move.  Time may have been reset, so
14312    if clock is already running, stop and restart it. */
14313 void
14314 StartClocks()
14315 {
14316     (void) StopClockTimer(); /* in case it was running already */
14317     DisplayBothClocks();
14318     if (CheckFlags()) return;
14319
14320     if (!appData.clockMode) return;
14321     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14322
14323     GetTimeMark(&tickStartTM);
14324     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14325       whiteTimeRemaining : blackTimeRemaining);
14326
14327    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14328     whiteNPS = blackNPS = -1; 
14329     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14330        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14331         whiteNPS = first.nps;
14332     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14333        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14334         blackNPS = first.nps;
14335     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14336         whiteNPS = second.nps;
14337     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14338         blackNPS = second.nps;
14339     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14340
14341     StartClockTimer(intendedTickLength);
14342 }
14343
14344 char *
14345 TimeString(ms)
14346      long ms;
14347 {
14348     long second, minute, hour, day;
14349     char *sign = "";
14350     static char buf[32];
14351     
14352     if (ms > 0 && ms <= 9900) {
14353       /* convert milliseconds to tenths, rounding up */
14354       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14355
14356       sprintf(buf, " %03.1f ", tenths/10.0);
14357       return buf;
14358     }
14359
14360     /* convert milliseconds to seconds, rounding up */
14361     /* use floating point to avoid strangeness of integer division
14362        with negative dividends on many machines */
14363     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14364
14365     if (second < 0) {
14366         sign = "-";
14367         second = -second;
14368     }
14369     
14370     day = second / (60 * 60 * 24);
14371     second = second % (60 * 60 * 24);
14372     hour = second / (60 * 60);
14373     second = second % (60 * 60);
14374     minute = second / 60;
14375     second = second % 60;
14376     
14377     if (day > 0)
14378       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14379               sign, day, hour, minute, second);
14380     else if (hour > 0)
14381       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14382     else
14383       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14384     
14385     return buf;
14386 }
14387
14388
14389 /*
14390  * This is necessary because some C libraries aren't ANSI C compliant yet.
14391  */
14392 char *
14393 StrStr(string, match)
14394      char *string, *match;
14395 {
14396     int i, length;
14397     
14398     length = strlen(match);
14399     
14400     for (i = strlen(string) - length; i >= 0; i--, string++)
14401       if (!strncmp(match, string, length))
14402         return string;
14403     
14404     return NULL;
14405 }
14406
14407 char *
14408 StrCaseStr(string, match)
14409      char *string, *match;
14410 {
14411     int i, j, length;
14412     
14413     length = strlen(match);
14414     
14415     for (i = strlen(string) - length; i >= 0; i--, string++) {
14416         for (j = 0; j < length; j++) {
14417             if (ToLower(match[j]) != ToLower(string[j]))
14418               break;
14419         }
14420         if (j == length) return string;
14421     }
14422
14423     return NULL;
14424 }
14425
14426 #ifndef _amigados
14427 int
14428 StrCaseCmp(s1, s2)
14429      char *s1, *s2;
14430 {
14431     char c1, c2;
14432     
14433     for (;;) {
14434         c1 = ToLower(*s1++);
14435         c2 = ToLower(*s2++);
14436         if (c1 > c2) return 1;
14437         if (c1 < c2) return -1;
14438         if (c1 == NULLCHAR) return 0;
14439     }
14440 }
14441
14442
14443 int
14444 ToLower(c)
14445      int c;
14446 {
14447     return isupper(c) ? tolower(c) : c;
14448 }
14449
14450
14451 int
14452 ToUpper(c)
14453      int c;
14454 {
14455     return islower(c) ? toupper(c) : c;
14456 }
14457 #endif /* !_amigados    */
14458
14459 char *
14460 StrSave(s)
14461      char *s;
14462 {
14463     char *ret;
14464
14465     if ((ret = (char *) malloc(strlen(s) + 1))) {
14466         strcpy(ret, s);
14467     }
14468     return ret;
14469 }
14470
14471 char *
14472 StrSavePtr(s, savePtr)
14473      char *s, **savePtr;
14474 {
14475     if (*savePtr) {
14476         free(*savePtr);
14477     }
14478     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14479         strcpy(*savePtr, s);
14480     }
14481     return(*savePtr);
14482 }
14483
14484 char *
14485 PGNDate()
14486 {
14487     time_t clock;
14488     struct tm *tm;
14489     char buf[MSG_SIZ];
14490
14491     clock = time((time_t *)NULL);
14492     tm = localtime(&clock);
14493     sprintf(buf, "%04d.%02d.%02d",
14494             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14495     return StrSave(buf);
14496 }
14497
14498
14499 char *
14500 PositionToFEN(move, overrideCastling)
14501      int move;
14502      char *overrideCastling;
14503 {
14504     int i, j, fromX, fromY, toX, toY;
14505     int whiteToPlay;
14506     char buf[128];
14507     char *p, *q;
14508     int emptycount;
14509     ChessSquare piece;
14510
14511     whiteToPlay = (gameMode == EditPosition) ?
14512       !blackPlaysFirst : (move % 2 == 0);
14513     p = buf;
14514
14515     /* Piece placement data */
14516     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14517         emptycount = 0;
14518         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14519             if (boards[move][i][j] == EmptySquare) {
14520                 emptycount++;
14521             } else { ChessSquare piece = boards[move][i][j];
14522                 if (emptycount > 0) {
14523                     if(emptycount<10) /* [HGM] can be >= 10 */
14524                         *p++ = '0' + emptycount;
14525                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14526                     emptycount = 0;
14527                 }
14528                 if(PieceToChar(piece) == '+') {
14529                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14530                     *p++ = '+';
14531                     piece = (ChessSquare)(DEMOTED piece);
14532                 } 
14533                 *p++ = PieceToChar(piece);
14534                 if(p[-1] == '~') {
14535                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14536                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14537                     *p++ = '~';
14538                 }
14539             }
14540         }
14541         if (emptycount > 0) {
14542             if(emptycount<10) /* [HGM] can be >= 10 */
14543                 *p++ = '0' + emptycount;
14544             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14545             emptycount = 0;
14546         }
14547         *p++ = '/';
14548     }
14549     *(p - 1) = ' ';
14550
14551     /* [HGM] print Crazyhouse or Shogi holdings */
14552     if( gameInfo.holdingsWidth ) {
14553         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14554         q = p;
14555         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14556             piece = boards[move][i][BOARD_WIDTH-1];
14557             if( piece != EmptySquare )
14558               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14559                   *p++ = PieceToChar(piece);
14560         }
14561         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14562             piece = boards[move][BOARD_HEIGHT-i-1][0];
14563             if( piece != EmptySquare )
14564               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14565                   *p++ = PieceToChar(piece);
14566         }
14567
14568         if( q == p ) *p++ = '-';
14569         *p++ = ']';
14570         *p++ = ' ';
14571     }
14572
14573     /* Active color */
14574     *p++ = whiteToPlay ? 'w' : 'b';
14575     *p++ = ' ';
14576
14577   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14578     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14579   } else {
14580   if(nrCastlingRights) {
14581      q = p;
14582      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14583        /* [HGM] write directly from rights */
14584            if(boards[move][CASTLING][2] != NoRights &&
14585               boards[move][CASTLING][0] != NoRights   )
14586                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14587            if(boards[move][CASTLING][2] != NoRights &&
14588               boards[move][CASTLING][1] != NoRights   )
14589                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14590            if(boards[move][CASTLING][5] != NoRights &&
14591               boards[move][CASTLING][3] != NoRights   )
14592                 *p++ = boards[move][CASTLING][3] + AAA;
14593            if(boards[move][CASTLING][5] != NoRights &&
14594               boards[move][CASTLING][4] != NoRights   )
14595                 *p++ = boards[move][CASTLING][4] + AAA;
14596      } else {
14597
14598         /* [HGM] write true castling rights */
14599         if( nrCastlingRights == 6 ) {
14600             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14601                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14602             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14603                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14604             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14605                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14606             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14607                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14608         }
14609      }
14610      if (q == p) *p++ = '-'; /* No castling rights */
14611      *p++ = ' ';
14612   }
14613
14614   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14615      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14616     /* En passant target square */
14617     if (move > backwardMostMove) {
14618         fromX = moveList[move - 1][0] - AAA;
14619         fromY = moveList[move - 1][1] - ONE;
14620         toX = moveList[move - 1][2] - AAA;
14621         toY = moveList[move - 1][3] - ONE;
14622         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14623             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14624             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14625             fromX == toX) {
14626             /* 2-square pawn move just happened */
14627             *p++ = toX + AAA;
14628             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14629         } else {
14630             *p++ = '-';
14631         }
14632     } else if(move == backwardMostMove) {
14633         // [HGM] perhaps we should always do it like this, and forget the above?
14634         if((signed char)boards[move][EP_STATUS] >= 0) {
14635             *p++ = boards[move][EP_STATUS] + AAA;
14636             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14637         } else {
14638             *p++ = '-';
14639         }
14640     } else {
14641         *p++ = '-';
14642     }
14643     *p++ = ' ';
14644   }
14645   }
14646
14647     /* [HGM] find reversible plies */
14648     {   int i = 0, j=move;
14649
14650         if (appData.debugMode) { int k;
14651             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14652             for(k=backwardMostMove; k<=forwardMostMove; k++)
14653                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14654
14655         }
14656
14657         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14658         if( j == backwardMostMove ) i += initialRulePlies;
14659         sprintf(p, "%d ", i);
14660         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14661     }
14662     /* Fullmove number */
14663     sprintf(p, "%d", (move / 2) + 1);
14664     
14665     return StrSave(buf);
14666 }
14667
14668 Boolean
14669 ParseFEN(board, blackPlaysFirst, fen)
14670     Board board;
14671      int *blackPlaysFirst;
14672      char *fen;
14673 {
14674     int i, j;
14675     char *p;
14676     int emptycount;
14677     ChessSquare piece;
14678
14679     p = fen;
14680
14681     /* [HGM] by default clear Crazyhouse holdings, if present */
14682     if(gameInfo.holdingsWidth) {
14683        for(i=0; i<BOARD_HEIGHT; i++) {
14684            board[i][0]             = EmptySquare; /* black holdings */
14685            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14686            board[i][1]             = (ChessSquare) 0; /* black counts */
14687            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14688        }
14689     }
14690
14691     /* Piece placement data */
14692     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14693         j = 0;
14694         for (;;) {
14695             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14696                 if (*p == '/') p++;
14697                 emptycount = gameInfo.boardWidth - j;
14698                 while (emptycount--)
14699                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14700                 break;
14701 #if(BOARD_FILES >= 10)
14702             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14703                 p++; emptycount=10;
14704                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14705                 while (emptycount--)
14706                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14707 #endif
14708             } else if (isdigit(*p)) {
14709                 emptycount = *p++ - '0';
14710                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14711                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14712                 while (emptycount--)
14713                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14714             } else if (*p == '+' || isalpha(*p)) {
14715                 if (j >= gameInfo.boardWidth) return FALSE;
14716                 if(*p=='+') {
14717                     piece = CharToPiece(*++p);
14718                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14719                     piece = (ChessSquare) (PROMOTED piece ); p++;
14720                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14721                 } else piece = CharToPiece(*p++);
14722
14723                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14724                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14725                     piece = (ChessSquare) (PROMOTED piece);
14726                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14727                     p++;
14728                 }
14729                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14730             } else {
14731                 return FALSE;
14732             }
14733         }
14734     }
14735     while (*p == '/' || *p == ' ') p++;
14736
14737     /* [HGM] look for Crazyhouse holdings here */
14738     while(*p==' ') p++;
14739     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14740         if(*p == '[') p++;
14741         if(*p == '-' ) *p++; /* empty holdings */ else {
14742             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14743             /* if we would allow FEN reading to set board size, we would   */
14744             /* have to add holdings and shift the board read so far here   */
14745             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14746                 *p++;
14747                 if((int) piece >= (int) BlackPawn ) {
14748                     i = (int)piece - (int)BlackPawn;
14749                     i = PieceToNumber((ChessSquare)i);
14750                     if( i >= gameInfo.holdingsSize ) return FALSE;
14751                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14752                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14753                 } else {
14754                     i = (int)piece - (int)WhitePawn;
14755                     i = PieceToNumber((ChessSquare)i);
14756                     if( i >= gameInfo.holdingsSize ) return FALSE;
14757                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14758                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14759                 }
14760             }
14761         }
14762         if(*p == ']') *p++;
14763     }
14764
14765     while(*p == ' ') p++;
14766
14767     /* Active color */
14768     switch (*p++) {
14769       case 'w':
14770         *blackPlaysFirst = FALSE;
14771         break;
14772       case 'b': 
14773         *blackPlaysFirst = TRUE;
14774         break;
14775       default:
14776         return FALSE;
14777     }
14778
14779     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14780     /* return the extra info in global variiables             */
14781
14782     /* set defaults in case FEN is incomplete */
14783     board[EP_STATUS] = EP_UNKNOWN;
14784     for(i=0; i<nrCastlingRights; i++ ) {
14785         board[CASTLING][i] =
14786             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14787     }   /* assume possible unless obviously impossible */
14788     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14789     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14790     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14791                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14792     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14793     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14794     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14795                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14796     FENrulePlies = 0;
14797
14798     while(*p==' ') p++;
14799     if(nrCastlingRights) {
14800       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14801           /* castling indicator present, so default becomes no castlings */
14802           for(i=0; i<nrCastlingRights; i++ ) {
14803                  board[CASTLING][i] = NoRights;
14804           }
14805       }
14806       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14807              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14808              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14809              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14810         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14811
14812         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14813             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14814             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14815         }
14816         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14817             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14818         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14819                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14820         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14821                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14822         switch(c) {
14823           case'K':
14824               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14825               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14826               board[CASTLING][2] = whiteKingFile;
14827               break;
14828           case'Q':
14829               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14830               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14831               board[CASTLING][2] = whiteKingFile;
14832               break;
14833           case'k':
14834               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14835               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14836               board[CASTLING][5] = blackKingFile;
14837               break;
14838           case'q':
14839               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14840               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14841               board[CASTLING][5] = blackKingFile;
14842           case '-':
14843               break;
14844           default: /* FRC castlings */
14845               if(c >= 'a') { /* black rights */
14846                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14847                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14848                   if(i == BOARD_RGHT) break;
14849                   board[CASTLING][5] = i;
14850                   c -= AAA;
14851                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14852                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14853                   if(c > i)
14854                       board[CASTLING][3] = c;
14855                   else
14856                       board[CASTLING][4] = c;
14857               } else { /* white rights */
14858                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14859                     if(board[0][i] == WhiteKing) break;
14860                   if(i == BOARD_RGHT) break;
14861                   board[CASTLING][2] = i;
14862                   c -= AAA - 'a' + 'A';
14863                   if(board[0][c] >= WhiteKing) break;
14864                   if(c > i)
14865                       board[CASTLING][0] = c;
14866                   else
14867                       board[CASTLING][1] = c;
14868               }
14869         }
14870       }
14871       for(i=0; i<nrCastlingRights; i++)
14872         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14873     if (appData.debugMode) {
14874         fprintf(debugFP, "FEN castling rights:");
14875         for(i=0; i<nrCastlingRights; i++)
14876         fprintf(debugFP, " %d", board[CASTLING][i]);
14877         fprintf(debugFP, "\n");
14878     }
14879
14880       while(*p==' ') p++;
14881     }
14882
14883     /* read e.p. field in games that know e.p. capture */
14884     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14885        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14886       if(*p=='-') {
14887         p++; board[EP_STATUS] = EP_NONE;
14888       } else {
14889          char c = *p++ - AAA;
14890
14891          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14892          if(*p >= '0' && *p <='9') *p++;
14893          board[EP_STATUS] = c;
14894       }
14895     }
14896
14897
14898     if(sscanf(p, "%d", &i) == 1) {
14899         FENrulePlies = i; /* 50-move ply counter */
14900         /* (The move number is still ignored)    */
14901     }
14902
14903     return TRUE;
14904 }
14905       
14906 void
14907 EditPositionPasteFEN(char *fen)
14908 {
14909   if (fen != NULL) {
14910     Board initial_position;
14911
14912     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14913       DisplayError(_("Bad FEN position in clipboard"), 0);
14914       return ;
14915     } else {
14916       int savedBlackPlaysFirst = blackPlaysFirst;
14917       EditPositionEvent();
14918       blackPlaysFirst = savedBlackPlaysFirst;
14919       CopyBoard(boards[0], initial_position);
14920       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14921       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14922       DisplayBothClocks();
14923       DrawPosition(FALSE, boards[currentMove]);
14924     }
14925   }
14926 }
14927
14928 static char cseq[12] = "\\   ";
14929
14930 Boolean set_cont_sequence(char *new_seq)
14931 {
14932     int len;
14933     Boolean ret;
14934
14935     // handle bad attempts to set the sequence
14936         if (!new_seq)
14937                 return 0; // acceptable error - no debug
14938
14939     len = strlen(new_seq);
14940     ret = (len > 0) && (len < sizeof(cseq));
14941     if (ret)
14942         strcpy(cseq, new_seq);
14943     else if (appData.debugMode)
14944         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14945     return ret;
14946 }
14947
14948 /*
14949     reformat a source message so words don't cross the width boundary.  internal
14950     newlines are not removed.  returns the wrapped size (no null character unless
14951     included in source message).  If dest is NULL, only calculate the size required
14952     for the dest buffer.  lp argument indicats line position upon entry, and it's
14953     passed back upon exit.
14954 */
14955 int wrap(char *dest, char *src, int count, int width, int *lp)
14956 {
14957     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14958
14959     cseq_len = strlen(cseq);
14960     old_line = line = *lp;
14961     ansi = len = clen = 0;
14962
14963     for (i=0; i < count; i++)
14964     {
14965         if (src[i] == '\033')
14966             ansi = 1;
14967
14968         // if we hit the width, back up
14969         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14970         {
14971             // store i & len in case the word is too long
14972             old_i = i, old_len = len;
14973
14974             // find the end of the last word
14975             while (i && src[i] != ' ' && src[i] != '\n')
14976             {
14977                 i--;
14978                 len--;
14979             }
14980
14981             // word too long?  restore i & len before splitting it
14982             if ((old_i-i+clen) >= width)
14983             {
14984                 i = old_i;
14985                 len = old_len;
14986             }
14987
14988             // extra space?
14989             if (i && src[i-1] == ' ')
14990                 len--;
14991
14992             if (src[i] != ' ' && src[i] != '\n')
14993             {
14994                 i--;
14995                 if (len)
14996                     len--;
14997             }
14998
14999             // now append the newline and continuation sequence
15000             if (dest)
15001                 dest[len] = '\n';
15002             len++;
15003             if (dest)
15004                 strncpy(dest+len, cseq, cseq_len);
15005             len += cseq_len;
15006             line = cseq_len;
15007             clen = cseq_len;
15008             continue;
15009         }
15010
15011         if (dest)
15012             dest[len] = src[i];
15013         len++;
15014         if (!ansi)
15015             line++;
15016         if (src[i] == '\n')
15017             line = 0;
15018         if (src[i] == 'm')
15019             ansi = 0;
15020     }
15021     if (dest && appData.debugMode)
15022     {
15023         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15024             count, width, line, len, *lp);
15025         show_bytes(debugFP, src, count);
15026         fprintf(debugFP, "\ndest: ");
15027         show_bytes(debugFP, dest, len);
15028         fprintf(debugFP, "\n");
15029     }
15030     *lp = dest ? line : old_line;
15031
15032     return len;
15033 }
15034
15035 // [HGM] vari: routines for shelving variations
15036
15037 void 
15038 PushTail(int firstMove, int lastMove)
15039 {
15040         int i, j, nrMoves = lastMove - firstMove;
15041
15042         if(appData.icsActive) { // only in local mode
15043                 forwardMostMove = currentMove; // mimic old ICS behavior
15044                 return;
15045         }
15046         if(storedGames >= MAX_VARIATIONS-1) return;
15047
15048         // push current tail of game on stack
15049         savedResult[storedGames] = gameInfo.result;
15050         savedDetails[storedGames] = gameInfo.resultDetails;
15051         gameInfo.resultDetails = NULL;
15052         savedFirst[storedGames] = firstMove;
15053         savedLast [storedGames] = lastMove;
15054         savedFramePtr[storedGames] = framePtr;
15055         framePtr -= nrMoves; // reserve space for the boards
15056         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15057             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15058             for(j=0; j<MOVE_LEN; j++)
15059                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15060             for(j=0; j<2*MOVE_LEN; j++)
15061                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15062             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15063             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15064             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15065             pvInfoList[firstMove+i-1].depth = 0;
15066             commentList[framePtr+i] = commentList[firstMove+i];
15067             commentList[firstMove+i] = NULL;
15068         }
15069
15070         storedGames++;
15071         forwardMostMove = firstMove; // truncate game so we can start variation
15072         if(storedGames == 1) GreyRevert(FALSE);
15073 }
15074
15075 Boolean
15076 PopTail(Boolean annotate)
15077 {
15078         int i, j, nrMoves;
15079         char buf[8000], moveBuf[20];
15080
15081         if(appData.icsActive) return FALSE; // only in local mode
15082         if(!storedGames) return FALSE; // sanity
15083         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15084
15085         storedGames--;
15086         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15087         nrMoves = savedLast[storedGames] - currentMove;
15088         if(annotate) {
15089                 int cnt = 10;
15090                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15091                 else strcpy(buf, "(");
15092                 for(i=currentMove; i<forwardMostMove; i++) {
15093                         if(WhiteOnMove(i))
15094                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15095                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15096                         strcat(buf, moveBuf);
15097                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15098                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15099                 }
15100                 strcat(buf, ")");
15101         }
15102         for(i=1; i<=nrMoves; i++) { // copy last variation back
15103             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15104             for(j=0; j<MOVE_LEN; j++)
15105                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15106             for(j=0; j<2*MOVE_LEN; j++)
15107                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15108             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15109             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15110             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15111             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15112             commentList[currentMove+i] = commentList[framePtr+i];
15113             commentList[framePtr+i] = NULL;
15114         }
15115         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15116         framePtr = savedFramePtr[storedGames];
15117         gameInfo.result = savedResult[storedGames];
15118         if(gameInfo.resultDetails != NULL) {
15119             free(gameInfo.resultDetails);
15120       }
15121         gameInfo.resultDetails = savedDetails[storedGames];
15122         forwardMostMove = currentMove + nrMoves;
15123         if(storedGames == 0) GreyRevert(TRUE);
15124         return TRUE;
15125 }
15126
15127 void 
15128 CleanupTail()
15129 {       // remove all shelved variations
15130         int i;
15131         for(i=0; i<storedGames; i++) {
15132             if(savedDetails[i])
15133                 free(savedDetails[i]);
15134             savedDetails[i] = NULL;
15135         }
15136         for(i=framePtr; i<MAX_MOVES; i++) {
15137                 if(commentList[i]) free(commentList[i]);
15138                 commentList[i] = NULL;
15139         }
15140         framePtr = MAX_MOVES-1;
15141         storedGames = 0;
15142 }
15143
15144 void
15145 LoadVariation(int index, char *text)
15146 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15147         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15148         int level = 0, move;
15149
15150         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15151         // first find outermost bracketing variation
15152         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15153             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15154                 if(*p == '{') wait = '}'; else
15155                 if(*p == '[') wait = ']'; else
15156                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15157                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15158             }
15159             if(*p == wait) wait = NULLCHAR; // closing ]} found
15160             p++;
15161         }
15162         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15163         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15164         end[1] = NULLCHAR; // clip off comment beyond variation
15165         ToNrEvent(currentMove-1);
15166         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15167         // kludge: use ParsePV() to append variation to game
15168         move = currentMove;
15169         ParsePV(start, TRUE);
15170         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15171         ClearPremoveHighlights();
15172         CommentPopDown();
15173         ToNrEvent(currentMove+1);
15174 }