Print seconds with 2 digits in backgroundObserve status line
[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:%02d B: %d:%02d (%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           VariantClass v = StringToVariant(gameInfo.event);
10179           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10180           if(v < VariantShogi) gameInfo.variant = v;
10181         }
10182         if (!matchMode) {
10183           if( appData.autoDisplayTags ) {
10184             tags = PGNTags(&gameInfo);
10185             TagsPopUp(tags, CmailMsg());
10186             free(tags);
10187           }
10188         }
10189     } else {
10190         /* Make something up, but don't display it now */
10191         SetGameInfo();
10192         TagsPopDown();
10193     }
10194
10195     if (cm == PositionDiagram) {
10196         int i, j;
10197         char *p;
10198         Board initial_position;
10199
10200         if (appData.debugMode)
10201           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10202
10203         if (!startedFromSetupPosition) {
10204             p = yy_text;
10205             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10206               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10207                 switch (*p) {
10208                   case '[':
10209                   case '-':
10210                   case ' ':
10211                   case '\t':
10212                   case '\n':
10213                   case '\r':
10214                     break;
10215                   default:
10216                     initial_position[i][j++] = CharToPiece(*p);
10217                     break;
10218                 }
10219             while (*p == ' ' || *p == '\t' ||
10220                    *p == '\n' || *p == '\r') p++;
10221         
10222             if (strncmp(p, "black", strlen("black"))==0)
10223               blackPlaysFirst = TRUE;
10224             else
10225               blackPlaysFirst = FALSE;
10226             startedFromSetupPosition = TRUE;
10227         
10228             CopyBoard(boards[0], initial_position);
10229             if (blackPlaysFirst) {
10230                 currentMove = forwardMostMove = backwardMostMove = 1;
10231                 CopyBoard(boards[1], initial_position);
10232                 strcpy(moveList[0], "");
10233                 strcpy(parseList[0], "");
10234                 timeRemaining[0][1] = whiteTimeRemaining;
10235                 timeRemaining[1][1] = blackTimeRemaining;
10236                 if (commentList[0] != NULL) {
10237                     commentList[1] = commentList[0];
10238                     commentList[0] = NULL;
10239                 }
10240             } else {
10241                 currentMove = forwardMostMove = backwardMostMove = 0;
10242             }
10243         }
10244         yyboardindex = forwardMostMove;
10245         cm = (ChessMove) yylex();
10246     }
10247
10248     if (first.pr == NoProc) {
10249         StartChessProgram(&first);
10250     }
10251     InitChessProgram(&first, FALSE);
10252     SendToProgram("force\n", &first);
10253     if (startedFromSetupPosition) {
10254         SendBoard(&first, forwardMostMove);
10255     if (appData.debugMode) {
10256         fprintf(debugFP, "Load Game\n");
10257     }
10258         DisplayBothClocks();
10259     }      
10260
10261     /* [HGM] server: flag to write setup moves in broadcast file as one */
10262     loadFlag = appData.suppressLoadMoves;
10263
10264     while (cm == Comment) {
10265         char *p;
10266         if (appData.debugMode) 
10267           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10268         p = yy_text;
10269         AppendComment(currentMove, p, FALSE);
10270         yyboardindex = forwardMostMove;
10271         cm = (ChessMove) yylex();
10272     }
10273
10274     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10275         cm == WhiteWins || cm == BlackWins ||
10276         cm == GameIsDrawn || cm == GameUnfinished) {
10277         DisplayMessage("", _("No moves in game"));
10278         if (cmailMsgLoaded) {
10279             if (appData.debugMode)
10280               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10281             ClearHighlights();
10282             flipView = FALSE;
10283         }
10284         DrawPosition(FALSE, boards[currentMove]);
10285         DisplayBothClocks();
10286         gameMode = EditGame;
10287         ModeHighlight();
10288         gameFileFP = NULL;
10289         cmailOldMove = 0;
10290         return TRUE;
10291     }
10292
10293     // [HGM] PV info: routine tests if comment empty
10294     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10295         DisplayComment(currentMove - 1, commentList[currentMove]);
10296     }
10297     if (!matchMode && appData.timeDelay != 0) 
10298       DrawPosition(FALSE, boards[currentMove]);
10299
10300     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10301       programStats.ok_to_send = 1;
10302     }
10303
10304     /* if the first token after the PGN tags is a move
10305      * and not move number 1, retrieve it from the parser 
10306      */
10307     if (cm != MoveNumberOne)
10308         LoadGameOneMove(cm);
10309
10310     /* load the remaining moves from the file */
10311     while (LoadGameOneMove((ChessMove)0)) {
10312       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10313       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10314     }
10315
10316     /* rewind to the start of the game */
10317     currentMove = backwardMostMove;
10318
10319     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10320
10321     if (oldGameMode == AnalyzeFile ||
10322         oldGameMode == AnalyzeMode) {
10323       AnalyzeFileEvent();
10324     }
10325
10326     if (matchMode || appData.timeDelay == 0) {
10327       ToEndEvent();
10328       gameMode = EditGame;
10329       ModeHighlight();
10330     } else if (appData.timeDelay > 0) {
10331       AutoPlayGameLoop();
10332     }
10333
10334     if (appData.debugMode) 
10335         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10336
10337     loadFlag = 0; /* [HGM] true game starts */
10338     return TRUE;
10339 }
10340
10341 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10342 int
10343 ReloadPosition(offset)
10344      int offset;
10345 {
10346     int positionNumber = lastLoadPositionNumber + offset;
10347     if (lastLoadPositionFP == NULL) {
10348         DisplayError(_("No position has been loaded yet"), 0);
10349         return FALSE;
10350     }
10351     if (positionNumber <= 0) {
10352         DisplayError(_("Can't back up any further"), 0);
10353         return FALSE;
10354     }
10355     return LoadPosition(lastLoadPositionFP, positionNumber,
10356                         lastLoadPositionTitle);
10357 }
10358
10359 /* Load the nth position from the given file */
10360 int
10361 LoadPositionFromFile(filename, n, title)
10362      char *filename;
10363      int n;
10364      char *title;
10365 {
10366     FILE *f;
10367     char buf[MSG_SIZ];
10368
10369     if (strcmp(filename, "-") == 0) {
10370         return LoadPosition(stdin, n, "stdin");
10371     } else {
10372         f = fopen(filename, "rb");
10373         if (f == NULL) {
10374             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10375             DisplayError(buf, errno);
10376             return FALSE;
10377         } else {
10378             return LoadPosition(f, n, title);
10379         }
10380     }
10381 }
10382
10383 /* Load the nth position from the given open file, and close it */
10384 int
10385 LoadPosition(f, positionNumber, title)
10386      FILE *f;
10387      int positionNumber;
10388      char *title;
10389 {
10390     char *p, line[MSG_SIZ];
10391     Board initial_position;
10392     int i, j, fenMode, pn;
10393     
10394     if (gameMode == Training )
10395         SetTrainingModeOff();
10396
10397     if (gameMode != BeginningOfGame) {
10398         Reset(FALSE, TRUE);
10399     }
10400     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10401         fclose(lastLoadPositionFP);
10402     }
10403     if (positionNumber == 0) positionNumber = 1;
10404     lastLoadPositionFP = f;
10405     lastLoadPositionNumber = positionNumber;
10406     strcpy(lastLoadPositionTitle, title);
10407     if (first.pr == NoProc) {
10408       StartChessProgram(&first);
10409       InitChessProgram(&first, FALSE);
10410     }    
10411     pn = positionNumber;
10412     if (positionNumber < 0) {
10413         /* Negative position number means to seek to that byte offset */
10414         if (fseek(f, -positionNumber, 0) == -1) {
10415             DisplayError(_("Can't seek on position file"), 0);
10416             return FALSE;
10417         };
10418         pn = 1;
10419     } else {
10420         if (fseek(f, 0, 0) == -1) {
10421             if (f == lastLoadPositionFP ?
10422                 positionNumber == lastLoadPositionNumber + 1 :
10423                 positionNumber == 1) {
10424                 pn = 1;
10425             } else {
10426                 DisplayError(_("Can't seek on position file"), 0);
10427                 return FALSE;
10428             }
10429         }
10430     }
10431     /* See if this file is FEN or old-style xboard */
10432     if (fgets(line, MSG_SIZ, f) == NULL) {
10433         DisplayError(_("Position not found in file"), 0);
10434         return FALSE;
10435     }
10436     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10437     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10438
10439     if (pn >= 2) {
10440         if (fenMode || line[0] == '#') pn--;
10441         while (pn > 0) {
10442             /* skip positions before number pn */
10443             if (fgets(line, MSG_SIZ, f) == NULL) {
10444                 Reset(TRUE, TRUE);
10445                 DisplayError(_("Position not found in file"), 0);
10446                 return FALSE;
10447             }
10448             if (fenMode || line[0] == '#') pn--;
10449         }
10450     }
10451
10452     if (fenMode) {
10453         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10454             DisplayError(_("Bad FEN position in file"), 0);
10455             return FALSE;
10456         }
10457     } else {
10458         (void) fgets(line, MSG_SIZ, f);
10459         (void) fgets(line, MSG_SIZ, f);
10460     
10461         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10462             (void) fgets(line, MSG_SIZ, f);
10463             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10464                 if (*p == ' ')
10465                   continue;
10466                 initial_position[i][j++] = CharToPiece(*p);
10467             }
10468         }
10469     
10470         blackPlaysFirst = FALSE;
10471         if (!feof(f)) {
10472             (void) fgets(line, MSG_SIZ, f);
10473             if (strncmp(line, "black", strlen("black"))==0)
10474               blackPlaysFirst = TRUE;
10475         }
10476     }
10477     startedFromSetupPosition = TRUE;
10478     
10479     SendToProgram("force\n", &first);
10480     CopyBoard(boards[0], initial_position);
10481     if (blackPlaysFirst) {
10482         currentMove = forwardMostMove = backwardMostMove = 1;
10483         strcpy(moveList[0], "");
10484         strcpy(parseList[0], "");
10485         CopyBoard(boards[1], initial_position);
10486         DisplayMessage("", _("Black to play"));
10487     } else {
10488         currentMove = forwardMostMove = backwardMostMove = 0;
10489         DisplayMessage("", _("White to play"));
10490     }
10491     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10492     SendBoard(&first, forwardMostMove);
10493     if (appData.debugMode) {
10494 int i, j;
10495   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10496   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10497         fprintf(debugFP, "Load Position\n");
10498     }
10499
10500     if (positionNumber > 1) {
10501         sprintf(line, "%s %d", title, positionNumber);
10502         DisplayTitle(line);
10503     } else {
10504         DisplayTitle(title);
10505     }
10506     gameMode = EditGame;
10507     ModeHighlight();
10508     ResetClocks();
10509     timeRemaining[0][1] = whiteTimeRemaining;
10510     timeRemaining[1][1] = blackTimeRemaining;
10511     DrawPosition(FALSE, boards[currentMove]);
10512    
10513     return TRUE;
10514 }
10515
10516
10517 void
10518 CopyPlayerNameIntoFileName(dest, src)
10519      char **dest, *src;
10520 {
10521     while (*src != NULLCHAR && *src != ',') {
10522         if (*src == ' ') {
10523             *(*dest)++ = '_';
10524             src++;
10525         } else {
10526             *(*dest)++ = *src++;
10527         }
10528     }
10529 }
10530
10531 char *DefaultFileName(ext)
10532      char *ext;
10533 {
10534     static char def[MSG_SIZ];
10535     char *p;
10536
10537     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10538         p = def;
10539         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10540         *p++ = '-';
10541         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10542         *p++ = '.';
10543         strcpy(p, ext);
10544     } else {
10545         def[0] = NULLCHAR;
10546     }
10547     return def;
10548 }
10549
10550 /* Save the current game to the given file */
10551 int
10552 SaveGameToFile(filename, append)
10553      char *filename;
10554      int append;
10555 {
10556     FILE *f;
10557     char buf[MSG_SIZ];
10558
10559     if (strcmp(filename, "-") == 0) {
10560         return SaveGame(stdout, 0, NULL);
10561     } else {
10562         f = fopen(filename, append ? "a" : "w");
10563         if (f == NULL) {
10564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10565             DisplayError(buf, errno);
10566             return FALSE;
10567         } else {
10568             return SaveGame(f, 0, NULL);
10569         }
10570     }
10571 }
10572
10573 char *
10574 SavePart(str)
10575      char *str;
10576 {
10577     static char buf[MSG_SIZ];
10578     char *p;
10579     
10580     p = strchr(str, ' ');
10581     if (p == NULL) return str;
10582     strncpy(buf, str, p - str);
10583     buf[p - str] = NULLCHAR;
10584     return buf;
10585 }
10586
10587 #define PGN_MAX_LINE 75
10588
10589 #define PGN_SIDE_WHITE  0
10590 #define PGN_SIDE_BLACK  1
10591
10592 /* [AS] */
10593 static int FindFirstMoveOutOfBook( int side )
10594 {
10595     int result = -1;
10596
10597     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10598         int index = backwardMostMove;
10599         int has_book_hit = 0;
10600
10601         if( (index % 2) != side ) {
10602             index++;
10603         }
10604
10605         while( index < forwardMostMove ) {
10606             /* Check to see if engine is in book */
10607             int depth = pvInfoList[index].depth;
10608             int score = pvInfoList[index].score;
10609             int in_book = 0;
10610
10611             if( depth <= 2 ) {
10612                 in_book = 1;
10613             }
10614             else if( score == 0 && depth == 63 ) {
10615                 in_book = 1; /* Zappa */
10616             }
10617             else if( score == 2 && depth == 99 ) {
10618                 in_book = 1; /* Abrok */
10619             }
10620
10621             has_book_hit += in_book;
10622
10623             if( ! in_book ) {
10624                 result = index;
10625
10626                 break;
10627             }
10628
10629             index += 2;
10630         }
10631     }
10632
10633     return result;
10634 }
10635
10636 /* [AS] */
10637 void GetOutOfBookInfo( char * buf )
10638 {
10639     int oob[2];
10640     int i;
10641     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10642
10643     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10644     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10645
10646     *buf = '\0';
10647
10648     if( oob[0] >= 0 || oob[1] >= 0 ) {
10649         for( i=0; i<2; i++ ) {
10650             int idx = oob[i];
10651
10652             if( idx >= 0 ) {
10653                 if( i > 0 && oob[0] >= 0 ) {
10654                     strcat( buf, "   " );
10655                 }
10656
10657                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10658                 sprintf( buf+strlen(buf), "%s%.2f", 
10659                     pvInfoList[idx].score >= 0 ? "+" : "",
10660                     pvInfoList[idx].score / 100.0 );
10661             }
10662         }
10663     }
10664 }
10665
10666 /* Save game in PGN style and close the file */
10667 int
10668 SaveGamePGN(f)
10669      FILE *f;
10670 {
10671     int i, offset, linelen, newblock;
10672     time_t tm;
10673 //    char *movetext;
10674     char numtext[32];
10675     int movelen, numlen, blank;
10676     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10677
10678     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10679     
10680     tm = time((time_t *) NULL);
10681     
10682     PrintPGNTags(f, &gameInfo);
10683     
10684     if (backwardMostMove > 0 || startedFromSetupPosition) {
10685         char *fen = PositionToFEN(backwardMostMove, NULL);
10686         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10687         fprintf(f, "\n{--------------\n");
10688         PrintPosition(f, backwardMostMove);
10689         fprintf(f, "--------------}\n");
10690         free(fen);
10691     }
10692     else {
10693         /* [AS] Out of book annotation */
10694         if( appData.saveOutOfBookInfo ) {
10695             char buf[64];
10696
10697             GetOutOfBookInfo( buf );
10698
10699             if( buf[0] != '\0' ) {
10700                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10701             }
10702         }
10703
10704         fprintf(f, "\n");
10705     }
10706
10707     i = backwardMostMove;
10708     linelen = 0;
10709     newblock = TRUE;
10710
10711     while (i < forwardMostMove) {
10712         /* Print comments preceding this move */
10713         if (commentList[i] != NULL) {
10714             if (linelen > 0) fprintf(f, "\n");
10715             fprintf(f, "%s", commentList[i]);
10716             linelen = 0;
10717             newblock = TRUE;
10718         }
10719
10720         /* Format move number */
10721         if ((i % 2) == 0) {
10722             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10723         } else {
10724             if (newblock) {
10725                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10726             } else {
10727                 numtext[0] = NULLCHAR;
10728             }
10729         }
10730         numlen = strlen(numtext);
10731         newblock = FALSE;
10732
10733         /* Print move number */
10734         blank = linelen > 0 && numlen > 0;
10735         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10736             fprintf(f, "\n");
10737             linelen = 0;
10738             blank = 0;
10739         }
10740         if (blank) {
10741             fprintf(f, " ");
10742             linelen++;
10743         }
10744         fprintf(f, "%s", numtext);
10745         linelen += numlen;
10746
10747         /* Get move */
10748         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10749         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10750
10751         /* Print move */
10752         blank = linelen > 0 && movelen > 0;
10753         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10754             fprintf(f, "\n");
10755             linelen = 0;
10756             blank = 0;
10757         }
10758         if (blank) {
10759             fprintf(f, " ");
10760             linelen++;
10761         }
10762         fprintf(f, "%s", move_buffer);
10763         linelen += movelen;
10764
10765         /* [AS] Add PV info if present */
10766         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10767             /* [HGM] add time */
10768             char buf[MSG_SIZ]; int seconds;
10769
10770             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10771
10772             if( seconds <= 0) buf[0] = 0; else
10773             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10774                 seconds = (seconds + 4)/10; // round to full seconds
10775                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10776                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10777             }
10778
10779             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10780                 pvInfoList[i].score >= 0 ? "+" : "",
10781                 pvInfoList[i].score / 100.0,
10782                 pvInfoList[i].depth,
10783                 buf );
10784
10785             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10786
10787             /* Print score/depth */
10788             blank = linelen > 0 && movelen > 0;
10789             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10790                 fprintf(f, "\n");
10791                 linelen = 0;
10792                 blank = 0;
10793             }
10794             if (blank) {
10795                 fprintf(f, " ");
10796                 linelen++;
10797             }
10798             fprintf(f, "%s", move_buffer);
10799             linelen += movelen;
10800         }
10801
10802         i++;
10803     }
10804     
10805     /* Start a new line */
10806     if (linelen > 0) fprintf(f, "\n");
10807
10808     /* Print comments after last move */
10809     if (commentList[i] != NULL) {
10810         fprintf(f, "%s\n", commentList[i]);
10811     }
10812
10813     /* Print result */
10814     if (gameInfo.resultDetails != NULL &&
10815         gameInfo.resultDetails[0] != NULLCHAR) {
10816         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10817                 PGNResult(gameInfo.result));
10818     } else {
10819         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10820     }
10821
10822     fclose(f);
10823     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10824     return TRUE;
10825 }
10826
10827 /* Save game in old style and close the file */
10828 int
10829 SaveGameOldStyle(f)
10830      FILE *f;
10831 {
10832     int i, offset;
10833     time_t tm;
10834     
10835     tm = time((time_t *) NULL);
10836     
10837     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10838     PrintOpponents(f);
10839     
10840     if (backwardMostMove > 0 || startedFromSetupPosition) {
10841         fprintf(f, "\n[--------------\n");
10842         PrintPosition(f, backwardMostMove);
10843         fprintf(f, "--------------]\n");
10844     } else {
10845         fprintf(f, "\n");
10846     }
10847
10848     i = backwardMostMove;
10849     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10850
10851     while (i < forwardMostMove) {
10852         if (commentList[i] != NULL) {
10853             fprintf(f, "[%s]\n", commentList[i]);
10854         }
10855
10856         if ((i % 2) == 1) {
10857             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10858             i++;
10859         } else {
10860             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10861             i++;
10862             if (commentList[i] != NULL) {
10863                 fprintf(f, "\n");
10864                 continue;
10865             }
10866             if (i >= forwardMostMove) {
10867                 fprintf(f, "\n");
10868                 break;
10869             }
10870             fprintf(f, "%s\n", parseList[i]);
10871             i++;
10872         }
10873     }
10874     
10875     if (commentList[i] != NULL) {
10876         fprintf(f, "[%s]\n", commentList[i]);
10877     }
10878
10879     /* This isn't really the old style, but it's close enough */
10880     if (gameInfo.resultDetails != NULL &&
10881         gameInfo.resultDetails[0] != NULLCHAR) {
10882         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10883                 gameInfo.resultDetails);
10884     } else {
10885         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10886     }
10887
10888     fclose(f);
10889     return TRUE;
10890 }
10891
10892 /* Save the current game to open file f and close the file */
10893 int
10894 SaveGame(f, dummy, dummy2)
10895      FILE *f;
10896      int dummy;
10897      char *dummy2;
10898 {
10899     if (gameMode == EditPosition) EditPositionDone(TRUE);
10900     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10901     if (appData.oldSaveStyle)
10902       return SaveGameOldStyle(f);
10903     else
10904       return SaveGamePGN(f);
10905 }
10906
10907 /* Save the current position to the given file */
10908 int
10909 SavePositionToFile(filename)
10910      char *filename;
10911 {
10912     FILE *f;
10913     char buf[MSG_SIZ];
10914
10915     if (strcmp(filename, "-") == 0) {
10916         return SavePosition(stdout, 0, NULL);
10917     } else {
10918         f = fopen(filename, "a");
10919         if (f == NULL) {
10920             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10921             DisplayError(buf, errno);
10922             return FALSE;
10923         } else {
10924             SavePosition(f, 0, NULL);
10925             return TRUE;
10926         }
10927     }
10928 }
10929
10930 /* Save the current position to the given open file and close the file */
10931 int
10932 SavePosition(f, dummy, dummy2)
10933      FILE *f;
10934      int dummy;
10935      char *dummy2;
10936 {
10937     time_t tm;
10938     char *fen;
10939     
10940     if (gameMode == EditPosition) EditPositionDone(TRUE);
10941     if (appData.oldSaveStyle) {
10942         tm = time((time_t *) NULL);
10943     
10944         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10945         PrintOpponents(f);
10946         fprintf(f, "[--------------\n");
10947         PrintPosition(f, currentMove);
10948         fprintf(f, "--------------]\n");
10949     } else {
10950         fen = PositionToFEN(currentMove, NULL);
10951         fprintf(f, "%s\n", fen);
10952         free(fen);
10953     }
10954     fclose(f);
10955     return TRUE;
10956 }
10957
10958 void
10959 ReloadCmailMsgEvent(unregister)
10960      int unregister;
10961 {
10962 #if !WIN32
10963     static char *inFilename = NULL;
10964     static char *outFilename;
10965     int i;
10966     struct stat inbuf, outbuf;
10967     int status;
10968     
10969     /* Any registered moves are unregistered if unregister is set, */
10970     /* i.e. invoked by the signal handler */
10971     if (unregister) {
10972         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10973             cmailMoveRegistered[i] = FALSE;
10974             if (cmailCommentList[i] != NULL) {
10975                 free(cmailCommentList[i]);
10976                 cmailCommentList[i] = NULL;
10977             }
10978         }
10979         nCmailMovesRegistered = 0;
10980     }
10981
10982     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10983         cmailResult[i] = CMAIL_NOT_RESULT;
10984     }
10985     nCmailResults = 0;
10986
10987     if (inFilename == NULL) {
10988         /* Because the filenames are static they only get malloced once  */
10989         /* and they never get freed                                      */
10990         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10991         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10992
10993         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10994         sprintf(outFilename, "%s.out", appData.cmailGameName);
10995     }
10996     
10997     status = stat(outFilename, &outbuf);
10998     if (status < 0) {
10999         cmailMailedMove = FALSE;
11000     } else {
11001         status = stat(inFilename, &inbuf);
11002         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11003     }
11004     
11005     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11006        counts the games, notes how each one terminated, etc.
11007        
11008        It would be nice to remove this kludge and instead gather all
11009        the information while building the game list.  (And to keep it
11010        in the game list nodes instead of having a bunch of fixed-size
11011        parallel arrays.)  Note this will require getting each game's
11012        termination from the PGN tags, as the game list builder does
11013        not process the game moves.  --mann
11014        */
11015     cmailMsgLoaded = TRUE;
11016     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11017     
11018     /* Load first game in the file or popup game menu */
11019     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11020
11021 #endif /* !WIN32 */
11022     return;
11023 }
11024
11025 int
11026 RegisterMove()
11027 {
11028     FILE *f;
11029     char string[MSG_SIZ];
11030
11031     if (   cmailMailedMove
11032         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11033         return TRUE;            /* Allow free viewing  */
11034     }
11035
11036     /* Unregister move to ensure that we don't leave RegisterMove        */
11037     /* with the move registered when the conditions for registering no   */
11038     /* longer hold                                                       */
11039     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11040         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11041         nCmailMovesRegistered --;
11042
11043         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11044           {
11045               free(cmailCommentList[lastLoadGameNumber - 1]);
11046               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11047           }
11048     }
11049
11050     if (cmailOldMove == -1) {
11051         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11052         return FALSE;
11053     }
11054
11055     if (currentMove > cmailOldMove + 1) {
11056         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11057         return FALSE;
11058     }
11059
11060     if (currentMove < cmailOldMove) {
11061         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11062         return FALSE;
11063     }
11064
11065     if (forwardMostMove > currentMove) {
11066         /* Silently truncate extra moves */
11067         TruncateGame();
11068     }
11069
11070     if (   (currentMove == cmailOldMove + 1)
11071         || (   (currentMove == cmailOldMove)
11072             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11073                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11074         if (gameInfo.result != GameUnfinished) {
11075             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11076         }
11077
11078         if (commentList[currentMove] != NULL) {
11079             cmailCommentList[lastLoadGameNumber - 1]
11080               = StrSave(commentList[currentMove]);
11081         }
11082         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11083
11084         if (appData.debugMode)
11085           fprintf(debugFP, "Saving %s for game %d\n",
11086                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11087
11088         sprintf(string,
11089                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11090         
11091         f = fopen(string, "w");
11092         if (appData.oldSaveStyle) {
11093             SaveGameOldStyle(f); /* also closes the file */
11094             
11095             sprintf(string, "%s.pos.out", appData.cmailGameName);
11096             f = fopen(string, "w");
11097             SavePosition(f, 0, NULL); /* also closes the file */
11098         } else {
11099             fprintf(f, "{--------------\n");
11100             PrintPosition(f, currentMove);
11101             fprintf(f, "--------------}\n\n");
11102             
11103             SaveGame(f, 0, NULL); /* also closes the file*/
11104         }
11105         
11106         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11107         nCmailMovesRegistered ++;
11108     } else if (nCmailGames == 1) {
11109         DisplayError(_("You have not made a move yet"), 0);
11110         return FALSE;
11111     }
11112
11113     return TRUE;
11114 }
11115
11116 void
11117 MailMoveEvent()
11118 {
11119 #if !WIN32
11120     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11121     FILE *commandOutput;
11122     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11123     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11124     int nBuffers;
11125     int i;
11126     int archived;
11127     char *arcDir;
11128
11129     if (! cmailMsgLoaded) {
11130         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11131         return;
11132     }
11133
11134     if (nCmailGames == nCmailResults) {
11135         DisplayError(_("No unfinished games"), 0);
11136         return;
11137     }
11138
11139 #if CMAIL_PROHIBIT_REMAIL
11140     if (cmailMailedMove) {
11141         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);
11142         DisplayError(msg, 0);
11143         return;
11144     }
11145 #endif
11146
11147     if (! (cmailMailedMove || RegisterMove())) return;
11148     
11149     if (   cmailMailedMove
11150         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11151         sprintf(string, partCommandString,
11152                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11153         commandOutput = popen(string, "r");
11154
11155         if (commandOutput == NULL) {
11156             DisplayError(_("Failed to invoke cmail"), 0);
11157         } else {
11158             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11159                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11160             }
11161             if (nBuffers > 1) {
11162                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11163                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11164                 nBytes = MSG_SIZ - 1;
11165             } else {
11166                 (void) memcpy(msg, buffer, nBytes);
11167             }
11168             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11169
11170             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11171                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11172
11173                 archived = TRUE;
11174                 for (i = 0; i < nCmailGames; i ++) {
11175                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11176                         archived = FALSE;
11177                     }
11178                 }
11179                 if (   archived
11180                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11181                         != NULL)) {
11182                     sprintf(buffer, "%s/%s.%s.archive",
11183                             arcDir,
11184                             appData.cmailGameName,
11185                             gameInfo.date);
11186                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11187                     cmailMsgLoaded = FALSE;
11188                 }
11189             }
11190
11191             DisplayInformation(msg);
11192             pclose(commandOutput);
11193         }
11194     } else {
11195         if ((*cmailMsg) != '\0') {
11196             DisplayInformation(cmailMsg);
11197         }
11198     }
11199
11200     return;
11201 #endif /* !WIN32 */
11202 }
11203
11204 char *
11205 CmailMsg()
11206 {
11207 #if WIN32
11208     return NULL;
11209 #else
11210     int  prependComma = 0;
11211     char number[5];
11212     char string[MSG_SIZ];       /* Space for game-list */
11213     int  i;
11214     
11215     if (!cmailMsgLoaded) return "";
11216
11217     if (cmailMailedMove) {
11218         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11219     } else {
11220         /* Create a list of games left */
11221         sprintf(string, "[");
11222         for (i = 0; i < nCmailGames; i ++) {
11223             if (! (   cmailMoveRegistered[i]
11224                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11225                 if (prependComma) {
11226                     sprintf(number, ",%d", i + 1);
11227                 } else {
11228                     sprintf(number, "%d", i + 1);
11229                     prependComma = 1;
11230                 }
11231                 
11232                 strcat(string, number);
11233             }
11234         }
11235         strcat(string, "]");
11236
11237         if (nCmailMovesRegistered + nCmailResults == 0) {
11238             switch (nCmailGames) {
11239               case 1:
11240                 sprintf(cmailMsg,
11241                         _("Still need to make move for game\n"));
11242                 break;
11243                 
11244               case 2:
11245                 sprintf(cmailMsg,
11246                         _("Still need to make moves for both games\n"));
11247                 break;
11248                 
11249               default:
11250                 sprintf(cmailMsg,
11251                         _("Still need to make moves for all %d games\n"),
11252                         nCmailGames);
11253                 break;
11254             }
11255         } else {
11256             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11257               case 1:
11258                 sprintf(cmailMsg,
11259                         _("Still need to make a move for game %s\n"),
11260                         string);
11261                 break;
11262                 
11263               case 0:
11264                 if (nCmailResults == nCmailGames) {
11265                     sprintf(cmailMsg, _("No unfinished games\n"));
11266                 } else {
11267                     sprintf(cmailMsg, _("Ready to send mail\n"));
11268                 }
11269                 break;
11270                 
11271               default:
11272                 sprintf(cmailMsg,
11273                         _("Still need to make moves for games %s\n"),
11274                         string);
11275             }
11276         }
11277     }
11278     return cmailMsg;
11279 #endif /* WIN32 */
11280 }
11281
11282 void
11283 ResetGameEvent()
11284 {
11285     if (gameMode == Training)
11286       SetTrainingModeOff();
11287
11288     Reset(TRUE, TRUE);
11289     cmailMsgLoaded = FALSE;
11290     if (appData.icsActive) {
11291       SendToICS(ics_prefix);
11292       SendToICS("refresh\n");
11293     }
11294 }
11295
11296 void
11297 ExitEvent(status)
11298      int status;
11299 {
11300     exiting++;
11301     if (exiting > 2) {
11302       /* Give up on clean exit */
11303       exit(status);
11304     }
11305     if (exiting > 1) {
11306       /* Keep trying for clean exit */
11307       return;
11308     }
11309
11310     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11311
11312     if (telnetISR != NULL) {
11313       RemoveInputSource(telnetISR);
11314     }
11315     if (icsPR != NoProc) {
11316       DestroyChildProcess(icsPR, TRUE);
11317     }
11318
11319     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11320     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11321
11322     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11323     /* make sure this other one finishes before killing it!                  */
11324     if(endingGame) { int count = 0;
11325         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11326         while(endingGame && count++ < 10) DoSleep(1);
11327         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11328     }
11329
11330     /* Kill off chess programs */
11331     if (first.pr != NoProc) {
11332         ExitAnalyzeMode();
11333         
11334         DoSleep( appData.delayBeforeQuit );
11335         SendToProgram("quit\n", &first);
11336         DoSleep( appData.delayAfterQuit );
11337         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11338     }
11339     if (second.pr != NoProc) {
11340         DoSleep( appData.delayBeforeQuit );
11341         SendToProgram("quit\n", &second);
11342         DoSleep( appData.delayAfterQuit );
11343         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11344     }
11345     if (first.isr != NULL) {
11346         RemoveInputSource(first.isr);
11347     }
11348     if (second.isr != NULL) {
11349         RemoveInputSource(second.isr);
11350     }
11351
11352     ShutDownFrontEnd();
11353     exit(status);
11354 }
11355
11356 void
11357 PauseEvent()
11358 {
11359     if (appData.debugMode)
11360         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11361     if (pausing) {
11362         pausing = FALSE;
11363         ModeHighlight();
11364         if (gameMode == MachinePlaysWhite ||
11365             gameMode == MachinePlaysBlack) {
11366             StartClocks();
11367         } else {
11368             DisplayBothClocks();
11369         }
11370         if (gameMode == PlayFromGameFile) {
11371             if (appData.timeDelay >= 0) 
11372                 AutoPlayGameLoop();
11373         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11374             Reset(FALSE, TRUE);
11375             SendToICS(ics_prefix);
11376             SendToICS("refresh\n");
11377         } else if (currentMove < forwardMostMove) {
11378             ForwardInner(forwardMostMove);
11379         }
11380         pauseExamInvalid = FALSE;
11381     } else {
11382         switch (gameMode) {
11383           default:
11384             return;
11385           case IcsExamining:
11386             pauseExamForwardMostMove = forwardMostMove;
11387             pauseExamInvalid = FALSE;
11388             /* fall through */
11389           case IcsObserving:
11390           case IcsPlayingWhite:
11391           case IcsPlayingBlack:
11392             pausing = TRUE;
11393             ModeHighlight();
11394             return;
11395           case PlayFromGameFile:
11396             (void) StopLoadGameTimer();
11397             pausing = TRUE;
11398             ModeHighlight();
11399             break;
11400           case BeginningOfGame:
11401             if (appData.icsActive) return;
11402             /* else fall through */
11403           case MachinePlaysWhite:
11404           case MachinePlaysBlack:
11405           case TwoMachinesPlay:
11406             if (forwardMostMove == 0)
11407               return;           /* don't pause if no one has moved */
11408             if ((gameMode == MachinePlaysWhite &&
11409                  !WhiteOnMove(forwardMostMove)) ||
11410                 (gameMode == MachinePlaysBlack &&
11411                  WhiteOnMove(forwardMostMove))) {
11412                 StopClocks();
11413             }
11414             pausing = TRUE;
11415             ModeHighlight();
11416             break;
11417         }
11418     }
11419 }
11420
11421 void
11422 EditCommentEvent()
11423 {
11424     char title[MSG_SIZ];
11425
11426     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11427         strcpy(title, _("Edit comment"));
11428     } else {
11429         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11430                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11431                 parseList[currentMove - 1]);
11432     }
11433
11434     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11435 }
11436
11437
11438 void
11439 EditTagsEvent()
11440 {
11441     char *tags = PGNTags(&gameInfo);
11442     EditTagsPopUp(tags);
11443     free(tags);
11444 }
11445
11446 void
11447 AnalyzeModeEvent()
11448 {
11449     if (appData.noChessProgram || gameMode == AnalyzeMode)
11450       return;
11451
11452     if (gameMode != AnalyzeFile) {
11453         if (!appData.icsEngineAnalyze) {
11454                EditGameEvent();
11455                if (gameMode != EditGame) return;
11456         }
11457         ResurrectChessProgram();
11458         SendToProgram("analyze\n", &first);
11459         first.analyzing = TRUE;
11460         /*first.maybeThinking = TRUE;*/
11461         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11462         EngineOutputPopUp();
11463     }
11464     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11465     pausing = FALSE;
11466     ModeHighlight();
11467     SetGameInfo();
11468
11469     StartAnalysisClock();
11470     GetTimeMark(&lastNodeCountTime);
11471     lastNodeCount = 0;
11472 }
11473
11474 void
11475 AnalyzeFileEvent()
11476 {
11477     if (appData.noChessProgram || gameMode == AnalyzeFile)
11478       return;
11479
11480     if (gameMode != AnalyzeMode) {
11481         EditGameEvent();
11482         if (gameMode != EditGame) return;
11483         ResurrectChessProgram();
11484         SendToProgram("analyze\n", &first);
11485         first.analyzing = TRUE;
11486         /*first.maybeThinking = TRUE;*/
11487         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11488         EngineOutputPopUp();
11489     }
11490     gameMode = AnalyzeFile;
11491     pausing = FALSE;
11492     ModeHighlight();
11493     SetGameInfo();
11494
11495     StartAnalysisClock();
11496     GetTimeMark(&lastNodeCountTime);
11497     lastNodeCount = 0;
11498 }
11499
11500 void
11501 MachineWhiteEvent()
11502 {
11503     char buf[MSG_SIZ];
11504     char *bookHit = NULL;
11505
11506     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11507       return;
11508
11509
11510     if (gameMode == PlayFromGameFile || 
11511         gameMode == TwoMachinesPlay  || 
11512         gameMode == Training         || 
11513         gameMode == AnalyzeMode      || 
11514         gameMode == EndOfGame)
11515         EditGameEvent();
11516
11517     if (gameMode == EditPosition) 
11518         EditPositionDone(TRUE);
11519
11520     if (!WhiteOnMove(currentMove)) {
11521         DisplayError(_("It is not White's turn"), 0);
11522         return;
11523     }
11524   
11525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11526       ExitAnalyzeMode();
11527
11528     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11529         gameMode == AnalyzeFile)
11530         TruncateGame();
11531
11532     ResurrectChessProgram();    /* in case it isn't running */
11533     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11534         gameMode = MachinePlaysWhite;
11535         ResetClocks();
11536     } else
11537     gameMode = MachinePlaysWhite;
11538     pausing = FALSE;
11539     ModeHighlight();
11540     SetGameInfo();
11541     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11542     DisplayTitle(buf);
11543     if (first.sendName) {
11544       sprintf(buf, "name %s\n", gameInfo.black);
11545       SendToProgram(buf, &first);
11546     }
11547     if (first.sendTime) {
11548       if (first.useColors) {
11549         SendToProgram("black\n", &first); /*gnu kludge*/
11550       }
11551       SendTimeRemaining(&first, TRUE);
11552     }
11553     if (first.useColors) {
11554       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11555     }
11556     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11557     SetMachineThinkingEnables();
11558     first.maybeThinking = TRUE;
11559     StartClocks();
11560     firstMove = FALSE;
11561
11562     if (appData.autoFlipView && !flipView) {
11563       flipView = !flipView;
11564       DrawPosition(FALSE, NULL);
11565       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11566     }
11567
11568     if(bookHit) { // [HGM] book: simulate book reply
11569         static char bookMove[MSG_SIZ]; // a bit generous?
11570
11571         programStats.nodes = programStats.depth = programStats.time = 
11572         programStats.score = programStats.got_only_move = 0;
11573         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11574
11575         strcpy(bookMove, "move ");
11576         strcat(bookMove, bookHit);
11577         HandleMachineMove(bookMove, &first);
11578     }
11579 }
11580
11581 void
11582 MachineBlackEvent()
11583 {
11584     char buf[MSG_SIZ];
11585    char *bookHit = NULL;
11586
11587     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11588         return;
11589
11590
11591     if (gameMode == PlayFromGameFile || 
11592         gameMode == TwoMachinesPlay  || 
11593         gameMode == Training         || 
11594         gameMode == AnalyzeMode      || 
11595         gameMode == EndOfGame)
11596         EditGameEvent();
11597
11598     if (gameMode == EditPosition) 
11599         EditPositionDone(TRUE);
11600
11601     if (WhiteOnMove(currentMove)) {
11602         DisplayError(_("It is not Black's turn"), 0);
11603         return;
11604     }
11605     
11606     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11607       ExitAnalyzeMode();
11608
11609     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11610         gameMode == AnalyzeFile)
11611         TruncateGame();
11612
11613     ResurrectChessProgram();    /* in case it isn't running */
11614     gameMode = MachinePlaysBlack;
11615     pausing = FALSE;
11616     ModeHighlight();
11617     SetGameInfo();
11618     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11619     DisplayTitle(buf);
11620     if (first.sendName) {
11621       sprintf(buf, "name %s\n", gameInfo.white);
11622       SendToProgram(buf, &first);
11623     }
11624     if (first.sendTime) {
11625       if (first.useColors) {
11626         SendToProgram("white\n", &first); /*gnu kludge*/
11627       }
11628       SendTimeRemaining(&first, FALSE);
11629     }
11630     if (first.useColors) {
11631       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11632     }
11633     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11634     SetMachineThinkingEnables();
11635     first.maybeThinking = TRUE;
11636     StartClocks();
11637
11638     if (appData.autoFlipView && flipView) {
11639       flipView = !flipView;
11640       DrawPosition(FALSE, NULL);
11641       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11642     }
11643     if(bookHit) { // [HGM] book: simulate book reply
11644         static char bookMove[MSG_SIZ]; // a bit generous?
11645
11646         programStats.nodes = programStats.depth = programStats.time = 
11647         programStats.score = programStats.got_only_move = 0;
11648         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11649
11650         strcpy(bookMove, "move ");
11651         strcat(bookMove, bookHit);
11652         HandleMachineMove(bookMove, &first);
11653     }
11654 }
11655
11656
11657 void
11658 DisplayTwoMachinesTitle()
11659 {
11660     char buf[MSG_SIZ];
11661     if (appData.matchGames > 0) {
11662         if (first.twoMachinesColor[0] == 'w') {
11663             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11664                     gameInfo.white, gameInfo.black,
11665                     first.matchWins, second.matchWins,
11666                     matchGame - 1 - (first.matchWins + second.matchWins));
11667         } else {
11668             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11669                     gameInfo.white, gameInfo.black,
11670                     second.matchWins, first.matchWins,
11671                     matchGame - 1 - (first.matchWins + second.matchWins));
11672         }
11673     } else {
11674         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11675     }
11676     DisplayTitle(buf);
11677 }
11678
11679 void
11680 TwoMachinesEvent P((void))
11681 {
11682     int i;
11683     char buf[MSG_SIZ];
11684     ChessProgramState *onmove;
11685     char *bookHit = NULL;
11686     
11687     if (appData.noChessProgram) return;
11688
11689     switch (gameMode) {
11690       case TwoMachinesPlay:
11691         return;
11692       case MachinePlaysWhite:
11693       case MachinePlaysBlack:
11694         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11695             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11696             return;
11697         }
11698         /* fall through */
11699       case BeginningOfGame:
11700       case PlayFromGameFile:
11701       case EndOfGame:
11702         EditGameEvent();
11703         if (gameMode != EditGame) return;
11704         break;
11705       case EditPosition:
11706         EditPositionDone(TRUE);
11707         break;
11708       case AnalyzeMode:
11709       case AnalyzeFile:
11710         ExitAnalyzeMode();
11711         break;
11712       case EditGame:
11713       default:
11714         break;
11715     }
11716
11717 //    forwardMostMove = currentMove;
11718     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11719     ResurrectChessProgram();    /* in case first program isn't running */
11720
11721     if (second.pr == NULL) {
11722         StartChessProgram(&second);
11723         if (second.protocolVersion == 1) {
11724           TwoMachinesEventIfReady();
11725         } else {
11726           /* kludge: allow timeout for initial "feature" command */
11727           FreezeUI();
11728           DisplayMessage("", _("Starting second chess program"));
11729           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11730         }
11731         return;
11732     }
11733     DisplayMessage("", "");
11734     InitChessProgram(&second, FALSE);
11735     SendToProgram("force\n", &second);
11736     if (startedFromSetupPosition) {
11737         SendBoard(&second, backwardMostMove);
11738     if (appData.debugMode) {
11739         fprintf(debugFP, "Two Machines\n");
11740     }
11741     }
11742     for (i = backwardMostMove; i < forwardMostMove; i++) {
11743         SendMoveToProgram(i, &second);
11744     }
11745
11746     gameMode = TwoMachinesPlay;
11747     pausing = FALSE;
11748     ModeHighlight();
11749     SetGameInfo();
11750     DisplayTwoMachinesTitle();
11751     firstMove = TRUE;
11752     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11753         onmove = &first;
11754     } else {
11755         onmove = &second;
11756     }
11757
11758     SendToProgram(first.computerString, &first);
11759     if (first.sendName) {
11760       sprintf(buf, "name %s\n", second.tidy);
11761       SendToProgram(buf, &first);
11762     }
11763     SendToProgram(second.computerString, &second);
11764     if (second.sendName) {
11765       sprintf(buf, "name %s\n", first.tidy);
11766       SendToProgram(buf, &second);
11767     }
11768
11769     ResetClocks();
11770     if (!first.sendTime || !second.sendTime) {
11771         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11772         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11773     }
11774     if (onmove->sendTime) {
11775       if (onmove->useColors) {
11776         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11777       }
11778       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11779     }
11780     if (onmove->useColors) {
11781       SendToProgram(onmove->twoMachinesColor, onmove);
11782     }
11783     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11784 //    SendToProgram("go\n", onmove);
11785     onmove->maybeThinking = TRUE;
11786     SetMachineThinkingEnables();
11787
11788     StartClocks();
11789
11790     if(bookHit) { // [HGM] book: simulate book reply
11791         static char bookMove[MSG_SIZ]; // a bit generous?
11792
11793         programStats.nodes = programStats.depth = programStats.time = 
11794         programStats.score = programStats.got_only_move = 0;
11795         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11796
11797         strcpy(bookMove, "move ");
11798         strcat(bookMove, bookHit);
11799         savedMessage = bookMove; // args for deferred call
11800         savedState = onmove;
11801         ScheduleDelayedEvent(DeferredBookMove, 1);
11802     }
11803 }
11804
11805 void
11806 TrainingEvent()
11807 {
11808     if (gameMode == Training) {
11809       SetTrainingModeOff();
11810       gameMode = PlayFromGameFile;
11811       DisplayMessage("", _("Training mode off"));
11812     } else {
11813       gameMode = Training;
11814       animateTraining = appData.animate;
11815
11816       /* make sure we are not already at the end of the game */
11817       if (currentMove < forwardMostMove) {
11818         SetTrainingModeOn();
11819         DisplayMessage("", _("Training mode on"));
11820       } else {
11821         gameMode = PlayFromGameFile;
11822         DisplayError(_("Already at end of game"), 0);
11823       }
11824     }
11825     ModeHighlight();
11826 }
11827
11828 void
11829 IcsClientEvent()
11830 {
11831     if (!appData.icsActive) return;
11832     switch (gameMode) {
11833       case IcsPlayingWhite:
11834       case IcsPlayingBlack:
11835       case IcsObserving:
11836       case IcsIdle:
11837       case BeginningOfGame:
11838       case IcsExamining:
11839         return;
11840
11841       case EditGame:
11842         break;
11843
11844       case EditPosition:
11845         EditPositionDone(TRUE);
11846         break;
11847
11848       case AnalyzeMode:
11849       case AnalyzeFile:
11850         ExitAnalyzeMode();
11851         break;
11852         
11853       default:
11854         EditGameEvent();
11855         break;
11856     }
11857
11858     gameMode = IcsIdle;
11859     ModeHighlight();
11860     return;
11861 }
11862
11863
11864 void
11865 EditGameEvent()
11866 {
11867     int i;
11868
11869     switch (gameMode) {
11870       case Training:
11871         SetTrainingModeOff();
11872         break;
11873       case MachinePlaysWhite:
11874       case MachinePlaysBlack:
11875       case BeginningOfGame:
11876         SendToProgram("force\n", &first);
11877         SetUserThinkingEnables();
11878         break;
11879       case PlayFromGameFile:
11880         (void) StopLoadGameTimer();
11881         if (gameFileFP != NULL) {
11882             gameFileFP = NULL;
11883         }
11884         break;
11885       case EditPosition:
11886         EditPositionDone(TRUE);
11887         break;
11888       case AnalyzeMode:
11889       case AnalyzeFile:
11890         ExitAnalyzeMode();
11891         SendToProgram("force\n", &first);
11892         break;
11893       case TwoMachinesPlay:
11894         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11895         ResurrectChessProgram();
11896         SetUserThinkingEnables();
11897         break;
11898       case EndOfGame:
11899         ResurrectChessProgram();
11900         break;
11901       case IcsPlayingBlack:
11902       case IcsPlayingWhite:
11903         DisplayError(_("Warning: You are still playing a game"), 0);
11904         break;
11905       case IcsObserving:
11906         DisplayError(_("Warning: You are still observing a game"), 0);
11907         break;
11908       case IcsExamining:
11909         DisplayError(_("Warning: You are still examining a game"), 0);
11910         break;
11911       case IcsIdle:
11912         break;
11913       case EditGame:
11914       default:
11915         return;
11916     }
11917     
11918     pausing = FALSE;
11919     StopClocks();
11920     first.offeredDraw = second.offeredDraw = 0;
11921
11922     if (gameMode == PlayFromGameFile) {
11923         whiteTimeRemaining = timeRemaining[0][currentMove];
11924         blackTimeRemaining = timeRemaining[1][currentMove];
11925         DisplayTitle("");
11926     }
11927
11928     if (gameMode == MachinePlaysWhite ||
11929         gameMode == MachinePlaysBlack ||
11930         gameMode == TwoMachinesPlay ||
11931         gameMode == EndOfGame) {
11932         i = forwardMostMove;
11933         while (i > currentMove) {
11934             SendToProgram("undo\n", &first);
11935             i--;
11936         }
11937         whiteTimeRemaining = timeRemaining[0][currentMove];
11938         blackTimeRemaining = timeRemaining[1][currentMove];
11939         DisplayBothClocks();
11940         if (whiteFlag || blackFlag) {
11941             whiteFlag = blackFlag = 0;
11942         }
11943         DisplayTitle("");
11944     }           
11945     
11946     gameMode = EditGame;
11947     ModeHighlight();
11948     SetGameInfo();
11949 }
11950
11951
11952 void
11953 EditPositionEvent()
11954 {
11955     if (gameMode == EditPosition) {
11956         EditGameEvent();
11957         return;
11958     }
11959     
11960     EditGameEvent();
11961     if (gameMode != EditGame) return;
11962     
11963     gameMode = EditPosition;
11964     ModeHighlight();
11965     SetGameInfo();
11966     if (currentMove > 0)
11967       CopyBoard(boards[0], boards[currentMove]);
11968     
11969     blackPlaysFirst = !WhiteOnMove(currentMove);
11970     ResetClocks();
11971     currentMove = forwardMostMove = backwardMostMove = 0;
11972     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11973     DisplayMove(-1);
11974 }
11975
11976 void
11977 ExitAnalyzeMode()
11978 {
11979     /* [DM] icsEngineAnalyze - possible call from other functions */
11980     if (appData.icsEngineAnalyze) {
11981         appData.icsEngineAnalyze = FALSE;
11982
11983         DisplayMessage("",_("Close ICS engine analyze..."));
11984     }
11985     if (first.analysisSupport && first.analyzing) {
11986       SendToProgram("exit\n", &first);
11987       first.analyzing = FALSE;
11988     }
11989     thinkOutput[0] = NULLCHAR;
11990 }
11991
11992 void
11993 EditPositionDone(Boolean fakeRights)
11994 {
11995     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11996
11997     startedFromSetupPosition = TRUE;
11998     InitChessProgram(&first, FALSE);
11999     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12000       boards[0][EP_STATUS] = EP_NONE;
12001       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12002     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12003         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12004         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12005       } else boards[0][CASTLING][2] = NoRights;
12006     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12007         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12008         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12009       } else boards[0][CASTLING][5] = NoRights;
12010     }
12011     SendToProgram("force\n", &first);
12012     if (blackPlaysFirst) {
12013         strcpy(moveList[0], "");
12014         strcpy(parseList[0], "");
12015         currentMove = forwardMostMove = backwardMostMove = 1;
12016         CopyBoard(boards[1], boards[0]);
12017     } else {
12018         currentMove = forwardMostMove = backwardMostMove = 0;
12019     }
12020     SendBoard(&first, forwardMostMove);
12021     if (appData.debugMode) {
12022         fprintf(debugFP, "EditPosDone\n");
12023     }
12024     DisplayTitle("");
12025     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12026     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12027     gameMode = EditGame;
12028     ModeHighlight();
12029     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12030     ClearHighlights(); /* [AS] */
12031 }
12032
12033 /* Pause for `ms' milliseconds */
12034 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12035 void
12036 TimeDelay(ms)
12037      long ms;
12038 {
12039     TimeMark m1, m2;
12040
12041     GetTimeMark(&m1);
12042     do {
12043         GetTimeMark(&m2);
12044     } while (SubtractTimeMarks(&m2, &m1) < ms);
12045 }
12046
12047 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12048 void
12049 SendMultiLineToICS(buf)
12050      char *buf;
12051 {
12052     char temp[MSG_SIZ+1], *p;
12053     int len;
12054
12055     len = strlen(buf);
12056     if (len > MSG_SIZ)
12057       len = MSG_SIZ;
12058   
12059     strncpy(temp, buf, len);
12060     temp[len] = 0;
12061
12062     p = temp;
12063     while (*p) {
12064         if (*p == '\n' || *p == '\r')
12065           *p = ' ';
12066         ++p;
12067     }
12068
12069     strcat(temp, "\n");
12070     SendToICS(temp);
12071     SendToPlayer(temp, strlen(temp));
12072 }
12073
12074 void
12075 SetWhiteToPlayEvent()
12076 {
12077     if (gameMode == EditPosition) {
12078         blackPlaysFirst = FALSE;
12079         DisplayBothClocks();    /* works because currentMove is 0 */
12080     } else if (gameMode == IcsExamining) {
12081         SendToICS(ics_prefix);
12082         SendToICS("tomove white\n");
12083     }
12084 }
12085
12086 void
12087 SetBlackToPlayEvent()
12088 {
12089     if (gameMode == EditPosition) {
12090         blackPlaysFirst = TRUE;
12091         currentMove = 1;        /* kludge */
12092         DisplayBothClocks();
12093         currentMove = 0;
12094     } else if (gameMode == IcsExamining) {
12095         SendToICS(ics_prefix);
12096         SendToICS("tomove black\n");
12097     }
12098 }
12099
12100 void
12101 EditPositionMenuEvent(selection, x, y)
12102      ChessSquare selection;
12103      int x, y;
12104 {
12105     char buf[MSG_SIZ];
12106     ChessSquare piece = boards[0][y][x];
12107
12108     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12109
12110     switch (selection) {
12111       case ClearBoard:
12112         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12113             SendToICS(ics_prefix);
12114             SendToICS("bsetup clear\n");
12115         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12116             SendToICS(ics_prefix);
12117             SendToICS("clearboard\n");
12118         } else {
12119             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12120                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12121                 for (y = 0; y < BOARD_HEIGHT; y++) {
12122                     if (gameMode == IcsExamining) {
12123                         if (boards[currentMove][y][x] != EmptySquare) {
12124                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12125                                     AAA + x, ONE + y);
12126                             SendToICS(buf);
12127                         }
12128                     } else {
12129                         boards[0][y][x] = p;
12130                     }
12131                 }
12132             }
12133         }
12134         if (gameMode == EditPosition) {
12135             DrawPosition(FALSE, boards[0]);
12136         }
12137         break;
12138
12139       case WhitePlay:
12140         SetWhiteToPlayEvent();
12141         break;
12142
12143       case BlackPlay:
12144         SetBlackToPlayEvent();
12145         break;
12146
12147       case EmptySquare:
12148         if (gameMode == IcsExamining) {
12149             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12150             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12151             SendToICS(buf);
12152         } else {
12153             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12154                 if(x == BOARD_LEFT-2) {
12155                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12156                     boards[0][y][1] = 0;
12157                 } else
12158                 if(x == BOARD_RGHT+1) {
12159                     if(y >= gameInfo.holdingsSize) break;
12160                     boards[0][y][BOARD_WIDTH-2] = 0;
12161                 } else break;
12162             }
12163             boards[0][y][x] = EmptySquare;
12164             DrawPosition(FALSE, boards[0]);
12165         }
12166         break;
12167
12168       case PromotePiece:
12169         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12170            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12171             selection = (ChessSquare) (PROMOTED piece);
12172         } else if(piece == EmptySquare) selection = WhiteSilver;
12173         else selection = (ChessSquare)((int)piece - 1);
12174         goto defaultlabel;
12175
12176       case DemotePiece:
12177         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12178            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12179             selection = (ChessSquare) (DEMOTED piece);
12180         } else if(piece == EmptySquare) selection = BlackSilver;
12181         else selection = (ChessSquare)((int)piece + 1);       
12182         goto defaultlabel;
12183
12184       case WhiteQueen:
12185       case BlackQueen:
12186         if(gameInfo.variant == VariantShatranj ||
12187            gameInfo.variant == VariantXiangqi  ||
12188            gameInfo.variant == VariantCourier  ||
12189            gameInfo.variant == VariantMakruk     )
12190             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12191         goto defaultlabel;
12192
12193       case WhiteKing:
12194       case BlackKing:
12195         if(gameInfo.variant == VariantXiangqi)
12196             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12197         if(gameInfo.variant == VariantKnightmate)
12198             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12199       default:
12200         defaultlabel:
12201         if (gameMode == IcsExamining) {
12202             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12203             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12204                     PieceToChar(selection), AAA + x, ONE + y);
12205             SendToICS(buf);
12206         } else {
12207             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12208                 int n;
12209                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12210                     n = PieceToNumber(selection - BlackPawn);
12211                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12212                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12213                     boards[0][BOARD_HEIGHT-1-n][1]++;
12214                 } else
12215                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12216                     n = PieceToNumber(selection);
12217                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12218                     boards[0][n][BOARD_WIDTH-1] = selection;
12219                     boards[0][n][BOARD_WIDTH-2]++;
12220                 }
12221             } else
12222             boards[0][y][x] = selection;
12223             DrawPosition(TRUE, boards[0]);
12224         }
12225         break;
12226     }
12227 }
12228
12229
12230 void
12231 DropMenuEvent(selection, x, y)
12232      ChessSquare selection;
12233      int x, y;
12234 {
12235     ChessMove moveType;
12236
12237     switch (gameMode) {
12238       case IcsPlayingWhite:
12239       case MachinePlaysBlack:
12240         if (!WhiteOnMove(currentMove)) {
12241             DisplayMoveError(_("It is Black's turn"));
12242             return;
12243         }
12244         moveType = WhiteDrop;
12245         break;
12246       case IcsPlayingBlack:
12247       case MachinePlaysWhite:
12248         if (WhiteOnMove(currentMove)) {
12249             DisplayMoveError(_("It is White's turn"));
12250             return;
12251         }
12252         moveType = BlackDrop;
12253         break;
12254       case EditGame:
12255         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12256         break;
12257       default:
12258         return;
12259     }
12260
12261     if (moveType == BlackDrop && selection < BlackPawn) {
12262       selection = (ChessSquare) ((int) selection
12263                                  + (int) BlackPawn - (int) WhitePawn);
12264     }
12265     if (boards[currentMove][y][x] != EmptySquare) {
12266         DisplayMoveError(_("That square is occupied"));
12267         return;
12268     }
12269
12270     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12271 }
12272
12273 void
12274 AcceptEvent()
12275 {
12276     /* Accept a pending offer of any kind from opponent */
12277     
12278     if (appData.icsActive) {
12279         SendToICS(ics_prefix);
12280         SendToICS("accept\n");
12281     } else if (cmailMsgLoaded) {
12282         if (currentMove == cmailOldMove &&
12283             commentList[cmailOldMove] != NULL &&
12284             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12285                    "Black offers a draw" : "White offers a draw")) {
12286             TruncateGame();
12287             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12289         } else {
12290             DisplayError(_("There is no pending offer on this move"), 0);
12291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12292         }
12293     } else {
12294         /* Not used for offers from chess program */
12295     }
12296 }
12297
12298 void
12299 DeclineEvent()
12300 {
12301     /* Decline a pending offer of any kind from opponent */
12302     
12303     if (appData.icsActive) {
12304         SendToICS(ics_prefix);
12305         SendToICS("decline\n");
12306     } else if (cmailMsgLoaded) {
12307         if (currentMove == cmailOldMove &&
12308             commentList[cmailOldMove] != NULL &&
12309             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12310                    "Black offers a draw" : "White offers a draw")) {
12311 #ifdef NOTDEF
12312             AppendComment(cmailOldMove, "Draw declined", TRUE);
12313             DisplayComment(cmailOldMove - 1, "Draw declined");
12314 #endif /*NOTDEF*/
12315         } else {
12316             DisplayError(_("There is no pending offer on this move"), 0);
12317         }
12318     } else {
12319         /* Not used for offers from chess program */
12320     }
12321 }
12322
12323 void
12324 RematchEvent()
12325 {
12326     /* Issue ICS rematch command */
12327     if (appData.icsActive) {
12328         SendToICS(ics_prefix);
12329         SendToICS("rematch\n");
12330     }
12331 }
12332
12333 void
12334 CallFlagEvent()
12335 {
12336     /* Call your opponent's flag (claim a win on time) */
12337     if (appData.icsActive) {
12338         SendToICS(ics_prefix);
12339         SendToICS("flag\n");
12340     } else {
12341         switch (gameMode) {
12342           default:
12343             return;
12344           case MachinePlaysWhite:
12345             if (whiteFlag) {
12346                 if (blackFlag)
12347                   GameEnds(GameIsDrawn, "Both players ran out of time",
12348                            GE_PLAYER);
12349                 else
12350                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12351             } else {
12352                 DisplayError(_("Your opponent is not out of time"), 0);
12353             }
12354             break;
12355           case MachinePlaysBlack:
12356             if (blackFlag) {
12357                 if (whiteFlag)
12358                   GameEnds(GameIsDrawn, "Both players ran out of time",
12359                            GE_PLAYER);
12360                 else
12361                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12362             } else {
12363                 DisplayError(_("Your opponent is not out of time"), 0);
12364             }
12365             break;
12366         }
12367     }
12368 }
12369
12370 void
12371 DrawEvent()
12372 {
12373     /* Offer draw or accept pending draw offer from opponent */
12374     
12375     if (appData.icsActive) {
12376         /* Note: tournament rules require draw offers to be
12377            made after you make your move but before you punch
12378            your clock.  Currently ICS doesn't let you do that;
12379            instead, you immediately punch your clock after making
12380            a move, but you can offer a draw at any time. */
12381         
12382         SendToICS(ics_prefix);
12383         SendToICS("draw\n");
12384         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12385     } else if (cmailMsgLoaded) {
12386         if (currentMove == cmailOldMove &&
12387             commentList[cmailOldMove] != NULL &&
12388             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12389                    "Black offers a draw" : "White offers a draw")) {
12390             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12391             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12392         } else if (currentMove == cmailOldMove + 1) {
12393             char *offer = WhiteOnMove(cmailOldMove) ?
12394               "White offers a draw" : "Black offers a draw";
12395             AppendComment(currentMove, offer, TRUE);
12396             DisplayComment(currentMove - 1, offer);
12397             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12398         } else {
12399             DisplayError(_("You must make your move before offering a draw"), 0);
12400             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12401         }
12402     } else if (first.offeredDraw) {
12403         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12404     } else {
12405         if (first.sendDrawOffers) {
12406             SendToProgram("draw\n", &first);
12407             userOfferedDraw = TRUE;
12408         }
12409     }
12410 }
12411
12412 void
12413 AdjournEvent()
12414 {
12415     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12416     
12417     if (appData.icsActive) {
12418         SendToICS(ics_prefix);
12419         SendToICS("adjourn\n");
12420     } else {
12421         /* Currently GNU Chess doesn't offer or accept Adjourns */
12422     }
12423 }
12424
12425
12426 void
12427 AbortEvent()
12428 {
12429     /* Offer Abort or accept pending Abort offer from opponent */
12430     
12431     if (appData.icsActive) {
12432         SendToICS(ics_prefix);
12433         SendToICS("abort\n");
12434     } else {
12435         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12436     }
12437 }
12438
12439 void
12440 ResignEvent()
12441 {
12442     /* Resign.  You can do this even if it's not your turn. */
12443     
12444     if (appData.icsActive) {
12445         SendToICS(ics_prefix);
12446         SendToICS("resign\n");
12447     } else {
12448         switch (gameMode) {
12449           case MachinePlaysWhite:
12450             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12451             break;
12452           case MachinePlaysBlack:
12453             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12454             break;
12455           case EditGame:
12456             if (cmailMsgLoaded) {
12457                 TruncateGame();
12458                 if (WhiteOnMove(cmailOldMove)) {
12459                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12460                 } else {
12461                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12462                 }
12463                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12464             }
12465             break;
12466           default:
12467             break;
12468         }
12469     }
12470 }
12471
12472
12473 void
12474 StopObservingEvent()
12475 {
12476     /* Stop observing current games */
12477     SendToICS(ics_prefix);
12478     SendToICS("unobserve\n");
12479 }
12480
12481 void
12482 StopExaminingEvent()
12483 {
12484     /* Stop observing current game */
12485     SendToICS(ics_prefix);
12486     SendToICS("unexamine\n");
12487 }
12488
12489 void
12490 ForwardInner(target)
12491      int target;
12492 {
12493     int limit;
12494
12495     if (appData.debugMode)
12496         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12497                 target, currentMove, forwardMostMove);
12498
12499     if (gameMode == EditPosition)
12500       return;
12501
12502     if (gameMode == PlayFromGameFile && !pausing)
12503       PauseEvent();
12504     
12505     if (gameMode == IcsExamining && pausing)
12506       limit = pauseExamForwardMostMove;
12507     else
12508       limit = forwardMostMove;
12509     
12510     if (target > limit) target = limit;
12511
12512     if (target > 0 && moveList[target - 1][0]) {
12513         int fromX, fromY, toX, toY;
12514         toX = moveList[target - 1][2] - AAA;
12515         toY = moveList[target - 1][3] - ONE;
12516         if (moveList[target - 1][1] == '@') {
12517             if (appData.highlightLastMove) {
12518                 SetHighlights(-1, -1, toX, toY);
12519             }
12520         } else {
12521             fromX = moveList[target - 1][0] - AAA;
12522             fromY = moveList[target - 1][1] - ONE;
12523             if (target == currentMove + 1) {
12524                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12525             }
12526             if (appData.highlightLastMove) {
12527                 SetHighlights(fromX, fromY, toX, toY);
12528             }
12529         }
12530     }
12531     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12532         gameMode == Training || gameMode == PlayFromGameFile || 
12533         gameMode == AnalyzeFile) {
12534         while (currentMove < target) {
12535             SendMoveToProgram(currentMove++, &first);
12536         }
12537     } else {
12538         currentMove = target;
12539     }
12540     
12541     if (gameMode == EditGame || gameMode == EndOfGame) {
12542         whiteTimeRemaining = timeRemaining[0][currentMove];
12543         blackTimeRemaining = timeRemaining[1][currentMove];
12544     }
12545     DisplayBothClocks();
12546     DisplayMove(currentMove - 1);
12547     DrawPosition(FALSE, boards[currentMove]);
12548     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12549     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12550         DisplayComment(currentMove - 1, commentList[currentMove]);
12551     }
12552 }
12553
12554
12555 void
12556 ForwardEvent()
12557 {
12558     if (gameMode == IcsExamining && !pausing) {
12559         SendToICS(ics_prefix);
12560         SendToICS("forward\n");
12561     } else {
12562         ForwardInner(currentMove + 1);
12563     }
12564 }
12565
12566 void
12567 ToEndEvent()
12568 {
12569     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12570         /* to optimze, we temporarily turn off analysis mode while we feed
12571          * the remaining moves to the engine. Otherwise we get analysis output
12572          * after each move.
12573          */ 
12574         if (first.analysisSupport) {
12575           SendToProgram("exit\nforce\n", &first);
12576           first.analyzing = FALSE;
12577         }
12578     }
12579         
12580     if (gameMode == IcsExamining && !pausing) {
12581         SendToICS(ics_prefix);
12582         SendToICS("forward 999999\n");
12583     } else {
12584         ForwardInner(forwardMostMove);
12585     }
12586
12587     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12588         /* we have fed all the moves, so reactivate analysis mode */
12589         SendToProgram("analyze\n", &first);
12590         first.analyzing = TRUE;
12591         /*first.maybeThinking = TRUE;*/
12592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12593     }
12594 }
12595
12596 void
12597 BackwardInner(target)
12598      int target;
12599 {
12600     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12601
12602     if (appData.debugMode)
12603         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12604                 target, currentMove, forwardMostMove);
12605
12606     if (gameMode == EditPosition) return;
12607     if (currentMove <= backwardMostMove) {
12608         ClearHighlights();
12609         DrawPosition(full_redraw, boards[currentMove]);
12610         return;
12611     }
12612     if (gameMode == PlayFromGameFile && !pausing)
12613       PauseEvent();
12614     
12615     if (moveList[target][0]) {
12616         int fromX, fromY, toX, toY;
12617         toX = moveList[target][2] - AAA;
12618         toY = moveList[target][3] - ONE;
12619         if (moveList[target][1] == '@') {
12620             if (appData.highlightLastMove) {
12621                 SetHighlights(-1, -1, toX, toY);
12622             }
12623         } else {
12624             fromX = moveList[target][0] - AAA;
12625             fromY = moveList[target][1] - ONE;
12626             if (target == currentMove - 1) {
12627                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12628             }
12629             if (appData.highlightLastMove) {
12630                 SetHighlights(fromX, fromY, toX, toY);
12631             }
12632         }
12633     }
12634     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12635         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12636         while (currentMove > target) {
12637             SendToProgram("undo\n", &first);
12638             currentMove--;
12639         }
12640     } else {
12641         currentMove = target;
12642     }
12643     
12644     if (gameMode == EditGame || gameMode == EndOfGame) {
12645         whiteTimeRemaining = timeRemaining[0][currentMove];
12646         blackTimeRemaining = timeRemaining[1][currentMove];
12647     }
12648     DisplayBothClocks();
12649     DisplayMove(currentMove - 1);
12650     DrawPosition(full_redraw, boards[currentMove]);
12651     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12652     // [HGM] PV info: routine tests if comment empty
12653     DisplayComment(currentMove - 1, commentList[currentMove]);
12654 }
12655
12656 void
12657 BackwardEvent()
12658 {
12659     if (gameMode == IcsExamining && !pausing) {
12660         SendToICS(ics_prefix);
12661         SendToICS("backward\n");
12662     } else {
12663         BackwardInner(currentMove - 1);
12664     }
12665 }
12666
12667 void
12668 ToStartEvent()
12669 {
12670     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12671         /* to optimize, we temporarily turn off analysis mode while we undo
12672          * all the moves. Otherwise we get analysis output after each undo.
12673          */ 
12674         if (first.analysisSupport) {
12675           SendToProgram("exit\nforce\n", &first);
12676           first.analyzing = FALSE;
12677         }
12678     }
12679
12680     if (gameMode == IcsExamining && !pausing) {
12681         SendToICS(ics_prefix);
12682         SendToICS("backward 999999\n");
12683     } else {
12684         BackwardInner(backwardMostMove);
12685     }
12686
12687     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12688         /* we have fed all the moves, so reactivate analysis mode */
12689         SendToProgram("analyze\n", &first);
12690         first.analyzing = TRUE;
12691         /*first.maybeThinking = TRUE;*/
12692         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12693     }
12694 }
12695
12696 void
12697 ToNrEvent(int to)
12698 {
12699   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12700   if (to >= forwardMostMove) to = forwardMostMove;
12701   if (to <= backwardMostMove) to = backwardMostMove;
12702   if (to < currentMove) {
12703     BackwardInner(to);
12704   } else {
12705     ForwardInner(to);
12706   }
12707 }
12708
12709 void
12710 RevertEvent(Boolean annotate)
12711 {
12712     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12713         return;
12714     }
12715     if (gameMode != IcsExamining) {
12716         DisplayError(_("You are not examining a game"), 0);
12717         return;
12718     }
12719     if (pausing) {
12720         DisplayError(_("You can't revert while pausing"), 0);
12721         return;
12722     }
12723     SendToICS(ics_prefix);
12724     SendToICS("revert\n");
12725 }
12726
12727 void
12728 RetractMoveEvent()
12729 {
12730     switch (gameMode) {
12731       case MachinePlaysWhite:
12732       case MachinePlaysBlack:
12733         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12734             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12735             return;
12736         }
12737         if (forwardMostMove < 2) return;
12738         currentMove = forwardMostMove = forwardMostMove - 2;
12739         whiteTimeRemaining = timeRemaining[0][currentMove];
12740         blackTimeRemaining = timeRemaining[1][currentMove];
12741         DisplayBothClocks();
12742         DisplayMove(currentMove - 1);
12743         ClearHighlights();/*!! could figure this out*/
12744         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12745         SendToProgram("remove\n", &first);
12746         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12747         break;
12748
12749       case BeginningOfGame:
12750       default:
12751         break;
12752
12753       case IcsPlayingWhite:
12754       case IcsPlayingBlack:
12755         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12756             SendToICS(ics_prefix);
12757             SendToICS("takeback 2\n");
12758         } else {
12759             SendToICS(ics_prefix);
12760             SendToICS("takeback 1\n");
12761         }
12762         break;
12763     }
12764 }
12765
12766 void
12767 MoveNowEvent()
12768 {
12769     ChessProgramState *cps;
12770
12771     switch (gameMode) {
12772       case MachinePlaysWhite:
12773         if (!WhiteOnMove(forwardMostMove)) {
12774             DisplayError(_("It is your turn"), 0);
12775             return;
12776         }
12777         cps = &first;
12778         break;
12779       case MachinePlaysBlack:
12780         if (WhiteOnMove(forwardMostMove)) {
12781             DisplayError(_("It is your turn"), 0);
12782             return;
12783         }
12784         cps = &first;
12785         break;
12786       case TwoMachinesPlay:
12787         if (WhiteOnMove(forwardMostMove) ==
12788             (first.twoMachinesColor[0] == 'w')) {
12789             cps = &first;
12790         } else {
12791             cps = &second;
12792         }
12793         break;
12794       case BeginningOfGame:
12795       default:
12796         return;
12797     }
12798     SendToProgram("?\n", cps);
12799 }
12800
12801 void
12802 TruncateGameEvent()
12803 {
12804     EditGameEvent();
12805     if (gameMode != EditGame) return;
12806     TruncateGame();
12807 }
12808
12809 void
12810 TruncateGame()
12811 {
12812     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12813     if (forwardMostMove > currentMove) {
12814         if (gameInfo.resultDetails != NULL) {
12815             free(gameInfo.resultDetails);
12816             gameInfo.resultDetails = NULL;
12817             gameInfo.result = GameUnfinished;
12818         }
12819         forwardMostMove = currentMove;
12820         HistorySet(parseList, backwardMostMove, forwardMostMove,
12821                    currentMove-1);
12822     }
12823 }
12824
12825 void
12826 HintEvent()
12827 {
12828     if (appData.noChessProgram) return;
12829     switch (gameMode) {
12830       case MachinePlaysWhite:
12831         if (WhiteOnMove(forwardMostMove)) {
12832             DisplayError(_("Wait until your turn"), 0);
12833             return;
12834         }
12835         break;
12836       case BeginningOfGame:
12837       case MachinePlaysBlack:
12838         if (!WhiteOnMove(forwardMostMove)) {
12839             DisplayError(_("Wait until your turn"), 0);
12840             return;
12841         }
12842         break;
12843       default:
12844         DisplayError(_("No hint available"), 0);
12845         return;
12846     }
12847     SendToProgram("hint\n", &first);
12848     hintRequested = TRUE;
12849 }
12850
12851 void
12852 BookEvent()
12853 {
12854     if (appData.noChessProgram) return;
12855     switch (gameMode) {
12856       case MachinePlaysWhite:
12857         if (WhiteOnMove(forwardMostMove)) {
12858             DisplayError(_("Wait until your turn"), 0);
12859             return;
12860         }
12861         break;
12862       case BeginningOfGame:
12863       case MachinePlaysBlack:
12864         if (!WhiteOnMove(forwardMostMove)) {
12865             DisplayError(_("Wait until your turn"), 0);
12866             return;
12867         }
12868         break;
12869       case EditPosition:
12870         EditPositionDone(TRUE);
12871         break;
12872       case TwoMachinesPlay:
12873         return;
12874       default:
12875         break;
12876     }
12877     SendToProgram("bk\n", &first);
12878     bookOutput[0] = NULLCHAR;
12879     bookRequested = TRUE;
12880 }
12881
12882 void
12883 AboutGameEvent()
12884 {
12885     char *tags = PGNTags(&gameInfo);
12886     TagsPopUp(tags, CmailMsg());
12887     free(tags);
12888 }
12889
12890 /* end button procedures */
12891
12892 void
12893 PrintPosition(fp, move)
12894      FILE *fp;
12895      int move;
12896 {
12897     int i, j;
12898     
12899     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12900         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12901             char c = PieceToChar(boards[move][i][j]);
12902             fputc(c == 'x' ? '.' : c, fp);
12903             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12904         }
12905     }
12906     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12907       fprintf(fp, "white to play\n");
12908     else
12909       fprintf(fp, "black to play\n");
12910 }
12911
12912 void
12913 PrintOpponents(fp)
12914      FILE *fp;
12915 {
12916     if (gameInfo.white != NULL) {
12917         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12918     } else {
12919         fprintf(fp, "\n");
12920     }
12921 }
12922
12923 /* Find last component of program's own name, using some heuristics */
12924 void
12925 TidyProgramName(prog, host, buf)
12926      char *prog, *host, buf[MSG_SIZ];
12927 {
12928     char *p, *q;
12929     int local = (strcmp(host, "localhost") == 0);
12930     while (!local && (p = strchr(prog, ';')) != NULL) {
12931         p++;
12932         while (*p == ' ') p++;
12933         prog = p;
12934     }
12935     if (*prog == '"' || *prog == '\'') {
12936         q = strchr(prog + 1, *prog);
12937     } else {
12938         q = strchr(prog, ' ');
12939     }
12940     if (q == NULL) q = prog + strlen(prog);
12941     p = q;
12942     while (p >= prog && *p != '/' && *p != '\\') p--;
12943     p++;
12944     if(p == prog && *p == '"') p++;
12945     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12946     memcpy(buf, p, q - p);
12947     buf[q - p] = NULLCHAR;
12948     if (!local) {
12949         strcat(buf, "@");
12950         strcat(buf, host);
12951     }
12952 }
12953
12954 char *
12955 TimeControlTagValue()
12956 {
12957     char buf[MSG_SIZ];
12958     if (!appData.clockMode) {
12959         strcpy(buf, "-");
12960     } else if (movesPerSession > 0) {
12961         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12962     } else if (timeIncrement == 0) {
12963         sprintf(buf, "%ld", timeControl/1000);
12964     } else {
12965         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12966     }
12967     return StrSave(buf);
12968 }
12969
12970 void
12971 SetGameInfo()
12972 {
12973     /* This routine is used only for certain modes */
12974     VariantClass v = gameInfo.variant;
12975     ChessMove r = GameUnfinished;
12976     char *p = NULL;
12977
12978     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12979         r = gameInfo.result; 
12980         p = gameInfo.resultDetails; 
12981         gameInfo.resultDetails = NULL;
12982     }
12983     ClearGameInfo(&gameInfo);
12984     gameInfo.variant = v;
12985
12986     switch (gameMode) {
12987       case MachinePlaysWhite:
12988         gameInfo.event = StrSave( appData.pgnEventHeader );
12989         gameInfo.site = StrSave(HostName());
12990         gameInfo.date = PGNDate();
12991         gameInfo.round = StrSave("-");
12992         gameInfo.white = StrSave(first.tidy);
12993         gameInfo.black = StrSave(UserName());
12994         gameInfo.timeControl = TimeControlTagValue();
12995         break;
12996
12997       case MachinePlaysBlack:
12998         gameInfo.event = StrSave( appData.pgnEventHeader );
12999         gameInfo.site = StrSave(HostName());
13000         gameInfo.date = PGNDate();
13001         gameInfo.round = StrSave("-");
13002         gameInfo.white = StrSave(UserName());
13003         gameInfo.black = StrSave(first.tidy);
13004         gameInfo.timeControl = TimeControlTagValue();
13005         break;
13006
13007       case TwoMachinesPlay:
13008         gameInfo.event = StrSave( appData.pgnEventHeader );
13009         gameInfo.site = StrSave(HostName());
13010         gameInfo.date = PGNDate();
13011         if (matchGame > 0) {
13012             char buf[MSG_SIZ];
13013             sprintf(buf, "%d", matchGame);
13014             gameInfo.round = StrSave(buf);
13015         } else {
13016             gameInfo.round = StrSave("-");
13017         }
13018         if (first.twoMachinesColor[0] == 'w') {
13019             gameInfo.white = StrSave(first.tidy);
13020             gameInfo.black = StrSave(second.tidy);
13021         } else {
13022             gameInfo.white = StrSave(second.tidy);
13023             gameInfo.black = StrSave(first.tidy);
13024         }
13025         gameInfo.timeControl = TimeControlTagValue();
13026         break;
13027
13028       case EditGame:
13029         gameInfo.event = StrSave("Edited game");
13030         gameInfo.site = StrSave(HostName());
13031         gameInfo.date = PGNDate();
13032         gameInfo.round = StrSave("-");
13033         gameInfo.white = StrSave("-");
13034         gameInfo.black = StrSave("-");
13035         gameInfo.result = r;
13036         gameInfo.resultDetails = p;
13037         break;
13038
13039       case EditPosition:
13040         gameInfo.event = StrSave("Edited position");
13041         gameInfo.site = StrSave(HostName());
13042         gameInfo.date = PGNDate();
13043         gameInfo.round = StrSave("-");
13044         gameInfo.white = StrSave("-");
13045         gameInfo.black = StrSave("-");
13046         break;
13047
13048       case IcsPlayingWhite:
13049       case IcsPlayingBlack:
13050       case IcsObserving:
13051       case IcsExamining:
13052         break;
13053
13054       case PlayFromGameFile:
13055         gameInfo.event = StrSave("Game from non-PGN file");
13056         gameInfo.site = StrSave(HostName());
13057         gameInfo.date = PGNDate();
13058         gameInfo.round = StrSave("-");
13059         gameInfo.white = StrSave("?");
13060         gameInfo.black = StrSave("?");
13061         break;
13062
13063       default:
13064         break;
13065     }
13066 }
13067
13068 void
13069 ReplaceComment(index, text)
13070      int index;
13071      char *text;
13072 {
13073     int len;
13074
13075     while (*text == '\n') text++;
13076     len = strlen(text);
13077     while (len > 0 && text[len - 1] == '\n') len--;
13078
13079     if (commentList[index] != NULL)
13080       free(commentList[index]);
13081
13082     if (len == 0) {
13083         commentList[index] = NULL;
13084         return;
13085     }
13086   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13087       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13088       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13089     commentList[index] = (char *) malloc(len + 2);
13090     strncpy(commentList[index], text, len);
13091     commentList[index][len] = '\n';
13092     commentList[index][len + 1] = NULLCHAR;
13093   } else { 
13094     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13095     char *p;
13096     commentList[index] = (char *) malloc(len + 6);
13097     strcpy(commentList[index], "{\n");
13098     strncpy(commentList[index]+2, text, len);
13099     commentList[index][len+2] = NULLCHAR;
13100     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13101     strcat(commentList[index], "\n}\n");
13102   }
13103 }
13104
13105 void
13106 CrushCRs(text)
13107      char *text;
13108 {
13109   char *p = text;
13110   char *q = text;
13111   char ch;
13112
13113   do {
13114     ch = *p++;
13115     if (ch == '\r') continue;
13116     *q++ = ch;
13117   } while (ch != '\0');
13118 }
13119
13120 void
13121 AppendComment(index, text, addBraces)
13122      int index;
13123      char *text;
13124      Boolean addBraces; // [HGM] braces: tells if we should add {}
13125 {
13126     int oldlen, len;
13127     char *old;
13128
13129 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13130     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13131
13132     CrushCRs(text);
13133     while (*text == '\n') text++;
13134     len = strlen(text);
13135     while (len > 0 && text[len - 1] == '\n') len--;
13136
13137     if (len == 0) return;
13138
13139     if (commentList[index] != NULL) {
13140         old = commentList[index];
13141         oldlen = strlen(old);
13142         while(commentList[index][oldlen-1] ==  '\n')
13143           commentList[index][--oldlen] = NULLCHAR;
13144         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13145         strcpy(commentList[index], old);
13146         free(old);
13147         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13148         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13149           if(addBraces) addBraces = FALSE; else { text++; len--; }
13150           while (*text == '\n') { text++; len--; }
13151           commentList[index][--oldlen] = NULLCHAR;
13152       }
13153         if(addBraces) strcat(commentList[index], "\n{\n");
13154         else          strcat(commentList[index], "\n");
13155         strcat(commentList[index], text);
13156         if(addBraces) strcat(commentList[index], "\n}\n");
13157         else          strcat(commentList[index], "\n");
13158     } else {
13159         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13160         if(addBraces)
13161              strcpy(commentList[index], "{\n");
13162         else commentList[index][0] = NULLCHAR;
13163         strcat(commentList[index], text);
13164         strcat(commentList[index], "\n");
13165         if(addBraces) strcat(commentList[index], "}\n");
13166     }
13167 }
13168
13169 static char * FindStr( char * text, char * sub_text )
13170 {
13171     char * result = strstr( text, sub_text );
13172
13173     if( result != NULL ) {
13174         result += strlen( sub_text );
13175     }
13176
13177     return result;
13178 }
13179
13180 /* [AS] Try to extract PV info from PGN comment */
13181 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13182 char *GetInfoFromComment( int index, char * text )
13183 {
13184     char * sep = text;
13185
13186     if( text != NULL && index > 0 ) {
13187         int score = 0;
13188         int depth = 0;
13189         int time = -1, sec = 0, deci;
13190         char * s_eval = FindStr( text, "[%eval " );
13191         char * s_emt = FindStr( text, "[%emt " );
13192
13193         if( s_eval != NULL || s_emt != NULL ) {
13194             /* New style */
13195             char delim;
13196
13197             if( s_eval != NULL ) {
13198                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13199                     return text;
13200                 }
13201
13202                 if( delim != ']' ) {
13203                     return text;
13204                 }
13205             }
13206
13207             if( s_emt != NULL ) {
13208             }
13209                 return text;
13210         }
13211         else {
13212             /* We expect something like: [+|-]nnn.nn/dd */
13213             int score_lo = 0;
13214
13215             if(*text != '{') return text; // [HGM] braces: must be normal comment
13216
13217             sep = strchr( text, '/' );
13218             if( sep == NULL || sep < (text+4) ) {
13219                 return text;
13220             }
13221
13222             time = -1; sec = -1; deci = -1;
13223             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13224                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13225                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13226                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13227                 return text;
13228             }
13229
13230             if( score_lo < 0 || score_lo >= 100 ) {
13231                 return text;
13232             }
13233
13234             if(sec >= 0) time = 600*time + 10*sec; else
13235             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13236
13237             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13238
13239             /* [HGM] PV time: now locate end of PV info */
13240             while( *++sep >= '0' && *sep <= '9'); // strip depth
13241             if(time >= 0)
13242             while( *++sep >= '0' && *sep <= '9'); // strip time
13243             if(sec >= 0)
13244             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13245             if(deci >= 0)
13246             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13247             while(*sep == ' ') sep++;
13248         }
13249
13250         if( depth <= 0 ) {
13251             return text;
13252         }
13253
13254         if( time < 0 ) {
13255             time = -1;
13256         }
13257
13258         pvInfoList[index-1].depth = depth;
13259         pvInfoList[index-1].score = score;
13260         pvInfoList[index-1].time  = 10*time; // centi-sec
13261         if(*sep == '}') *sep = 0; else *--sep = '{';
13262     }
13263     return sep;
13264 }
13265
13266 void
13267 SendToProgram(message, cps)
13268      char *message;
13269      ChessProgramState *cps;
13270 {
13271     int count, outCount, error;
13272     char buf[MSG_SIZ];
13273
13274     if (cps->pr == NULL) return;
13275     Attention(cps);
13276     
13277     if (appData.debugMode) {
13278         TimeMark now;
13279         GetTimeMark(&now);
13280         fprintf(debugFP, "%ld >%-6s: %s", 
13281                 SubtractTimeMarks(&now, &programStartTime),
13282                 cps->which, message);
13283     }
13284     
13285     count = strlen(message);
13286     outCount = OutputToProcess(cps->pr, message, count, &error);
13287     if (outCount < count && !exiting 
13288                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13289         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13290         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13291             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13292                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13293                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13294             } else {
13295                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13296             }
13297             gameInfo.resultDetails = StrSave(buf);
13298         }
13299         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13300     }
13301 }
13302
13303 void
13304 ReceiveFromProgram(isr, closure, message, count, error)
13305      InputSourceRef isr;
13306      VOIDSTAR closure;
13307      char *message;
13308      int count;
13309      int error;
13310 {
13311     char *end_str;
13312     char buf[MSG_SIZ];
13313     ChessProgramState *cps = (ChessProgramState *)closure;
13314
13315     if (isr != cps->isr) return; /* Killed intentionally */
13316     if (count <= 0) {
13317         if (count == 0) {
13318             sprintf(buf,
13319                     _("Error: %s chess program (%s) exited unexpectedly"),
13320                     cps->which, cps->program);
13321         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13322                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13323                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13324                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13325                 } else {
13326                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13327                 }
13328                 gameInfo.resultDetails = StrSave(buf);
13329             }
13330             RemoveInputSource(cps->isr);
13331             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13332         } else {
13333             sprintf(buf,
13334                     _("Error reading from %s chess program (%s)"),
13335                     cps->which, cps->program);
13336             RemoveInputSource(cps->isr);
13337
13338             /* [AS] Program is misbehaving badly... kill it */
13339             if( count == -2 ) {
13340                 DestroyChildProcess( cps->pr, 9 );
13341                 cps->pr = NoProc;
13342             }
13343
13344             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13345         }
13346         return;
13347     }
13348     
13349     if ((end_str = strchr(message, '\r')) != NULL)
13350       *end_str = NULLCHAR;
13351     if ((end_str = strchr(message, '\n')) != NULL)
13352       *end_str = NULLCHAR;
13353     
13354     if (appData.debugMode) {
13355         TimeMark now; int print = 1;
13356         char *quote = ""; char c; int i;
13357
13358         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13359                 char start = message[0];
13360                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13361                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13362                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13363                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13364                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13365                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13366                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13367                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13368                         { quote = "# "; print = (appData.engineComments == 2); }
13369                 message[0] = start; // restore original message
13370         }
13371         if(print) {
13372                 GetTimeMark(&now);
13373                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13374                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13375                         quote,
13376                         message);
13377         }
13378     }
13379
13380     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13381     if (appData.icsEngineAnalyze) {
13382         if (strstr(message, "whisper") != NULL ||
13383              strstr(message, "kibitz") != NULL || 
13384             strstr(message, "tellics") != NULL) return;
13385     }
13386
13387     HandleMachineMove(message, cps);
13388 }
13389
13390
13391 void
13392 SendTimeControl(cps, mps, tc, inc, sd, st)
13393      ChessProgramState *cps;
13394      int mps, inc, sd, st;
13395      long tc;
13396 {
13397     char buf[MSG_SIZ];
13398     int seconds;
13399
13400     if( timeControl_2 > 0 ) {
13401         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13402             tc = timeControl_2;
13403         }
13404     }
13405     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13406     inc /= cps->timeOdds;
13407     st  /= cps->timeOdds;
13408
13409     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13410
13411     if (st > 0) {
13412       /* Set exact time per move, normally using st command */
13413       if (cps->stKludge) {
13414         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13415         seconds = st % 60;
13416         if (seconds == 0) {
13417           sprintf(buf, "level 1 %d\n", st/60);
13418         } else {
13419           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13420         }
13421       } else {
13422         sprintf(buf, "st %d\n", st);
13423       }
13424     } else {
13425       /* Set conventional or incremental time control, using level command */
13426       if (seconds == 0) {
13427         /* Note old gnuchess bug -- minutes:seconds used to not work.
13428            Fixed in later versions, but still avoid :seconds
13429            when seconds is 0. */
13430         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13431       } else {
13432         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13433                 seconds, inc/1000);
13434       }
13435     }
13436     SendToProgram(buf, cps);
13437
13438     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13439     /* Orthogonally, limit search to given depth */
13440     if (sd > 0) {
13441       if (cps->sdKludge) {
13442         sprintf(buf, "depth\n%d\n", sd);
13443       } else {
13444         sprintf(buf, "sd %d\n", sd);
13445       }
13446       SendToProgram(buf, cps);
13447     }
13448
13449     if(cps->nps > 0) { /* [HGM] nps */
13450         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13451         else {
13452                 sprintf(buf, "nps %d\n", cps->nps);
13453               SendToProgram(buf, cps);
13454         }
13455     }
13456 }
13457
13458 ChessProgramState *WhitePlayer()
13459 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13460 {
13461     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13462        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13463         return &second;
13464     return &first;
13465 }
13466
13467 void
13468 SendTimeRemaining(cps, machineWhite)
13469      ChessProgramState *cps;
13470      int /*boolean*/ machineWhite;
13471 {
13472     char message[MSG_SIZ];
13473     long time, otime;
13474
13475     /* Note: this routine must be called when the clocks are stopped
13476        or when they have *just* been set or switched; otherwise
13477        it will be off by the time since the current tick started.
13478     */
13479     if (machineWhite) {
13480         time = whiteTimeRemaining / 10;
13481         otime = blackTimeRemaining / 10;
13482     } else {
13483         time = blackTimeRemaining / 10;
13484         otime = whiteTimeRemaining / 10;
13485     }
13486     /* [HGM] translate opponent's time by time-odds factor */
13487     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13488     if (appData.debugMode) {
13489         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13490     }
13491
13492     if (time <= 0) time = 1;
13493     if (otime <= 0) otime = 1;
13494     
13495     sprintf(message, "time %ld\n", time);
13496     SendToProgram(message, cps);
13497
13498     sprintf(message, "otim %ld\n", otime);
13499     SendToProgram(message, cps);
13500 }
13501
13502 int
13503 BoolFeature(p, name, loc, cps)
13504      char **p;
13505      char *name;
13506      int *loc;
13507      ChessProgramState *cps;
13508 {
13509   char buf[MSG_SIZ];
13510   int len = strlen(name);
13511   int val;
13512   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13513     (*p) += len + 1;
13514     sscanf(*p, "%d", &val);
13515     *loc = (val != 0);
13516     while (**p && **p != ' ') (*p)++;
13517     sprintf(buf, "accepted %s\n", name);
13518     SendToProgram(buf, cps);
13519     return TRUE;
13520   }
13521   return FALSE;
13522 }
13523
13524 int
13525 IntFeature(p, name, loc, cps)
13526      char **p;
13527      char *name;
13528      int *loc;
13529      ChessProgramState *cps;
13530 {
13531   char buf[MSG_SIZ];
13532   int len = strlen(name);
13533   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13534     (*p) += len + 1;
13535     sscanf(*p, "%d", loc);
13536     while (**p && **p != ' ') (*p)++;
13537     sprintf(buf, "accepted %s\n", name);
13538     SendToProgram(buf, cps);
13539     return TRUE;
13540   }
13541   return FALSE;
13542 }
13543
13544 int
13545 StringFeature(p, name, loc, cps)
13546      char **p;
13547      char *name;
13548      char loc[];
13549      ChessProgramState *cps;
13550 {
13551   char buf[MSG_SIZ];
13552   int len = strlen(name);
13553   if (strncmp((*p), name, len) == 0
13554       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13555     (*p) += len + 2;
13556     sscanf(*p, "%[^\"]", loc);
13557     while (**p && **p != '\"') (*p)++;
13558     if (**p == '\"') (*p)++;
13559     sprintf(buf, "accepted %s\n", name);
13560     SendToProgram(buf, cps);
13561     return TRUE;
13562   }
13563   return FALSE;
13564 }
13565
13566 int 
13567 ParseOption(Option *opt, ChessProgramState *cps)
13568 // [HGM] options: process the string that defines an engine option, and determine
13569 // name, type, default value, and allowed value range
13570 {
13571         char *p, *q, buf[MSG_SIZ];
13572         int n, min = (-1)<<31, max = 1<<31, def;
13573
13574         if(p = strstr(opt->name, " -spin ")) {
13575             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13576             if(max < min) max = min; // enforce consistency
13577             if(def < min) def = min;
13578             if(def > max) def = max;
13579             opt->value = def;
13580             opt->min = min;
13581             opt->max = max;
13582             opt->type = Spin;
13583         } else if((p = strstr(opt->name, " -slider "))) {
13584             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13585             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13586             if(max < min) max = min; // enforce consistency
13587             if(def < min) def = min;
13588             if(def > max) def = max;
13589             opt->value = def;
13590             opt->min = min;
13591             opt->max = max;
13592             opt->type = Spin; // Slider;
13593         } else if((p = strstr(opt->name, " -string "))) {
13594             opt->textValue = p+9;
13595             opt->type = TextBox;
13596         } else if((p = strstr(opt->name, " -file "))) {
13597             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13598             opt->textValue = p+7;
13599             opt->type = TextBox; // FileName;
13600         } else if((p = strstr(opt->name, " -path "))) {
13601             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13602             opt->textValue = p+7;
13603             opt->type = TextBox; // PathName;
13604         } else if(p = strstr(opt->name, " -check ")) {
13605             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13606             opt->value = (def != 0);
13607             opt->type = CheckBox;
13608         } else if(p = strstr(opt->name, " -combo ")) {
13609             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13610             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13611             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13612             opt->value = n = 0;
13613             while(q = StrStr(q, " /// ")) {
13614                 n++; *q = 0;    // count choices, and null-terminate each of them
13615                 q += 5;
13616                 if(*q == '*') { // remember default, which is marked with * prefix
13617                     q++;
13618                     opt->value = n;
13619                 }
13620                 cps->comboList[cps->comboCnt++] = q;
13621             }
13622             cps->comboList[cps->comboCnt++] = NULL;
13623             opt->max = n + 1;
13624             opt->type = ComboBox;
13625         } else if(p = strstr(opt->name, " -button")) {
13626             opt->type = Button;
13627         } else if(p = strstr(opt->name, " -save")) {
13628             opt->type = SaveButton;
13629         } else return FALSE;
13630         *p = 0; // terminate option name
13631         // now look if the command-line options define a setting for this engine option.
13632         if(cps->optionSettings && cps->optionSettings[0])
13633             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13634         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13635                 sprintf(buf, "option %s", p);
13636                 if(p = strstr(buf, ",")) *p = 0;
13637                 strcat(buf, "\n");
13638                 SendToProgram(buf, cps);
13639         }
13640         return TRUE;
13641 }
13642
13643 void
13644 FeatureDone(cps, val)
13645      ChessProgramState* cps;
13646      int val;
13647 {
13648   DelayedEventCallback cb = GetDelayedEvent();
13649   if ((cb == InitBackEnd3 && cps == &first) ||
13650       (cb == TwoMachinesEventIfReady && cps == &second)) {
13651     CancelDelayedEvent();
13652     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13653   }
13654   cps->initDone = val;
13655 }
13656
13657 /* Parse feature command from engine */
13658 void
13659 ParseFeatures(args, cps)
13660      char* args;
13661      ChessProgramState *cps;  
13662 {
13663   char *p = args;
13664   char *q;
13665   int val;
13666   char buf[MSG_SIZ];
13667
13668   for (;;) {
13669     while (*p == ' ') p++;
13670     if (*p == NULLCHAR) return;
13671
13672     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13673     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13674     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13675     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13676     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13677     if (BoolFeature(&p, "reuse", &val, cps)) {
13678       /* Engine can disable reuse, but can't enable it if user said no */
13679       if (!val) cps->reuse = FALSE;
13680       continue;
13681     }
13682     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13683     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13684       if (gameMode == TwoMachinesPlay) {
13685         DisplayTwoMachinesTitle();
13686       } else {
13687         DisplayTitle("");
13688       }
13689       continue;
13690     }
13691     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13692     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13693     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13694     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13695     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13696     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13697     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13698     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13699     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13700     if (IntFeature(&p, "done", &val, cps)) {
13701       FeatureDone(cps, val);
13702       continue;
13703     }
13704     /* Added by Tord: */
13705     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13706     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13707     /* End of additions by Tord */
13708
13709     /* [HGM] added features: */
13710     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13711     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13712     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13713     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13714     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13715     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13716     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13717         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13718             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13719             SendToProgram(buf, cps);
13720             continue;
13721         }
13722         if(cps->nrOptions >= MAX_OPTIONS) {
13723             cps->nrOptions--;
13724             sprintf(buf, "%s engine has too many options\n", cps->which);
13725             DisplayError(buf, 0);
13726         }
13727         continue;
13728     }
13729     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13730     /* End of additions by HGM */
13731
13732     /* unknown feature: complain and skip */
13733     q = p;
13734     while (*q && *q != '=') q++;
13735     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13736     SendToProgram(buf, cps);
13737     p = q;
13738     if (*p == '=') {
13739       p++;
13740       if (*p == '\"') {
13741         p++;
13742         while (*p && *p != '\"') p++;
13743         if (*p == '\"') p++;
13744       } else {
13745         while (*p && *p != ' ') p++;
13746       }
13747     }
13748   }
13749
13750 }
13751
13752 void
13753 PeriodicUpdatesEvent(newState)
13754      int newState;
13755 {
13756     if (newState == appData.periodicUpdates)
13757       return;
13758
13759     appData.periodicUpdates=newState;
13760
13761     /* Display type changes, so update it now */
13762 //    DisplayAnalysis();
13763
13764     /* Get the ball rolling again... */
13765     if (newState) {
13766         AnalysisPeriodicEvent(1);
13767         StartAnalysisClock();
13768     }
13769 }
13770
13771 void
13772 PonderNextMoveEvent(newState)
13773      int newState;
13774 {
13775     if (newState == appData.ponderNextMove) return;
13776     if (gameMode == EditPosition) EditPositionDone(TRUE);
13777     if (newState) {
13778         SendToProgram("hard\n", &first);
13779         if (gameMode == TwoMachinesPlay) {
13780             SendToProgram("hard\n", &second);
13781         }
13782     } else {
13783         SendToProgram("easy\n", &first);
13784         thinkOutput[0] = NULLCHAR;
13785         if (gameMode == TwoMachinesPlay) {
13786             SendToProgram("easy\n", &second);
13787         }
13788     }
13789     appData.ponderNextMove = newState;
13790 }
13791
13792 void
13793 NewSettingEvent(option, command, value)
13794      char *command;
13795      int option, value;
13796 {
13797     char buf[MSG_SIZ];
13798
13799     if (gameMode == EditPosition) EditPositionDone(TRUE);
13800     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13801     SendToProgram(buf, &first);
13802     if (gameMode == TwoMachinesPlay) {
13803         SendToProgram(buf, &second);
13804     }
13805 }
13806
13807 void
13808 ShowThinkingEvent()
13809 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13810 {
13811     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13812     int newState = appData.showThinking
13813         // [HGM] thinking: other features now need thinking output as well
13814         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13815     
13816     if (oldState == newState) return;
13817     oldState = newState;
13818     if (gameMode == EditPosition) EditPositionDone(TRUE);
13819     if (oldState) {
13820         SendToProgram("post\n", &first);
13821         if (gameMode == TwoMachinesPlay) {
13822             SendToProgram("post\n", &second);
13823         }
13824     } else {
13825         SendToProgram("nopost\n", &first);
13826         thinkOutput[0] = NULLCHAR;
13827         if (gameMode == TwoMachinesPlay) {
13828             SendToProgram("nopost\n", &second);
13829         }
13830     }
13831 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13832 }
13833
13834 void
13835 AskQuestionEvent(title, question, replyPrefix, which)
13836      char *title; char *question; char *replyPrefix; char *which;
13837 {
13838   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13839   if (pr == NoProc) return;
13840   AskQuestion(title, question, replyPrefix, pr);
13841 }
13842
13843 void
13844 DisplayMove(moveNumber)
13845      int moveNumber;
13846 {
13847     char message[MSG_SIZ];
13848     char res[MSG_SIZ];
13849     char cpThinkOutput[MSG_SIZ];
13850
13851     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13852     
13853     if (moveNumber == forwardMostMove - 1 || 
13854         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13855
13856         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13857
13858         if (strchr(cpThinkOutput, '\n')) {
13859             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13860         }
13861     } else {
13862         *cpThinkOutput = NULLCHAR;
13863     }
13864
13865     /* [AS] Hide thinking from human user */
13866     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13867         *cpThinkOutput = NULLCHAR;
13868         if( thinkOutput[0] != NULLCHAR ) {
13869             int i;
13870
13871             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13872                 cpThinkOutput[i] = '.';
13873             }
13874             cpThinkOutput[i] = NULLCHAR;
13875             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13876         }
13877     }
13878
13879     if (moveNumber == forwardMostMove - 1 &&
13880         gameInfo.resultDetails != NULL) {
13881         if (gameInfo.resultDetails[0] == NULLCHAR) {
13882             sprintf(res, " %s", PGNResult(gameInfo.result));
13883         } else {
13884             sprintf(res, " {%s} %s",
13885                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13886         }
13887     } else {
13888         res[0] = NULLCHAR;
13889     }
13890
13891     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13892         DisplayMessage(res, cpThinkOutput);
13893     } else {
13894         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13895                 WhiteOnMove(moveNumber) ? " " : ".. ",
13896                 parseList[moveNumber], res);
13897         DisplayMessage(message, cpThinkOutput);
13898     }
13899 }
13900
13901 void
13902 DisplayComment(moveNumber, text)
13903      int moveNumber;
13904      char *text;
13905 {
13906     char title[MSG_SIZ];
13907     char buf[8000]; // comment can be long!
13908     int score, depth;
13909     
13910     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13911       strcpy(title, "Comment");
13912     } else {
13913       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13914               WhiteOnMove(moveNumber) ? " " : ".. ",
13915               parseList[moveNumber]);
13916     }
13917     // [HGM] PV info: display PV info together with (or as) comment
13918     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13919       if(text == NULL) text = "";                                           
13920       score = pvInfoList[moveNumber].score;
13921       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13922               depth, (pvInfoList[moveNumber].time+50)/100, text);
13923       text = buf;
13924     }
13925     if (text != NULL && (appData.autoDisplayComment || commentUp))
13926         CommentPopUp(title, text);
13927 }
13928
13929 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13930  * might be busy thinking or pondering.  It can be omitted if your
13931  * gnuchess is configured to stop thinking immediately on any user
13932  * input.  However, that gnuchess feature depends on the FIONREAD
13933  * ioctl, which does not work properly on some flavors of Unix.
13934  */
13935 void
13936 Attention(cps)
13937      ChessProgramState *cps;
13938 {
13939 #if ATTENTION
13940     if (!cps->useSigint) return;
13941     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13942     switch (gameMode) {
13943       case MachinePlaysWhite:
13944       case MachinePlaysBlack:
13945       case TwoMachinesPlay:
13946       case IcsPlayingWhite:
13947       case IcsPlayingBlack:
13948       case AnalyzeMode:
13949       case AnalyzeFile:
13950         /* Skip if we know it isn't thinking */
13951         if (!cps->maybeThinking) return;
13952         if (appData.debugMode)
13953           fprintf(debugFP, "Interrupting %s\n", cps->which);
13954         InterruptChildProcess(cps->pr);
13955         cps->maybeThinking = FALSE;
13956         break;
13957       default:
13958         break;
13959     }
13960 #endif /*ATTENTION*/
13961 }
13962
13963 int
13964 CheckFlags()
13965 {
13966     if (whiteTimeRemaining <= 0) {
13967         if (!whiteFlag) {
13968             whiteFlag = TRUE;
13969             if (appData.icsActive) {
13970                 if (appData.autoCallFlag &&
13971                     gameMode == IcsPlayingBlack && !blackFlag) {
13972                   SendToICS(ics_prefix);
13973                   SendToICS("flag\n");
13974                 }
13975             } else {
13976                 if (blackFlag) {
13977                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13978                 } else {
13979                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13980                     if (appData.autoCallFlag) {
13981                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13982                         return TRUE;
13983                     }
13984                 }
13985             }
13986         }
13987     }
13988     if (blackTimeRemaining <= 0) {
13989         if (!blackFlag) {
13990             blackFlag = TRUE;
13991             if (appData.icsActive) {
13992                 if (appData.autoCallFlag &&
13993                     gameMode == IcsPlayingWhite && !whiteFlag) {
13994                   SendToICS(ics_prefix);
13995                   SendToICS("flag\n");
13996                 }
13997             } else {
13998                 if (whiteFlag) {
13999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14000                 } else {
14001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14002                     if (appData.autoCallFlag) {
14003                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14004                         return TRUE;
14005                     }
14006                 }
14007             }
14008         }
14009     }
14010     return FALSE;
14011 }
14012
14013 void
14014 CheckTimeControl()
14015 {
14016     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14017         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14018
14019     /*
14020      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14021      */
14022     if ( !WhiteOnMove(forwardMostMove) )
14023         /* White made time control */
14024         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14025         /* [HGM] time odds: correct new time quota for time odds! */
14026                                             / WhitePlayer()->timeOdds;
14027       else
14028         /* Black made time control */
14029         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14030                                             / WhitePlayer()->other->timeOdds;
14031 }
14032
14033 void
14034 DisplayBothClocks()
14035 {
14036     int wom = gameMode == EditPosition ?
14037       !blackPlaysFirst : WhiteOnMove(currentMove);
14038     DisplayWhiteClock(whiteTimeRemaining, wom);
14039     DisplayBlackClock(blackTimeRemaining, !wom);
14040 }
14041
14042
14043 /* Timekeeping seems to be a portability nightmare.  I think everyone
14044    has ftime(), but I'm really not sure, so I'm including some ifdefs
14045    to use other calls if you don't.  Clocks will be less accurate if
14046    you have neither ftime nor gettimeofday.
14047 */
14048
14049 /* VS 2008 requires the #include outside of the function */
14050 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14051 #include <sys/timeb.h>
14052 #endif
14053
14054 /* Get the current time as a TimeMark */
14055 void
14056 GetTimeMark(tm)
14057      TimeMark *tm;
14058 {
14059 #if HAVE_GETTIMEOFDAY
14060
14061     struct timeval timeVal;
14062     struct timezone timeZone;
14063
14064     gettimeofday(&timeVal, &timeZone);
14065     tm->sec = (long) timeVal.tv_sec; 
14066     tm->ms = (int) (timeVal.tv_usec / 1000L);
14067
14068 #else /*!HAVE_GETTIMEOFDAY*/
14069 #if HAVE_FTIME
14070
14071 // include <sys/timeb.h> / moved to just above start of function
14072     struct timeb timeB;
14073
14074     ftime(&timeB);
14075     tm->sec = (long) timeB.time;
14076     tm->ms = (int) timeB.millitm;
14077
14078 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14079     tm->sec = (long) time(NULL);
14080     tm->ms = 0;
14081 #endif
14082 #endif
14083 }
14084
14085 /* Return the difference in milliseconds between two
14086    time marks.  We assume the difference will fit in a long!
14087 */
14088 long
14089 SubtractTimeMarks(tm2, tm1)
14090      TimeMark *tm2, *tm1;
14091 {
14092     return 1000L*(tm2->sec - tm1->sec) +
14093            (long) (tm2->ms - tm1->ms);
14094 }
14095
14096
14097 /*
14098  * Code to manage the game clocks.
14099  *
14100  * In tournament play, black starts the clock and then white makes a move.
14101  * We give the human user a slight advantage if he is playing white---the
14102  * clocks don't run until he makes his first move, so it takes zero time.
14103  * Also, we don't account for network lag, so we could get out of sync
14104  * with GNU Chess's clock -- but then, referees are always right.  
14105  */
14106
14107 static TimeMark tickStartTM;
14108 static long intendedTickLength;
14109
14110 long
14111 NextTickLength(timeRemaining)
14112      long timeRemaining;
14113 {
14114     long nominalTickLength, nextTickLength;
14115
14116     if (timeRemaining > 0L && timeRemaining <= 10000L)
14117       nominalTickLength = 100L;
14118     else
14119       nominalTickLength = 1000L;
14120     nextTickLength = timeRemaining % nominalTickLength;
14121     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14122
14123     return nextTickLength;
14124 }
14125
14126 /* Adjust clock one minute up or down */
14127 void
14128 AdjustClock(Boolean which, int dir)
14129 {
14130     if(which) blackTimeRemaining += 60000*dir;
14131     else      whiteTimeRemaining += 60000*dir;
14132     DisplayBothClocks();
14133 }
14134
14135 /* Stop clocks and reset to a fresh time control */
14136 void
14137 ResetClocks() 
14138 {
14139     (void) StopClockTimer();
14140     if (appData.icsActive) {
14141         whiteTimeRemaining = blackTimeRemaining = 0;
14142     } else if (searchTime) {
14143         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14144         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14145     } else { /* [HGM] correct new time quote for time odds */
14146         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14147         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14148     }
14149     if (whiteFlag || blackFlag) {
14150         DisplayTitle("");
14151         whiteFlag = blackFlag = FALSE;
14152     }
14153     DisplayBothClocks();
14154 }
14155
14156 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14157
14158 /* Decrement running clock by amount of time that has passed */
14159 void
14160 DecrementClocks()
14161 {
14162     long timeRemaining;
14163     long lastTickLength, fudge;
14164     TimeMark now;
14165
14166     if (!appData.clockMode) return;
14167     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14168         
14169     GetTimeMark(&now);
14170
14171     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14172
14173     /* Fudge if we woke up a little too soon */
14174     fudge = intendedTickLength - lastTickLength;
14175     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14176
14177     if (WhiteOnMove(forwardMostMove)) {
14178         if(whiteNPS >= 0) lastTickLength = 0;
14179         timeRemaining = whiteTimeRemaining -= lastTickLength;
14180         DisplayWhiteClock(whiteTimeRemaining - fudge,
14181                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14182     } else {
14183         if(blackNPS >= 0) lastTickLength = 0;
14184         timeRemaining = blackTimeRemaining -= lastTickLength;
14185         DisplayBlackClock(blackTimeRemaining - fudge,
14186                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14187     }
14188
14189     if (CheckFlags()) return;
14190         
14191     tickStartTM = now;
14192     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14193     StartClockTimer(intendedTickLength);
14194
14195     /* if the time remaining has fallen below the alarm threshold, sound the
14196      * alarm. if the alarm has sounded and (due to a takeback or time control
14197      * with increment) the time remaining has increased to a level above the
14198      * threshold, reset the alarm so it can sound again. 
14199      */
14200     
14201     if (appData.icsActive && appData.icsAlarm) {
14202
14203         /* make sure we are dealing with the user's clock */
14204         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14205                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14206            )) return;
14207
14208         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14209             alarmSounded = FALSE;
14210         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14211             PlayAlarmSound();
14212             alarmSounded = TRUE;
14213         }
14214     }
14215 }
14216
14217
14218 /* A player has just moved, so stop the previously running
14219    clock and (if in clock mode) start the other one.
14220    We redisplay both clocks in case we're in ICS mode, because
14221    ICS gives us an update to both clocks after every move.
14222    Note that this routine is called *after* forwardMostMove
14223    is updated, so the last fractional tick must be subtracted
14224    from the color that is *not* on move now.
14225 */
14226 void
14227 SwitchClocks(int newMoveNr)
14228 {
14229     long lastTickLength;
14230     TimeMark now;
14231     int flagged = FALSE;
14232
14233     GetTimeMark(&now);
14234
14235     if (StopClockTimer() && appData.clockMode) {
14236         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14237         if (!WhiteOnMove(forwardMostMove)) {
14238             if(blackNPS >= 0) lastTickLength = 0;
14239             blackTimeRemaining -= lastTickLength;
14240            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14241 //         if(pvInfoList[forwardMostMove-1].time == -1)
14242                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14243                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14244         } else {
14245            if(whiteNPS >= 0) lastTickLength = 0;
14246            whiteTimeRemaining -= lastTickLength;
14247            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14248 //         if(pvInfoList[forwardMostMove-1].time == -1)
14249                  pvInfoList[forwardMostMove-1].time = 
14250                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14251         }
14252         flagged = CheckFlags();
14253     }
14254     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14255     CheckTimeControl();
14256
14257     if (flagged || !appData.clockMode) return;
14258
14259     switch (gameMode) {
14260       case MachinePlaysBlack:
14261       case MachinePlaysWhite:
14262       case BeginningOfGame:
14263         if (pausing) return;
14264         break;
14265
14266       case EditGame:
14267       case PlayFromGameFile:
14268       case IcsExamining:
14269         return;
14270
14271       default:
14272         break;
14273     }
14274
14275     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14276         if(WhiteOnMove(forwardMostMove))
14277              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14278         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14279     }
14280
14281     tickStartTM = now;
14282     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14283       whiteTimeRemaining : blackTimeRemaining);
14284     StartClockTimer(intendedTickLength);
14285 }
14286         
14287
14288 /* Stop both clocks */
14289 void
14290 StopClocks()
14291 {       
14292     long lastTickLength;
14293     TimeMark now;
14294
14295     if (!StopClockTimer()) return;
14296     if (!appData.clockMode) return;
14297
14298     GetTimeMark(&now);
14299
14300     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14301     if (WhiteOnMove(forwardMostMove)) {
14302         if(whiteNPS >= 0) lastTickLength = 0;
14303         whiteTimeRemaining -= lastTickLength;
14304         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14305     } else {
14306         if(blackNPS >= 0) lastTickLength = 0;
14307         blackTimeRemaining -= lastTickLength;
14308         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14309     }
14310     CheckFlags();
14311 }
14312         
14313 /* Start clock of player on move.  Time may have been reset, so
14314    if clock is already running, stop and restart it. */
14315 void
14316 StartClocks()
14317 {
14318     (void) StopClockTimer(); /* in case it was running already */
14319     DisplayBothClocks();
14320     if (CheckFlags()) return;
14321
14322     if (!appData.clockMode) return;
14323     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14324
14325     GetTimeMark(&tickStartTM);
14326     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14327       whiteTimeRemaining : blackTimeRemaining);
14328
14329    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14330     whiteNPS = blackNPS = -1; 
14331     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14332        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14333         whiteNPS = first.nps;
14334     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14335        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14336         blackNPS = first.nps;
14337     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14338         whiteNPS = second.nps;
14339     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14340         blackNPS = second.nps;
14341     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14342
14343     StartClockTimer(intendedTickLength);
14344 }
14345
14346 char *
14347 TimeString(ms)
14348      long ms;
14349 {
14350     long second, minute, hour, day;
14351     char *sign = "";
14352     static char buf[32];
14353     
14354     if (ms > 0 && ms <= 9900) {
14355       /* convert milliseconds to tenths, rounding up */
14356       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14357
14358       sprintf(buf, " %03.1f ", tenths/10.0);
14359       return buf;
14360     }
14361
14362     /* convert milliseconds to seconds, rounding up */
14363     /* use floating point to avoid strangeness of integer division
14364        with negative dividends on many machines */
14365     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14366
14367     if (second < 0) {
14368         sign = "-";
14369         second = -second;
14370     }
14371     
14372     day = second / (60 * 60 * 24);
14373     second = second % (60 * 60 * 24);
14374     hour = second / (60 * 60);
14375     second = second % (60 * 60);
14376     minute = second / 60;
14377     second = second % 60;
14378     
14379     if (day > 0)
14380       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14381               sign, day, hour, minute, second);
14382     else if (hour > 0)
14383       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14384     else
14385       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14386     
14387     return buf;
14388 }
14389
14390
14391 /*
14392  * This is necessary because some C libraries aren't ANSI C compliant yet.
14393  */
14394 char *
14395 StrStr(string, match)
14396      char *string, *match;
14397 {
14398     int i, length;
14399     
14400     length = strlen(match);
14401     
14402     for (i = strlen(string) - length; i >= 0; i--, string++)
14403       if (!strncmp(match, string, length))
14404         return string;
14405     
14406     return NULL;
14407 }
14408
14409 char *
14410 StrCaseStr(string, match)
14411      char *string, *match;
14412 {
14413     int i, j, length;
14414     
14415     length = strlen(match);
14416     
14417     for (i = strlen(string) - length; i >= 0; i--, string++) {
14418         for (j = 0; j < length; j++) {
14419             if (ToLower(match[j]) != ToLower(string[j]))
14420               break;
14421         }
14422         if (j == length) return string;
14423     }
14424
14425     return NULL;
14426 }
14427
14428 #ifndef _amigados
14429 int
14430 StrCaseCmp(s1, s2)
14431      char *s1, *s2;
14432 {
14433     char c1, c2;
14434     
14435     for (;;) {
14436         c1 = ToLower(*s1++);
14437         c2 = ToLower(*s2++);
14438         if (c1 > c2) return 1;
14439         if (c1 < c2) return -1;
14440         if (c1 == NULLCHAR) return 0;
14441     }
14442 }
14443
14444
14445 int
14446 ToLower(c)
14447      int c;
14448 {
14449     return isupper(c) ? tolower(c) : c;
14450 }
14451
14452
14453 int
14454 ToUpper(c)
14455      int c;
14456 {
14457     return islower(c) ? toupper(c) : c;
14458 }
14459 #endif /* !_amigados    */
14460
14461 char *
14462 StrSave(s)
14463      char *s;
14464 {
14465     char *ret;
14466
14467     if ((ret = (char *) malloc(strlen(s) + 1))) {
14468         strcpy(ret, s);
14469     }
14470     return ret;
14471 }
14472
14473 char *
14474 StrSavePtr(s, savePtr)
14475      char *s, **savePtr;
14476 {
14477     if (*savePtr) {
14478         free(*savePtr);
14479     }
14480     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14481         strcpy(*savePtr, s);
14482     }
14483     return(*savePtr);
14484 }
14485
14486 char *
14487 PGNDate()
14488 {
14489     time_t clock;
14490     struct tm *tm;
14491     char buf[MSG_SIZ];
14492
14493     clock = time((time_t *)NULL);
14494     tm = localtime(&clock);
14495     sprintf(buf, "%04d.%02d.%02d",
14496             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14497     return StrSave(buf);
14498 }
14499
14500
14501 char *
14502 PositionToFEN(move, overrideCastling)
14503      int move;
14504      char *overrideCastling;
14505 {
14506     int i, j, fromX, fromY, toX, toY;
14507     int whiteToPlay;
14508     char buf[128];
14509     char *p, *q;
14510     int emptycount;
14511     ChessSquare piece;
14512
14513     whiteToPlay = (gameMode == EditPosition) ?
14514       !blackPlaysFirst : (move % 2 == 0);
14515     p = buf;
14516
14517     /* Piece placement data */
14518     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14519         emptycount = 0;
14520         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14521             if (boards[move][i][j] == EmptySquare) {
14522                 emptycount++;
14523             } else { ChessSquare piece = boards[move][i][j];
14524                 if (emptycount > 0) {
14525                     if(emptycount<10) /* [HGM] can be >= 10 */
14526                         *p++ = '0' + emptycount;
14527                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14528                     emptycount = 0;
14529                 }
14530                 if(PieceToChar(piece) == '+') {
14531                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14532                     *p++ = '+';
14533                     piece = (ChessSquare)(DEMOTED piece);
14534                 } 
14535                 *p++ = PieceToChar(piece);
14536                 if(p[-1] == '~') {
14537                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14538                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14539                     *p++ = '~';
14540                 }
14541             }
14542         }
14543         if (emptycount > 0) {
14544             if(emptycount<10) /* [HGM] can be >= 10 */
14545                 *p++ = '0' + emptycount;
14546             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14547             emptycount = 0;
14548         }
14549         *p++ = '/';
14550     }
14551     *(p - 1) = ' ';
14552
14553     /* [HGM] print Crazyhouse or Shogi holdings */
14554     if( gameInfo.holdingsWidth ) {
14555         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14556         q = p;
14557         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14558             piece = boards[move][i][BOARD_WIDTH-1];
14559             if( piece != EmptySquare )
14560               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14561                   *p++ = PieceToChar(piece);
14562         }
14563         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14564             piece = boards[move][BOARD_HEIGHT-i-1][0];
14565             if( piece != EmptySquare )
14566               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14567                   *p++ = PieceToChar(piece);
14568         }
14569
14570         if( q == p ) *p++ = '-';
14571         *p++ = ']';
14572         *p++ = ' ';
14573     }
14574
14575     /* Active color */
14576     *p++ = whiteToPlay ? 'w' : 'b';
14577     *p++ = ' ';
14578
14579   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14580     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14581   } else {
14582   if(nrCastlingRights) {
14583      q = p;
14584      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14585        /* [HGM] write directly from rights */
14586            if(boards[move][CASTLING][2] != NoRights &&
14587               boards[move][CASTLING][0] != NoRights   )
14588                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14589            if(boards[move][CASTLING][2] != NoRights &&
14590               boards[move][CASTLING][1] != NoRights   )
14591                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14592            if(boards[move][CASTLING][5] != NoRights &&
14593               boards[move][CASTLING][3] != NoRights   )
14594                 *p++ = boards[move][CASTLING][3] + AAA;
14595            if(boards[move][CASTLING][5] != NoRights &&
14596               boards[move][CASTLING][4] != NoRights   )
14597                 *p++ = boards[move][CASTLING][4] + AAA;
14598      } else {
14599
14600         /* [HGM] write true castling rights */
14601         if( nrCastlingRights == 6 ) {
14602             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14603                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14604             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14605                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14606             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14607                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14608             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14609                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14610         }
14611      }
14612      if (q == p) *p++ = '-'; /* No castling rights */
14613      *p++ = ' ';
14614   }
14615
14616   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14617      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14618     /* En passant target square */
14619     if (move > backwardMostMove) {
14620         fromX = moveList[move - 1][0] - AAA;
14621         fromY = moveList[move - 1][1] - ONE;
14622         toX = moveList[move - 1][2] - AAA;
14623         toY = moveList[move - 1][3] - ONE;
14624         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14625             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14626             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14627             fromX == toX) {
14628             /* 2-square pawn move just happened */
14629             *p++ = toX + AAA;
14630             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14631         } else {
14632             *p++ = '-';
14633         }
14634     } else if(move == backwardMostMove) {
14635         // [HGM] perhaps we should always do it like this, and forget the above?
14636         if((signed char)boards[move][EP_STATUS] >= 0) {
14637             *p++ = boards[move][EP_STATUS] + AAA;
14638             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14639         } else {
14640             *p++ = '-';
14641         }
14642     } else {
14643         *p++ = '-';
14644     }
14645     *p++ = ' ';
14646   }
14647   }
14648
14649     /* [HGM] find reversible plies */
14650     {   int i = 0, j=move;
14651
14652         if (appData.debugMode) { int k;
14653             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14654             for(k=backwardMostMove; k<=forwardMostMove; k++)
14655                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14656
14657         }
14658
14659         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14660         if( j == backwardMostMove ) i += initialRulePlies;
14661         sprintf(p, "%d ", i);
14662         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14663     }
14664     /* Fullmove number */
14665     sprintf(p, "%d", (move / 2) + 1);
14666     
14667     return StrSave(buf);
14668 }
14669
14670 Boolean
14671 ParseFEN(board, blackPlaysFirst, fen)
14672     Board board;
14673      int *blackPlaysFirst;
14674      char *fen;
14675 {
14676     int i, j;
14677     char *p;
14678     int emptycount;
14679     ChessSquare piece;
14680
14681     p = fen;
14682
14683     /* [HGM] by default clear Crazyhouse holdings, if present */
14684     if(gameInfo.holdingsWidth) {
14685        for(i=0; i<BOARD_HEIGHT; i++) {
14686            board[i][0]             = EmptySquare; /* black holdings */
14687            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14688            board[i][1]             = (ChessSquare) 0; /* black counts */
14689            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14690        }
14691     }
14692
14693     /* Piece placement data */
14694     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14695         j = 0;
14696         for (;;) {
14697             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14698                 if (*p == '/') p++;
14699                 emptycount = gameInfo.boardWidth - j;
14700                 while (emptycount--)
14701                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14702                 break;
14703 #if(BOARD_FILES >= 10)
14704             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14705                 p++; emptycount=10;
14706                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14707                 while (emptycount--)
14708                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14709 #endif
14710             } else if (isdigit(*p)) {
14711                 emptycount = *p++ - '0';
14712                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14713                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14714                 while (emptycount--)
14715                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14716             } else if (*p == '+' || isalpha(*p)) {
14717                 if (j >= gameInfo.boardWidth) return FALSE;
14718                 if(*p=='+') {
14719                     piece = CharToPiece(*++p);
14720                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14721                     piece = (ChessSquare) (PROMOTED piece ); p++;
14722                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14723                 } else piece = CharToPiece(*p++);
14724
14725                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14726                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14727                     piece = (ChessSquare) (PROMOTED piece);
14728                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14729                     p++;
14730                 }
14731                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14732             } else {
14733                 return FALSE;
14734             }
14735         }
14736     }
14737     while (*p == '/' || *p == ' ') p++;
14738
14739     /* [HGM] look for Crazyhouse holdings here */
14740     while(*p==' ') p++;
14741     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14742         if(*p == '[') p++;
14743         if(*p == '-' ) *p++; /* empty holdings */ else {
14744             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14745             /* if we would allow FEN reading to set board size, we would   */
14746             /* have to add holdings and shift the board read so far here   */
14747             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14748                 *p++;
14749                 if((int) piece >= (int) BlackPawn ) {
14750                     i = (int)piece - (int)BlackPawn;
14751                     i = PieceToNumber((ChessSquare)i);
14752                     if( i >= gameInfo.holdingsSize ) return FALSE;
14753                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14754                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14755                 } else {
14756                     i = (int)piece - (int)WhitePawn;
14757                     i = PieceToNumber((ChessSquare)i);
14758                     if( i >= gameInfo.holdingsSize ) return FALSE;
14759                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14760                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14761                 }
14762             }
14763         }
14764         if(*p == ']') *p++;
14765     }
14766
14767     while(*p == ' ') p++;
14768
14769     /* Active color */
14770     switch (*p++) {
14771       case 'w':
14772         *blackPlaysFirst = FALSE;
14773         break;
14774       case 'b': 
14775         *blackPlaysFirst = TRUE;
14776         break;
14777       default:
14778         return FALSE;
14779     }
14780
14781     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14782     /* return the extra info in global variiables             */
14783
14784     /* set defaults in case FEN is incomplete */
14785     board[EP_STATUS] = EP_UNKNOWN;
14786     for(i=0; i<nrCastlingRights; i++ ) {
14787         board[CASTLING][i] =
14788             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14789     }   /* assume possible unless obviously impossible */
14790     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14791     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14792     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14793                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14794     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14795     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14796     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14797                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14798     FENrulePlies = 0;
14799
14800     while(*p==' ') p++;
14801     if(nrCastlingRights) {
14802       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14803           /* castling indicator present, so default becomes no castlings */
14804           for(i=0; i<nrCastlingRights; i++ ) {
14805                  board[CASTLING][i] = NoRights;
14806           }
14807       }
14808       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14809              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14810              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14811              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14812         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14813
14814         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14815             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14816             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14817         }
14818         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14819             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14820         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14821                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14822         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14823                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14824         switch(c) {
14825           case'K':
14826               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14827               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14828               board[CASTLING][2] = whiteKingFile;
14829               break;
14830           case'Q':
14831               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14832               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14833               board[CASTLING][2] = whiteKingFile;
14834               break;
14835           case'k':
14836               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14837               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14838               board[CASTLING][5] = blackKingFile;
14839               break;
14840           case'q':
14841               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14842               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14843               board[CASTLING][5] = blackKingFile;
14844           case '-':
14845               break;
14846           default: /* FRC castlings */
14847               if(c >= 'a') { /* black rights */
14848                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14849                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14850                   if(i == BOARD_RGHT) break;
14851                   board[CASTLING][5] = i;
14852                   c -= AAA;
14853                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14854                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14855                   if(c > i)
14856                       board[CASTLING][3] = c;
14857                   else
14858                       board[CASTLING][4] = c;
14859               } else { /* white rights */
14860                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14861                     if(board[0][i] == WhiteKing) break;
14862                   if(i == BOARD_RGHT) break;
14863                   board[CASTLING][2] = i;
14864                   c -= AAA - 'a' + 'A';
14865                   if(board[0][c] >= WhiteKing) break;
14866                   if(c > i)
14867                       board[CASTLING][0] = c;
14868                   else
14869                       board[CASTLING][1] = c;
14870               }
14871         }
14872       }
14873       for(i=0; i<nrCastlingRights; i++)
14874         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14875     if (appData.debugMode) {
14876         fprintf(debugFP, "FEN castling rights:");
14877         for(i=0; i<nrCastlingRights; i++)
14878         fprintf(debugFP, " %d", board[CASTLING][i]);
14879         fprintf(debugFP, "\n");
14880     }
14881
14882       while(*p==' ') p++;
14883     }
14884
14885     /* read e.p. field in games that know e.p. capture */
14886     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14887        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14888       if(*p=='-') {
14889         p++; board[EP_STATUS] = EP_NONE;
14890       } else {
14891          char c = *p++ - AAA;
14892
14893          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14894          if(*p >= '0' && *p <='9') *p++;
14895          board[EP_STATUS] = c;
14896       }
14897     }
14898
14899
14900     if(sscanf(p, "%d", &i) == 1) {
14901         FENrulePlies = i; /* 50-move ply counter */
14902         /* (The move number is still ignored)    */
14903     }
14904
14905     return TRUE;
14906 }
14907       
14908 void
14909 EditPositionPasteFEN(char *fen)
14910 {
14911   if (fen != NULL) {
14912     Board initial_position;
14913
14914     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14915       DisplayError(_("Bad FEN position in clipboard"), 0);
14916       return ;
14917     } else {
14918       int savedBlackPlaysFirst = blackPlaysFirst;
14919       EditPositionEvent();
14920       blackPlaysFirst = savedBlackPlaysFirst;
14921       CopyBoard(boards[0], initial_position);
14922       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14923       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14924       DisplayBothClocks();
14925       DrawPosition(FALSE, boards[currentMove]);
14926     }
14927   }
14928 }
14929
14930 static char cseq[12] = "\\   ";
14931
14932 Boolean set_cont_sequence(char *new_seq)
14933 {
14934     int len;
14935     Boolean ret;
14936
14937     // handle bad attempts to set the sequence
14938         if (!new_seq)
14939                 return 0; // acceptable error - no debug
14940
14941     len = strlen(new_seq);
14942     ret = (len > 0) && (len < sizeof(cseq));
14943     if (ret)
14944         strcpy(cseq, new_seq);
14945     else if (appData.debugMode)
14946         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14947     return ret;
14948 }
14949
14950 /*
14951     reformat a source message so words don't cross the width boundary.  internal
14952     newlines are not removed.  returns the wrapped size (no null character unless
14953     included in source message).  If dest is NULL, only calculate the size required
14954     for the dest buffer.  lp argument indicats line position upon entry, and it's
14955     passed back upon exit.
14956 */
14957 int wrap(char *dest, char *src, int count, int width, int *lp)
14958 {
14959     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14960
14961     cseq_len = strlen(cseq);
14962     old_line = line = *lp;
14963     ansi = len = clen = 0;
14964
14965     for (i=0; i < count; i++)
14966     {
14967         if (src[i] == '\033')
14968             ansi = 1;
14969
14970         // if we hit the width, back up
14971         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14972         {
14973             // store i & len in case the word is too long
14974             old_i = i, old_len = len;
14975
14976             // find the end of the last word
14977             while (i && src[i] != ' ' && src[i] != '\n')
14978             {
14979                 i--;
14980                 len--;
14981             }
14982
14983             // word too long?  restore i & len before splitting it
14984             if ((old_i-i+clen) >= width)
14985             {
14986                 i = old_i;
14987                 len = old_len;
14988             }
14989
14990             // extra space?
14991             if (i && src[i-1] == ' ')
14992                 len--;
14993
14994             if (src[i] != ' ' && src[i] != '\n')
14995             {
14996                 i--;
14997                 if (len)
14998                     len--;
14999             }
15000
15001             // now append the newline and continuation sequence
15002             if (dest)
15003                 dest[len] = '\n';
15004             len++;
15005             if (dest)
15006                 strncpy(dest+len, cseq, cseq_len);
15007             len += cseq_len;
15008             line = cseq_len;
15009             clen = cseq_len;
15010             continue;
15011         }
15012
15013         if (dest)
15014             dest[len] = src[i];
15015         len++;
15016         if (!ansi)
15017             line++;
15018         if (src[i] == '\n')
15019             line = 0;
15020         if (src[i] == 'm')
15021             ansi = 0;
15022     }
15023     if (dest && appData.debugMode)
15024     {
15025         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15026             count, width, line, len, *lp);
15027         show_bytes(debugFP, src, count);
15028         fprintf(debugFP, "\ndest: ");
15029         show_bytes(debugFP, dest, len);
15030         fprintf(debugFP, "\n");
15031     }
15032     *lp = dest ? line : old_line;
15033
15034     return len;
15035 }
15036
15037 // [HGM] vari: routines for shelving variations
15038
15039 void 
15040 PushTail(int firstMove, int lastMove)
15041 {
15042         int i, j, nrMoves = lastMove - firstMove;
15043
15044         if(appData.icsActive) { // only in local mode
15045                 forwardMostMove = currentMove; // mimic old ICS behavior
15046                 return;
15047         }
15048         if(storedGames >= MAX_VARIATIONS-1) return;
15049
15050         // push current tail of game on stack
15051         savedResult[storedGames] = gameInfo.result;
15052         savedDetails[storedGames] = gameInfo.resultDetails;
15053         gameInfo.resultDetails = NULL;
15054         savedFirst[storedGames] = firstMove;
15055         savedLast [storedGames] = lastMove;
15056         savedFramePtr[storedGames] = framePtr;
15057         framePtr -= nrMoves; // reserve space for the boards
15058         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15059             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15060             for(j=0; j<MOVE_LEN; j++)
15061                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15062             for(j=0; j<2*MOVE_LEN; j++)
15063                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15064             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15065             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15066             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15067             pvInfoList[firstMove+i-1].depth = 0;
15068             commentList[framePtr+i] = commentList[firstMove+i];
15069             commentList[firstMove+i] = NULL;
15070         }
15071
15072         storedGames++;
15073         forwardMostMove = firstMove; // truncate game so we can start variation
15074         if(storedGames == 1) GreyRevert(FALSE);
15075 }
15076
15077 Boolean
15078 PopTail(Boolean annotate)
15079 {
15080         int i, j, nrMoves;
15081         char buf[8000], moveBuf[20];
15082
15083         if(appData.icsActive) return FALSE; // only in local mode
15084         if(!storedGames) return FALSE; // sanity
15085         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15086
15087         storedGames--;
15088         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15089         nrMoves = savedLast[storedGames] - currentMove;
15090         if(annotate) {
15091                 int cnt = 10;
15092                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15093                 else strcpy(buf, "(");
15094                 for(i=currentMove; i<forwardMostMove; i++) {
15095                         if(WhiteOnMove(i))
15096                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15097                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15098                         strcat(buf, moveBuf);
15099                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15100                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15101                 }
15102                 strcat(buf, ")");
15103         }
15104         for(i=1; i<=nrMoves; i++) { // copy last variation back
15105             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15106             for(j=0; j<MOVE_LEN; j++)
15107                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15108             for(j=0; j<2*MOVE_LEN; j++)
15109                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15110             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15111             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15112             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15113             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15114             commentList[currentMove+i] = commentList[framePtr+i];
15115             commentList[framePtr+i] = NULL;
15116         }
15117         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15118         framePtr = savedFramePtr[storedGames];
15119         gameInfo.result = savedResult[storedGames];
15120         if(gameInfo.resultDetails != NULL) {
15121             free(gameInfo.resultDetails);
15122       }
15123         gameInfo.resultDetails = savedDetails[storedGames];
15124         forwardMostMove = currentMove + nrMoves;
15125         if(storedGames == 0) GreyRevert(TRUE);
15126         return TRUE;
15127 }
15128
15129 void 
15130 CleanupTail()
15131 {       // remove all shelved variations
15132         int i;
15133         for(i=0; i<storedGames; i++) {
15134             if(savedDetails[i])
15135                 free(savedDetails[i]);
15136             savedDetails[i] = NULL;
15137         }
15138         for(i=framePtr; i<MAX_MOVES; i++) {
15139                 if(commentList[i]) free(commentList[i]);
15140                 commentList[i] = NULL;
15141         }
15142         framePtr = MAX_MOVES-1;
15143         storedGames = 0;
15144 }
15145
15146 void
15147 LoadVariation(int index, char *text)
15148 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15149         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15150         int level = 0, move;
15151
15152         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15153         // first find outermost bracketing variation
15154         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15155             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15156                 if(*p == '{') wait = '}'; else
15157                 if(*p == '[') wait = ']'; else
15158                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15159                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15160             }
15161             if(*p == wait) wait = NULLCHAR; // closing ]} found
15162             p++;
15163         }
15164         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15165         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15166         end[1] = NULLCHAR; // clip off comment beyond variation
15167         ToNrEvent(currentMove-1);
15168         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15169         // kludge: use ParsePV() to append variation to game
15170         move = currentMove;
15171         ParsePV(start, TRUE);
15172         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15173         ClearPremoveHighlights();
15174         CommentPopDown();
15175         ToNrEvent(currentMove+1);
15176 }