7672207afe4c942a156151048f99e6c5065c79ac
[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 int partnerHighlight[2];
247 Boolean partnerBoardValid = 0;
248 char partnerStatus[MSG_SIZ];
249 Boolean partnerUp;
250 Boolean originalFlip;
251 Boolean twoBoards = 0;
252 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
253 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
254 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
255 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
256 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
257 int opponentKibitzes;
258 int lastSavedGame; /* [HGM] save: ID of game */
259 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
260 extern int chatCount;
261 int chattingPartner;
262 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
263
264 /* States for ics_getting_history */
265 #define H_FALSE 0
266 #define H_REQUESTED 1
267 #define H_GOT_REQ_HEADER 2
268 #define H_GOT_UNREQ_HEADER 3
269 #define H_GETTING_MOVES 4
270 #define H_GOT_UNWANTED_HEADER 5
271
272 /* whosays values for GameEnds */
273 #define GE_ICS 0
274 #define GE_ENGINE 1
275 #define GE_PLAYER 2
276 #define GE_FILE 3
277 #define GE_XBOARD 4
278 #define GE_ENGINE1 5
279 #define GE_ENGINE2 6
280
281 /* Maximum number of games in a cmail message */
282 #define CMAIL_MAX_GAMES 20
283
284 /* Different types of move when calling RegisterMove */
285 #define CMAIL_MOVE   0
286 #define CMAIL_RESIGN 1
287 #define CMAIL_DRAW   2
288 #define CMAIL_ACCEPT 3
289
290 /* Different types of result to remember for each game */
291 #define CMAIL_NOT_RESULT 0
292 #define CMAIL_OLD_RESULT 1
293 #define CMAIL_NEW_RESULT 2
294
295 /* Telnet protocol constants */
296 #define TN_WILL 0373
297 #define TN_WONT 0374
298 #define TN_DO   0375
299 #define TN_DONT 0376
300 #define TN_IAC  0377
301 #define TN_ECHO 0001
302 #define TN_SGA  0003
303 #define TN_PORT 23
304
305 /* [AS] */
306 static char * safeStrCpy( char * dst, const char * src, size_t count )
307 {
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     strncpy( dst, src, count );
313     dst[ count-1 ] = '\0';
314     return dst;
315 }
316
317 /* Some compiler can't cast u64 to double
318  * This function do the job for us:
319
320  * We use the highest bit for cast, this only
321  * works if the highest bit is not
322  * in use (This should not happen)
323  *
324  * We used this for all compiler
325  */
326 double
327 u64ToDouble(u64 value)
328 {
329   double r;
330   u64 tmp = value & u64Const(0x7fffffffffffffff);
331   r = (double)(s64)tmp;
332   if (value & u64Const(0x8000000000000000))
333        r +=  9.2233720368547758080e18; /* 2^63 */
334  return r;
335 }
336
337 /* Fake up flags for now, as we aren't keeping track of castling
338    availability yet. [HGM] Change of logic: the flag now only
339    indicates the type of castlings allowed by the rule of the game.
340    The actual rights themselves are maintained in the array
341    castlingRights, as part of the game history, and are not probed
342    by this function.
343  */
344 int
345 PosFlags(index)
346 {
347   int flags = F_ALL_CASTLE_OK;
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
349   switch (gameInfo.variant) {
350   case VariantSuicide:
351     flags &= ~F_ALL_CASTLE_OK;
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
353     flags |= F_IGNORE_CHECK;
354   case VariantLosers:
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
356     break;
357   case VariantAtomic:
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359     break;
360   case VariantKriegspiel:
361     flags |= F_KRIEGSPIEL_CAPTURE;
362     break;
363   case VariantCapaRandom: 
364   case VariantFischeRandom:
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
366   case VariantNoCastle:
367   case VariantShatranj:
368   case VariantCourier:
369   case VariantMakruk:
370     flags &= ~F_ALL_CASTLE_OK;
371     break;
372   default:
373     break;
374   }
375   return flags;
376 }
377
378 FILE *gameFileFP, *debugFP;
379
380 /* 
381     [AS] Note: sometimes, the sscanf() function is used to parse the input
382     into a fixed-size buffer. Because of this, we must be prepared to
383     receive strings as long as the size of the input buffer, which is currently
384     set to 4K for Windows and 8K for the rest.
385     So, we must either allocate sufficiently large buffers here, or
386     reduce the size of the input buffer in the input reading part.
387 */
388
389 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
390 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
391 char thinkOutput1[MSG_SIZ*10];
392
393 ChessProgramState first, second;
394
395 /* premove variables */
396 int premoveToX = 0;
397 int premoveToY = 0;
398 int premoveFromX = 0;
399 int premoveFromY = 0;
400 int premovePromoChar = 0;
401 int gotPremove = 0;
402 Boolean alarmSounded;
403 /* end premove variables */
404
405 char *ics_prefix = "$";
406 int ics_type = ICS_GENERIC;
407
408 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
409 int pauseExamForwardMostMove = 0;
410 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
411 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
412 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
413 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
414 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
415 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
416 int whiteFlag = FALSE, blackFlag = FALSE;
417 int userOfferedDraw = FALSE;
418 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
419 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
420 int cmailMoveType[CMAIL_MAX_GAMES];
421 long ics_clock_paused = 0;
422 ProcRef icsPR = NoProc, cmailPR = NoProc;
423 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
424 GameMode gameMode = BeginningOfGame;
425 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
426 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
427 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
428 int hiddenThinkOutputState = 0; /* [AS] */
429 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
430 int adjudicateLossPlies = 6;
431 char white_holding[64], black_holding[64];
432 TimeMark lastNodeCountTime;
433 long lastNodeCount=0;
434 int have_sent_ICS_logon = 0;
435 int movesPerSession;
436 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
437 long timeControl_2; /* [AS] Allow separate time controls */
438 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
439 long timeRemaining[2][MAX_MOVES];
440 int matchGame = 0;
441 TimeMark programStartTime;
442 char ics_handle[MSG_SIZ];
443 int have_set_title = 0;
444
445 /* animateTraining preserves the state of appData.animate
446  * when Training mode is activated. This allows the
447  * response to be animated when appData.animate == TRUE and
448  * appData.animateDragging == TRUE.
449  */
450 Boolean animateTraining;
451
452 GameInfo gameInfo;
453
454 AppData appData;
455
456 Board boards[MAX_MOVES];
457 /* [HGM] Following 7 needed for accurate legality tests: */
458 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
459 signed char  initialRights[BOARD_FILES];
460 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
461 int   initialRulePlies, FENrulePlies;
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
463 int loadFlag = 0; 
464 int shuffleOpenings;
465 int mute; // mute all sounds
466
467 // [HGM] vari: next 12 to save and restore variations
468 #define MAX_VARIATIONS 10
469 int framePtr = MAX_MOVES-1; // points to free stack entry
470 int storedGames = 0;
471 int savedFirst[MAX_VARIATIONS];
472 int savedLast[MAX_VARIATIONS];
473 int savedFramePtr[MAX_VARIATIONS];
474 char *savedDetails[MAX_VARIATIONS];
475 ChessMove savedResult[MAX_VARIATIONS];
476
477 void PushTail P((int firstMove, int lastMove));
478 Boolean PopTail P((Boolean annotate));
479 void CleanupTail P((void));
480
481 ChessSquare  FIDEArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackBishop, BlackKnight, BlackRook }
486 };
487
488 ChessSquare twoKingsArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
490         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
491     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
492         BlackKing, BlackKing, BlackKnight, BlackRook }
493 };
494
495 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
496     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
497         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
498     { BlackRook, BlackMan, BlackBishop, BlackQueen,
499         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
500 };
501
502 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
503     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
504         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
505     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
506         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
507 };
508
509 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
511         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
513         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
514 };
515
516 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
517     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
518         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackMan, BlackFerz,
520         BlackKing, BlackMan, BlackKnight, BlackRook }
521 };
522
523
524 #if (BOARD_FILES>=10)
525 ChessSquare ShogiArray[2][BOARD_FILES] = {
526     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
527         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
528     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
529         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
530 };
531
532 ChessSquare XiangqiArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
534         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
536         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare CapablancaArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
541         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
543         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
544 };
545
546 ChessSquare GreatArray[2][BOARD_FILES] = {
547     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
548         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
549     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
550         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
551 };
552
553 ChessSquare JanusArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
555         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
556     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
557         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
558 };
559
560 #ifdef GOTHIC
561 ChessSquare GothicArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
563         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
565         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
566 };
567 #else // !GOTHIC
568 #define GothicArray CapablancaArray
569 #endif // !GOTHIC
570
571 #ifdef FALCON
572 ChessSquare FalconArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
574         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
576         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
577 };
578 #else // !FALCON
579 #define FalconArray CapablancaArray
580 #endif // !FALCON
581
582 #else // !(BOARD_FILES>=10)
583 #define XiangqiPosition FIDEArray
584 #define CapablancaArray FIDEArray
585 #define GothicArray FIDEArray
586 #define GreatArray FIDEArray
587 #endif // !(BOARD_FILES>=10)
588
589 #if (BOARD_FILES>=12)
590 ChessSquare CourierArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
592         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
593     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
594         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
595 };
596 #else // !(BOARD_FILES>=12)
597 #define CourierArray CapablancaArray
598 #endif // !(BOARD_FILES>=12)
599
600
601 Board initialPosition;
602
603
604 /* Convert str to a rating. Checks for special cases of "----",
605
606    "++++", etc. Also strips ()'s */
607 int
608 string_to_rating(str)
609   char *str;
610 {
611   while(*str && !isdigit(*str)) ++str;
612   if (!*str)
613     return 0;   /* One of the special "no rating" cases */
614   else
615     return atoi(str);
616 }
617
618 void
619 ClearProgramStats()
620 {
621     /* Init programStats */
622     programStats.movelist[0] = 0;
623     programStats.depth = 0;
624     programStats.nr_moves = 0;
625     programStats.moves_left = 0;
626     programStats.nodes = 0;
627     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
628     programStats.score = 0;
629     programStats.got_only_move = 0;
630     programStats.got_fail = 0;
631     programStats.line_is_book = 0;
632 }
633
634 void
635 InitBackEnd1()
636 {
637     int matched, min, sec;
638
639     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
640
641     GetTimeMark(&programStartTime);
642     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
643
644     ClearProgramStats();
645     programStats.ok_to_send = 1;
646     programStats.seen_stat = 0;
647
648     /*
649      * Initialize game list
650      */
651     ListNew(&gameList);
652
653
654     /*
655      * Internet chess server status
656      */
657     if (appData.icsActive) {
658         appData.matchMode = FALSE;
659         appData.matchGames = 0;
660 #if ZIPPY       
661         appData.noChessProgram = !appData.zippyPlay;
662 #else
663         appData.zippyPlay = FALSE;
664         appData.zippyTalk = FALSE;
665         appData.noChessProgram = TRUE;
666 #endif
667         if (*appData.icsHelper != NULLCHAR) {
668             appData.useTelnet = TRUE;
669             appData.telnetProgram = appData.icsHelper;
670         }
671     } else {
672         appData.zippyTalk = appData.zippyPlay = FALSE;
673     }
674
675     /* [AS] Initialize pv info list [HGM] and game state */
676     {
677         int i, j;
678
679         for( i=0; i<=framePtr; i++ ) {
680             pvInfoList[i].depth = -1;
681             boards[i][EP_STATUS] = EP_NONE;
682             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
683         }
684     }
685
686     /*
687      * Parse timeControl resource
688      */
689     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
690                           appData.movesPerSession)) {
691         char buf[MSG_SIZ];
692         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
693         DisplayFatalError(buf, 0, 2);
694     }
695
696     /*
697      * Parse searchTime resource
698      */
699     if (*appData.searchTime != NULLCHAR) {
700         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
701         if (matched == 1) {
702             searchTime = min * 60;
703         } else if (matched == 2) {
704             searchTime = min * 60 + sec;
705         } else {
706             char buf[MSG_SIZ];
707             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
708             DisplayFatalError(buf, 0, 2);
709         }
710     }
711
712     /* [AS] Adjudication threshold */
713     adjudicateLossThreshold = appData.adjudicateLossThreshold;
714     
715     first.which = "first";
716     second.which = "second";
717     first.maybeThinking = second.maybeThinking = FALSE;
718     first.pr = second.pr = NoProc;
719     first.isr = second.isr = NULL;
720     first.sendTime = second.sendTime = 2;
721     first.sendDrawOffers = 1;
722     if (appData.firstPlaysBlack) {
723         first.twoMachinesColor = "black\n";
724         second.twoMachinesColor = "white\n";
725     } else {
726         first.twoMachinesColor = "white\n";
727         second.twoMachinesColor = "black\n";
728     }
729     first.program = appData.firstChessProgram;
730     second.program = appData.secondChessProgram;
731     first.host = appData.firstHost;
732     second.host = appData.secondHost;
733     first.dir = appData.firstDirectory;
734     second.dir = appData.secondDirectory;
735     first.other = &second;
736     second.other = &first;
737     first.initString = appData.initString;
738     second.initString = appData.secondInitString;
739     first.computerString = appData.firstComputerString;
740     second.computerString = appData.secondComputerString;
741     first.useSigint = second.useSigint = TRUE;
742     first.useSigterm = second.useSigterm = TRUE;
743     first.reuse = appData.reuseFirst;
744     second.reuse = appData.reuseSecond;
745     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
746     second.nps = appData.secondNPS;
747     first.useSetboard = second.useSetboard = FALSE;
748     first.useSAN = second.useSAN = FALSE;
749     first.usePing = second.usePing = FALSE;
750     first.lastPing = second.lastPing = 0;
751     first.lastPong = second.lastPong = 0;
752     first.usePlayother = second.usePlayother = FALSE;
753     first.useColors = second.useColors = TRUE;
754     first.useUsermove = second.useUsermove = FALSE;
755     first.sendICS = second.sendICS = FALSE;
756     first.sendName = second.sendName = appData.icsActive;
757     first.sdKludge = second.sdKludge = FALSE;
758     first.stKludge = second.stKludge = FALSE;
759     TidyProgramName(first.program, first.host, first.tidy);
760     TidyProgramName(second.program, second.host, second.tidy);
761     first.matchWins = second.matchWins = 0;
762     strcpy(first.variants, appData.variant);
763     strcpy(second.variants, appData.variant);
764     first.analysisSupport = second.analysisSupport = 2; /* detect */
765     first.analyzing = second.analyzing = FALSE;
766     first.initDone = second.initDone = FALSE;
767
768     /* New features added by Tord: */
769     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
770     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
771     /* End of new features added by Tord. */
772     first.fenOverride  = appData.fenOverride1;
773     second.fenOverride = appData.fenOverride2;
774
775     /* [HGM] time odds: set factor for each machine */
776     first.timeOdds  = appData.firstTimeOdds;
777     second.timeOdds = appData.secondTimeOdds;
778     { float norm = 1;
779         if(appData.timeOddsMode) {
780             norm = first.timeOdds;
781             if(norm > second.timeOdds) norm = second.timeOdds;
782         }
783         first.timeOdds /= norm;
784         second.timeOdds /= norm;
785     }
786
787     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
788     first.accumulateTC = appData.firstAccumulateTC;
789     second.accumulateTC = appData.secondAccumulateTC;
790     first.maxNrOfSessions = second.maxNrOfSessions = 1;
791
792     /* [HGM] debug */
793     first.debug = second.debug = FALSE;
794     first.supportsNPS = second.supportsNPS = UNKNOWN;
795
796     /* [HGM] options */
797     first.optionSettings  = appData.firstOptions;
798     second.optionSettings = appData.secondOptions;
799
800     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
801     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
802     first.isUCI = appData.firstIsUCI; /* [AS] */
803     second.isUCI = appData.secondIsUCI; /* [AS] */
804     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
805     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
806
807     if (appData.firstProtocolVersion > PROTOVER ||
808         appData.firstProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.firstProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       first.protocolVersion = appData.firstProtocolVersion;
815     }
816
817     if (appData.secondProtocolVersion > PROTOVER ||
818         appData.secondProtocolVersion < 1) {
819       char buf[MSG_SIZ];
820       sprintf(buf, _("protocol version %d not supported"),
821               appData.secondProtocolVersion);
822       DisplayFatalError(buf, 0, 2);
823     } else {
824       second.protocolVersion = appData.secondProtocolVersion;
825     }
826
827     if (appData.icsActive) {
828         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
829 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
830     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
831         appData.clockMode = FALSE;
832         first.sendTime = second.sendTime = 0;
833     }
834     
835 #if ZIPPY
836     /* Override some settings from environment variables, for backward
837        compatibility.  Unfortunately it's not feasible to have the env
838        vars just set defaults, at least in xboard.  Ugh.
839     */
840     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
841       ZippyInit();
842     }
843 #endif
844     
845     if (appData.noChessProgram) {
846         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
847         sprintf(programVersion, "%s", PACKAGE_STRING);
848     } else {
849       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
850       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
851       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
852     }
853
854     if (!appData.icsActive) {
855       char buf[MSG_SIZ];
856       /* Check for variants that are supported only in ICS mode,
857          or not at all.  Some that are accepted here nevertheless
858          have bugs; see comments below.
859       */
860       VariantClass variant = StringToVariant(appData.variant);
861       switch (variant) {
862       case VariantBughouse:     /* need four players and two boards */
863       case VariantKriegspiel:   /* need to hide pieces and move details */
864       /* case VariantFischeRandom: (Fabien: moved below) */
865         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
866         DisplayFatalError(buf, 0, 2);
867         return;
868
869       case VariantUnknown:
870       case VariantLoadable:
871       case Variant29:
872       case Variant30:
873       case Variant31:
874       case Variant32:
875       case Variant33:
876       case Variant34:
877       case Variant35:
878       case Variant36:
879       default:
880         sprintf(buf, _("Unknown variant name %s"), appData.variant);
881         DisplayFatalError(buf, 0, 2);
882         return;
883
884       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
885       case VariantFairy:      /* [HGM] TestLegality definitely off! */
886       case VariantGothic:     /* [HGM] should work */
887       case VariantCapablanca: /* [HGM] should work */
888       case VariantCourier:    /* [HGM] initial forced moves not implemented */
889       case VariantShogi:      /* [HGM] drops not tested for legality */
890       case VariantKnightmate: /* [HGM] should work */
891       case VariantCylinder:   /* [HGM] untested */
892       case VariantFalcon:     /* [HGM] untested */
893       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
894                                  offboard interposition not understood */
895       case VariantNormal:     /* definitely works! */
896       case VariantWildCastle: /* pieces not automatically shuffled */
897       case VariantNoCastle:   /* pieces not automatically shuffled */
898       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
899       case VariantLosers:     /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantSuicide:    /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantGiveaway:   /* should work except for win condition,
904                                  and doesn't know captures are mandatory */
905       case VariantTwoKings:   /* should work */
906       case VariantAtomic:     /* should work except for win condition */
907       case Variant3Check:     /* should work except for win condition */
908       case VariantShatranj:   /* should work except for all win conditions */
909       case VariantMakruk:     /* should work except for daw countdown */
910       case VariantBerolina:   /* might work if TestLegality is off */
911       case VariantCapaRandom: /* should work */
912       case VariantJanus:      /* should work */
913       case VariantSuper:      /* experimental */
914       case VariantGreat:      /* experimental, requires legality testing to be off */
915         break;
916       }
917     }
918
919     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
920     InitEngineUCI( installDir, &second );
921 }
922
923 int NextIntegerFromString( char ** str, long * value )
924 {
925     int result = -1;
926     char * s = *str;
927
928     while( *s == ' ' || *s == '\t' ) {
929         s++;
930     }
931
932     *value = 0;
933
934     if( *s >= '0' && *s <= '9' ) {
935         while( *s >= '0' && *s <= '9' ) {
936             *value = *value * 10 + (*s - '0');
937             s++;
938         }
939
940         result = 0;
941     }
942
943     *str = s;
944
945     return result;
946 }
947
948 int NextTimeControlFromString( char ** str, long * value )
949 {
950     long temp;
951     int result = NextIntegerFromString( str, &temp );
952
953     if( result == 0 ) {
954         *value = temp * 60; /* Minutes */
955         if( **str == ':' ) {
956             (*str)++;
957             result = NextIntegerFromString( str, &temp );
958             *value += temp; /* Seconds */
959         }
960     }
961
962     return result;
963 }
964
965 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
966 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
967     int result = -1; long temp, temp2;
968
969     if(**str != '+') return -1; // old params remain in force!
970     (*str)++;
971     if( NextTimeControlFromString( str, &temp ) ) return -1;
972
973     if(**str != '/') {
974         /* time only: incremental or sudden-death time control */
975         if(**str == '+') { /* increment follows; read it */
976             (*str)++;
977             if(result = NextIntegerFromString( str, &temp2)) return -1;
978             *inc = temp2 * 1000;
979         } else *inc = 0;
980         *moves = 0; *tc = temp * 1000; 
981         return 0;
982     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
983
984     (*str)++; /* classical time control */
985     result = NextTimeControlFromString( str, &temp2);
986     if(result == 0) {
987         *moves = temp/60;
988         *tc    = temp2 * 1000;
989         *inc   = 0;
990     }
991     return result;
992 }
993
994 int GetTimeQuota(int movenr)
995 {   /* [HGM] get time to add from the multi-session time-control string */
996     int moves=1; /* kludge to force reading of first session */
997     long time, increment;
998     char *s = fullTimeControlString;
999
1000     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1001     do {
1002         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1003         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1004         if(movenr == -1) return time;    /* last move before new session     */
1005         if(!moves) return increment;     /* current session is incremental   */
1006         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1007     } while(movenr >= -1);               /* try again for next session       */
1008
1009     return 0; // no new time quota on this move
1010 }
1011
1012 int
1013 ParseTimeControl(tc, ti, mps)
1014      char *tc;
1015      int ti;
1016      int mps;
1017 {
1018   long tc1;
1019   long tc2;
1020   char buf[MSG_SIZ];
1021   
1022   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1023   if(ti > 0) {
1024     if(mps)
1025       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1026     else sprintf(buf, "+%s+%d", tc, ti);
1027   } else {
1028     if(mps)
1029              sprintf(buf, "+%d/%s", mps, tc);
1030     else sprintf(buf, "+%s", tc);
1031   }
1032   fullTimeControlString = StrSave(buf);
1033   
1034   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1035     return FALSE;
1036   }
1037   
1038   if( *tc == '/' ) {
1039     /* Parse second time control */
1040     tc++;
1041     
1042     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043       return FALSE;
1044     }
1045     
1046     if( tc2 == 0 ) {
1047       return FALSE;
1048     }
1049     
1050     timeControl_2 = tc2 * 1000;
1051   }
1052   else {
1053     timeControl_2 = 0;
1054   }
1055   
1056   if( tc1 == 0 ) {
1057     return FALSE;
1058   }
1059   
1060   timeControl = tc1 * 1000;
1061   
1062   if (ti >= 0) {
1063     timeIncrement = ti * 1000;  /* convert to ms */
1064     movesPerSession = 0;
1065   } else {
1066     timeIncrement = 0;
1067     movesPerSession = mps;
1068   }
1069   return TRUE;
1070 }
1071
1072 void
1073 InitBackEnd2()
1074 {
1075     if (appData.debugMode) {
1076         fprintf(debugFP, "%s\n", programVersion);
1077     }
1078
1079     set_cont_sequence(appData.wrapContSeq);
1080     if (appData.matchGames > 0) {
1081         appData.matchMode = TRUE;
1082     } else if (appData.matchMode) {
1083         appData.matchGames = 1;
1084     }
1085     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1086         appData.matchGames = appData.sameColorGames;
1087     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1088         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1089         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1090     }
1091     Reset(TRUE, FALSE);
1092     if (appData.noChessProgram || first.protocolVersion == 1) {
1093       InitBackEnd3();
1094     } else {
1095       /* kludge: allow timeout for initial "feature" commands */
1096       FreezeUI();
1097       DisplayMessage("", _("Starting chess program"));
1098       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1099     }
1100 }
1101
1102 void
1103 InitBackEnd3 P((void))
1104 {
1105     GameMode initialMode;
1106     char buf[MSG_SIZ];
1107     int err;
1108
1109     InitChessProgram(&first, startedFromSetupPosition);
1110
1111     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1112         free(programVersion);
1113         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1114         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1115     }
1116
1117     if (appData.icsActive) {
1118 #ifdef WIN32
1119         /* [DM] Make a console window if needed [HGM] merged ifs */
1120         ConsoleCreate(); 
1121 #endif
1122         err = establish();
1123         if (err != 0) {
1124             if (*appData.icsCommPort != NULLCHAR) {
1125                 sprintf(buf, _("Could not open comm port %s"),  
1126                         appData.icsCommPort);
1127             } else {
1128                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1129                         appData.icsHost, appData.icsPort);
1130             }
1131             DisplayFatalError(buf, err, 1);
1132             return;
1133         }
1134         SetICSMode();
1135         telnetISR =
1136           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137         fromUserISR =
1138           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1139         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1140             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) {
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239             }
1240         }
1241         if (initialMode == AnalyzeMode) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248             return;
1249           }
1250           AnalyzeModeEvent();
1251         } else if (initialMode == AnalyzeFile) {
1252           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253           ShowThinkingEvent();
1254           AnalyzeFileEvent();
1255           AnalysisPeriodicEvent(1);
1256         } else if (initialMode == MachinePlaysWhite) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineWhiteEvent();
1268         } else if (initialMode == MachinePlaysBlack) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           MachineBlackEvent();
1280         } else if (initialMode == TwoMachinesPlay) {
1281           if (appData.noChessProgram) {
1282             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283                               0, 2);
1284             return;
1285           }
1286           if (appData.icsActive) {
1287             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1288                               0, 2);
1289             return;
1290           }
1291           TwoMachinesEvent();
1292         } else if (initialMode == EditGame) {
1293           EditGameEvent();
1294         } else if (initialMode == EditPosition) {
1295           EditPositionEvent();
1296         } else if (initialMode == Training) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299             return;
1300           }
1301           TrainingEvent();
1302         }
1303     }
1304 }
1305
1306 /*
1307  * Establish will establish a contact to a remote host.port.
1308  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309  *  used to talk to the host.
1310  * Returns 0 if okay, error code if not.
1311  */
1312 int
1313 establish()
1314 {
1315     char buf[MSG_SIZ];
1316
1317     if (*appData.icsCommPort != NULLCHAR) {
1318         /* Talk to the host through a serial comm port */
1319         return OpenCommPort(appData.icsCommPort, &icsPR);
1320
1321     } else if (*appData.gateway != NULLCHAR) {
1322         if (*appData.remoteShell == NULLCHAR) {
1323             /* Use the rcmd protocol to run telnet program on a gateway host */
1324             snprintf(buf, sizeof(buf), "%s %s %s",
1325                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1326             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327
1328         } else {
1329             /* Use the rsh program to run telnet program on a gateway host */
1330             if (*appData.remoteUser == NULLCHAR) {
1331                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332                         appData.gateway, appData.telnetProgram,
1333                         appData.icsHost, appData.icsPort);
1334             } else {
1335                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336                         appData.remoteShell, appData.gateway, 
1337                         appData.remoteUser, appData.telnetProgram,
1338                         appData.icsHost, appData.icsPort);
1339             }
1340             return StartChildProcess(buf, "", &icsPR);
1341
1342         }
1343     } else if (appData.useTelnet) {
1344         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345
1346     } else {
1347         /* TCP socket interface differs somewhat between
1348            Unix and NT; handle details in the front end.
1349            */
1350         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1351     }
1352 }
1353
1354 void
1355 show_bytes(fp, buf, count)
1356      FILE *fp;
1357      char *buf;
1358      int count;
1359 {
1360     while (count--) {
1361         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362             fprintf(fp, "\\%03o", *buf & 0xff);
1363         } else {
1364             putc(*buf, fp);
1365         }
1366         buf++;
1367     }
1368     fflush(fp);
1369 }
1370
1371 /* Returns an errno value */
1372 int
1373 OutputMaybeTelnet(pr, message, count, outError)
1374      ProcRef pr;
1375      char *message;
1376      int count;
1377      int *outError;
1378 {
1379     char buf[8192], *p, *q, *buflim;
1380     int left, newcount, outcount;
1381
1382     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383         *appData.gateway != NULLCHAR) {
1384         if (appData.debugMode) {
1385             fprintf(debugFP, ">ICS: ");
1386             show_bytes(debugFP, message, count);
1387             fprintf(debugFP, "\n");
1388         }
1389         return OutputToProcess(pr, message, count, outError);
1390     }
1391
1392     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393     p = message;
1394     q = buf;
1395     left = count;
1396     newcount = 0;
1397     while (left) {
1398         if (q >= buflim) {
1399             if (appData.debugMode) {
1400                 fprintf(debugFP, ">ICS: ");
1401                 show_bytes(debugFP, buf, newcount);
1402                 fprintf(debugFP, "\n");
1403             }
1404             outcount = OutputToProcess(pr, buf, newcount, outError);
1405             if (outcount < newcount) return -1; /* to be sure */
1406             q = buf;
1407             newcount = 0;
1408         }
1409         if (*p == '\n') {
1410             *q++ = '\r';
1411             newcount++;
1412         } else if (((unsigned char) *p) == TN_IAC) {
1413             *q++ = (char) TN_IAC;
1414             newcount ++;
1415         }
1416         *q++ = *p++;
1417         newcount++;
1418         left--;
1419     }
1420     if (appData.debugMode) {
1421         fprintf(debugFP, ">ICS: ");
1422         show_bytes(debugFP, buf, newcount);
1423         fprintf(debugFP, "\n");
1424     }
1425     outcount = OutputToProcess(pr, buf, newcount, outError);
1426     if (outcount < newcount) return -1; /* to be sure */
1427     return count;
1428 }
1429
1430 void
1431 read_from_player(isr, closure, message, count, error)
1432      InputSourceRef isr;
1433      VOIDSTAR closure;
1434      char *message;
1435      int count;
1436      int error;
1437 {
1438     int outError, outCount;
1439     static int gotEof = 0;
1440
1441     /* Pass data read from player on to ICS */
1442     if (count > 0) {
1443         gotEof = 0;
1444         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445         if (outCount < count) {
1446             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1447         }
1448     } else if (count < 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451     } else if (gotEof++ > 0) {
1452         RemoveInputSource(isr);
1453         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1454     }
1455 }
1456
1457 void
1458 KeepAlive()
1459 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1461     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1462     SendToICS("date\n");
1463     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1464 }
1465
1466 /* added routine for printf style output to ics */
1467 void ics_printf(char *format, ...)
1468 {
1469     char buffer[MSG_SIZ];
1470     va_list args;
1471
1472     va_start(args, format);
1473     vsnprintf(buffer, sizeof(buffer), format, args);
1474     buffer[sizeof(buffer)-1] = '\0';
1475     SendToICS(buffer);
1476     va_end(args);
1477 }
1478
1479 void
1480 SendToICS(s)
1481      char *s;
1482 {
1483     int count, outCount, outError;
1484
1485     if (icsPR == NULL) return;
1486
1487     count = strlen(s);
1488     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494 /* This is used for sending logon scripts to the ICS. Sending
1495    without a delay causes problems when using timestamp on ICC
1496    (at least on my machine). */
1497 void
1498 SendToICSDelayed(s,msdelay)
1499      char *s;
1500      long msdelay;
1501 {
1502     int count, outCount, outError;
1503
1504     if (icsPR == NULL) return;
1505
1506     count = strlen(s);
1507     if (appData.debugMode) {
1508         fprintf(debugFP, ">ICS: ");
1509         show_bytes(debugFP, s, count);
1510         fprintf(debugFP, "\n");
1511     }
1512     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1513                                       msdelay);
1514     if (outCount < count) {
1515         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1516     }
1517 }
1518
1519
1520 /* Remove all highlighting escape sequences in s
1521    Also deletes any suffix starting with '(' 
1522    */
1523 char *
1524 StripHighlightAndTitle(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             if (*s == '(' || *s == '[') {
1537                 *p = NULLCHAR;
1538                 return retbuf;
1539             }
1540             *p++ = *s++;
1541         }
1542     }
1543     *p = NULLCHAR;
1544     return retbuf;
1545 }
1546
1547 /* Remove all highlighting escape sequences in s */
1548 char *
1549 StripHighlight(s)
1550      char *s;
1551 {
1552     static char retbuf[MSG_SIZ];
1553     char *p = retbuf;
1554
1555     while (*s != NULLCHAR) {
1556         while (*s == '\033') {
1557             while (*s != NULLCHAR && !isalpha(*s)) s++;
1558             if (*s != NULLCHAR) s++;
1559         }
1560         while (*s != NULLCHAR && *s != '\033') {
1561             *p++ = *s++;
1562         }
1563     }
1564     *p = NULLCHAR;
1565     return retbuf;
1566 }
1567
1568 char *variantNames[] = VARIANT_NAMES;
1569 char *
1570 VariantName(v)
1571      VariantClass v;
1572 {
1573     return variantNames[v];
1574 }
1575
1576
1577 /* Identify a variant from the strings the chess servers use or the
1578    PGN Variant tag names we use. */
1579 VariantClass
1580 StringToVariant(e)
1581      char *e;
1582 {
1583     char *p;
1584     int wnum = -1;
1585     VariantClass v = VariantNormal;
1586     int i, found = FALSE;
1587     char buf[MSG_SIZ];
1588
1589     if (!e) return v;
1590
1591     /* [HGM] skip over optional board-size prefixes */
1592     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1593         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1594         while( *e++ != '_');
1595     }
1596
1597     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1598         v = VariantNormal;
1599         found = TRUE;
1600     } else
1601     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1602       if (StrCaseStr(e, variantNames[i])) {
1603         v = (VariantClass) i;
1604         found = TRUE;
1605         break;
1606       }
1607     }
1608
1609     if (!found) {
1610       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1611           || StrCaseStr(e, "wild/fr") 
1612           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1613         v = VariantFischeRandom;
1614       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1615                  (i = 1, p = StrCaseStr(e, "w"))) {
1616         p += i;
1617         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1618         if (isdigit(*p)) {
1619           wnum = atoi(p);
1620         } else {
1621           wnum = -1;
1622         }
1623         switch (wnum) {
1624         case 0: /* FICS only, actually */
1625         case 1:
1626           /* Castling legal even if K starts on d-file */
1627           v = VariantWildCastle;
1628           break;
1629         case 2:
1630         case 3:
1631         case 4:
1632           /* Castling illegal even if K & R happen to start in
1633              normal positions. */
1634           v = VariantNoCastle;
1635           break;
1636         case 5:
1637         case 7:
1638         case 8:
1639         case 10:
1640         case 11:
1641         case 12:
1642         case 13:
1643         case 14:
1644         case 15:
1645         case 18:
1646         case 19:
1647           /* Castling legal iff K & R start in normal positions */
1648           v = VariantNormal;
1649           break;
1650         case 6:
1651         case 20:
1652         case 21:
1653           /* Special wilds for position setup; unclear what to do here */
1654           v = VariantLoadable;
1655           break;
1656         case 9:
1657           /* Bizarre ICC game */
1658           v = VariantTwoKings;
1659           break;
1660         case 16:
1661           v = VariantKriegspiel;
1662           break;
1663         case 17:
1664           v = VariantLosers;
1665           break;
1666         case 22:
1667           v = VariantFischeRandom;
1668           break;
1669         case 23:
1670           v = VariantCrazyhouse;
1671           break;
1672         case 24:
1673           v = VariantBughouse;
1674           break;
1675         case 25:
1676           v = Variant3Check;
1677           break;
1678         case 26:
1679           /* Not quite the same as FICS suicide! */
1680           v = VariantGiveaway;
1681           break;
1682         case 27:
1683           v = VariantAtomic;
1684           break;
1685         case 28:
1686           v = VariantShatranj;
1687           break;
1688
1689         /* Temporary names for future ICC types.  The name *will* change in 
1690            the next xboard/WinBoard release after ICC defines it. */
1691         case 29:
1692           v = Variant29;
1693           break;
1694         case 30:
1695           v = Variant30;
1696           break;
1697         case 31:
1698           v = Variant31;
1699           break;
1700         case 32:
1701           v = Variant32;
1702           break;
1703         case 33:
1704           v = Variant33;
1705           break;
1706         case 34:
1707           v = Variant34;
1708           break;
1709         case 35:
1710           v = Variant35;
1711           break;
1712         case 36:
1713           v = Variant36;
1714           break;
1715         case 37:
1716           v = VariantShogi;
1717           break;
1718         case 38:
1719           v = VariantXiangqi;
1720           break;
1721         case 39:
1722           v = VariantCourier;
1723           break;
1724         case 40:
1725           v = VariantGothic;
1726           break;
1727         case 41:
1728           v = VariantCapablanca;
1729           break;
1730         case 42:
1731           v = VariantKnightmate;
1732           break;
1733         case 43:
1734           v = VariantFairy;
1735           break;
1736         case 44:
1737           v = VariantCylinder;
1738           break;
1739         case 45:
1740           v = VariantFalcon;
1741           break;
1742         case 46:
1743           v = VariantCapaRandom;
1744           break;
1745         case 47:
1746           v = VariantBerolina;
1747           break;
1748         case 48:
1749           v = VariantJanus;
1750           break;
1751         case 49:
1752           v = VariantSuper;
1753           break;
1754         case 50:
1755           v = VariantGreat;
1756           break;
1757         case -1:
1758           /* Found "wild" or "w" in the string but no number;
1759              must assume it's normal chess. */
1760           v = VariantNormal;
1761           break;
1762         default:
1763           sprintf(buf, _("Unknown wild type %d"), wnum);
1764           DisplayError(buf, 0);
1765           v = VariantUnknown;
1766           break;
1767         }
1768       }
1769     }
1770     if (appData.debugMode) {
1771       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1772               e, wnum, VariantName(v));
1773     }
1774     return v;
1775 }
1776
1777 static int leftover_start = 0, leftover_len = 0;
1778 char star_match[STAR_MATCH_N][MSG_SIZ];
1779
1780 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1781    advance *index beyond it, and set leftover_start to the new value of
1782    *index; else return FALSE.  If pattern contains the character '*', it
1783    matches any sequence of characters not containing '\r', '\n', or the
1784    character following the '*' (if any), and the matched sequence(s) are
1785    copied into star_match.
1786    */
1787 int
1788 looking_at(buf, index, pattern)
1789      char *buf;
1790      int *index;
1791      char *pattern;
1792 {
1793     char *bufp = &buf[*index], *patternp = pattern;
1794     int star_count = 0;
1795     char *matchp = star_match[0];
1796     
1797     for (;;) {
1798         if (*patternp == NULLCHAR) {
1799             *index = leftover_start = bufp - buf;
1800             *matchp = NULLCHAR;
1801             return TRUE;
1802         }
1803         if (*bufp == NULLCHAR) return FALSE;
1804         if (*patternp == '*') {
1805             if (*bufp == *(patternp + 1)) {
1806                 *matchp = NULLCHAR;
1807                 matchp = star_match[++star_count];
1808                 patternp += 2;
1809                 bufp++;
1810                 continue;
1811             } else if (*bufp == '\n' || *bufp == '\r') {
1812                 patternp++;
1813                 if (*patternp == NULLCHAR)
1814                   continue;
1815                 else
1816                   return FALSE;
1817             } else {
1818                 *matchp++ = *bufp++;
1819                 continue;
1820             }
1821         }
1822         if (*patternp != *bufp) return FALSE;
1823         patternp++;
1824         bufp++;
1825     }
1826 }
1827
1828 void
1829 SendToPlayer(data, length)
1830      char *data;
1831      int length;
1832 {
1833     int error, outCount;
1834     outCount = OutputToProcess(NoProc, data, length, &error);
1835     if (outCount < length) {
1836         DisplayFatalError(_("Error writing to display"), error, 1);
1837     }
1838 }
1839
1840 void
1841 PackHolding(packed, holding)
1842      char packed[];
1843      char *holding;
1844 {
1845     char *p = holding;
1846     char *q = packed;
1847     int runlength = 0;
1848     int curr = 9999;
1849     do {
1850         if (*p == curr) {
1851             runlength++;
1852         } else {
1853             switch (runlength) {
1854               case 0:
1855                 break;
1856               case 1:
1857                 *q++ = curr;
1858                 break;
1859               case 2:
1860                 *q++ = curr;
1861                 *q++ = curr;
1862                 break;
1863               default:
1864                 sprintf(q, "%d", runlength);
1865                 while (*q) q++;
1866                 *q++ = curr;
1867                 break;
1868             }
1869             runlength = 1;
1870             curr = *p;
1871         }
1872     } while (*p++);
1873     *q = NULLCHAR;
1874 }
1875
1876 /* Telnet protocol requests from the front end */
1877 void
1878 TelnetRequest(ddww, option)
1879      unsigned char ddww, option;
1880 {
1881     unsigned char msg[3];
1882     int outCount, outError;
1883
1884     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1885
1886     if (appData.debugMode) {
1887         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1888         switch (ddww) {
1889           case TN_DO:
1890             ddwwStr = "DO";
1891             break;
1892           case TN_DONT:
1893             ddwwStr = "DONT";
1894             break;
1895           case TN_WILL:
1896             ddwwStr = "WILL";
1897             break;
1898           case TN_WONT:
1899             ddwwStr = "WONT";
1900             break;
1901           default:
1902             ddwwStr = buf1;
1903             sprintf(buf1, "%d", ddww);
1904             break;
1905         }
1906         switch (option) {
1907           case TN_ECHO:
1908             optionStr = "ECHO";
1909             break;
1910           default:
1911             optionStr = buf2;
1912             sprintf(buf2, "%d", option);
1913             break;
1914         }
1915         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1916     }
1917     msg[0] = TN_IAC;
1918     msg[1] = ddww;
1919     msg[2] = option;
1920     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1921     if (outCount < 3) {
1922         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923     }
1924 }
1925
1926 void
1927 DoEcho()
1928 {
1929     if (!appData.icsActive) return;
1930     TelnetRequest(TN_DO, TN_ECHO);
1931 }
1932
1933 void
1934 DontEcho()
1935 {
1936     if (!appData.icsActive) return;
1937     TelnetRequest(TN_DONT, TN_ECHO);
1938 }
1939
1940 void
1941 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1942 {
1943     /* put the holdings sent to us by the server on the board holdings area */
1944     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1945     char p;
1946     ChessSquare piece;
1947
1948     if(gameInfo.holdingsWidth < 2)  return;
1949     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1950         return; // prevent overwriting by pre-board holdings
1951
1952     if( (int)lowestPiece >= BlackPawn ) {
1953         holdingsColumn = 0;
1954         countsColumn = 1;
1955         holdingsStartRow = BOARD_HEIGHT-1;
1956         direction = -1;
1957     } else {
1958         holdingsColumn = BOARD_WIDTH-1;
1959         countsColumn = BOARD_WIDTH-2;
1960         holdingsStartRow = 0;
1961         direction = 1;
1962     }
1963
1964     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1965         board[i][holdingsColumn] = EmptySquare;
1966         board[i][countsColumn]   = (ChessSquare) 0;
1967     }
1968     while( (p=*holdings++) != NULLCHAR ) {
1969         piece = CharToPiece( ToUpper(p) );
1970         if(piece == EmptySquare) continue;
1971         /*j = (int) piece - (int) WhitePawn;*/
1972         j = PieceToNumber(piece);
1973         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1974         if(j < 0) continue;               /* should not happen */
1975         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1976         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1977         board[holdingsStartRow+j*direction][countsColumn]++;
1978     }
1979 }
1980
1981
1982 void
1983 VariantSwitch(Board board, VariantClass newVariant)
1984 {
1985    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1986    static Board oldBoard;
1987
1988    startedFromPositionFile = FALSE;
1989    if(gameInfo.variant == newVariant) return;
1990
1991    /* [HGM] This routine is called each time an assignment is made to
1992     * gameInfo.variant during a game, to make sure the board sizes
1993     * are set to match the new variant. If that means adding or deleting
1994     * holdings, we shift the playing board accordingly
1995     * This kludge is needed because in ICS observe mode, we get boards
1996     * of an ongoing game without knowing the variant, and learn about the
1997     * latter only later. This can be because of the move list we requested,
1998     * in which case the game history is refilled from the beginning anyway,
1999     * but also when receiving holdings of a crazyhouse game. In the latter
2000     * case we want to add those holdings to the already received position.
2001     */
2002
2003    
2004    if (appData.debugMode) {
2005      fprintf(debugFP, "Switch board from %s to %s\n",
2006              VariantName(gameInfo.variant), VariantName(newVariant));
2007      setbuf(debugFP, NULL);
2008    }
2009    shuffleOpenings = 0;       /* [HGM] shuffle */
2010    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2011    switch(newVariant) 
2012      {
2013      case VariantShogi:
2014        newWidth = 9;  newHeight = 9;
2015        gameInfo.holdingsSize = 7;
2016      case VariantBughouse:
2017      case VariantCrazyhouse:
2018        newHoldingsWidth = 2; break;
2019      case VariantGreat:
2020        newWidth = 10;
2021      case VariantSuper:
2022        newHoldingsWidth = 2;
2023        gameInfo.holdingsSize = 8;
2024        break;
2025      case VariantGothic:
2026      case VariantCapablanca:
2027      case VariantCapaRandom:
2028        newWidth = 10;
2029      default:
2030        newHoldingsWidth = gameInfo.holdingsSize = 0;
2031      };
2032    
2033    if(newWidth  != gameInfo.boardWidth  ||
2034       newHeight != gameInfo.boardHeight ||
2035       newHoldingsWidth != gameInfo.holdingsWidth ) {
2036      
2037      /* shift position to new playing area, if needed */
2038      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2039        for(i=0; i<BOARD_HEIGHT; i++) 
2040          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2041            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2042              board[i][j];
2043        for(i=0; i<newHeight; i++) {
2044          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2045          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2046        }
2047      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2048        for(i=0; i<BOARD_HEIGHT; i++)
2049          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2050            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2051              board[i][j];
2052      }
2053      gameInfo.boardWidth  = newWidth;
2054      gameInfo.boardHeight = newHeight;
2055      gameInfo.holdingsWidth = newHoldingsWidth;
2056      gameInfo.variant = newVariant;
2057      InitDrawingSizes(-2, 0);
2058    } else gameInfo.variant = newVariant;
2059    CopyBoard(oldBoard, board);   // remember correctly formatted board
2060      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2061    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2062 }
2063
2064 static int loggedOn = FALSE;
2065
2066 /*-- Game start info cache: --*/
2067 int gs_gamenum;
2068 char gs_kind[MSG_SIZ];
2069 static char player1Name[128] = "";
2070 static char player2Name[128] = "";
2071 static char cont_seq[] = "\n\\   ";
2072 static int player1Rating = -1;
2073 static int player2Rating = -1;
2074 /*----------------------------*/
2075
2076 ColorClass curColor = ColorNormal;
2077 int suppressKibitz = 0;
2078
2079 // [HGM] seekgraph
2080 Boolean soughtPending = FALSE;
2081 Boolean seekGraphUp;
2082 #define MAX_SEEK_ADS 200
2083 #define SQUARE 0x80
2084 char *seekAdList[MAX_SEEK_ADS];
2085 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2086 float tcList[MAX_SEEK_ADS];
2087 char colorList[MAX_SEEK_ADS];
2088 int nrOfSeekAds = 0;
2089 int minRating = 1010, maxRating = 2800;
2090 int hMargin = 10, vMargin = 20, h, w;
2091 extern int squareSize, lineGap;
2092
2093 void
2094 PlotSeekAd(int i)
2095 {
2096         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2097         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2098         if(r < minRating+100 && r >=0 ) r = minRating+100;
2099         if(r > maxRating) r = maxRating;
2100         if(tc < 1.) tc = 1.;
2101         if(tc > 95.) tc = 95.;
2102         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2103         y = ((double)r - minRating)/(maxRating - minRating)
2104             * (h-vMargin-squareSize/8-1) + vMargin;
2105         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2106         if(strstr(seekAdList[i], " u ")) color = 1;
2107         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2108            !strstr(seekAdList[i], "bullet") &&
2109            !strstr(seekAdList[i], "blitz") &&
2110            !strstr(seekAdList[i], "standard") ) color = 2;
2111         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2112         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2113 }
2114
2115 void
2116 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2117 {
2118         char buf[MSG_SIZ], *ext = "";
2119         VariantClass v = StringToVariant(type);
2120         if(strstr(type, "wild")) {
2121             ext = type + 4; // append wild number
2122             if(v == VariantFischeRandom) type = "chess960"; else
2123             if(v == VariantLoadable) type = "setup"; else
2124             type = VariantName(v);
2125         }
2126         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2127         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2128             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2129             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2130             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2131             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2132             seekNrList[nrOfSeekAds] = nr;
2133             zList[nrOfSeekAds] = 0;
2134             seekAdList[nrOfSeekAds++] = StrSave(buf);
2135             if(plot) PlotSeekAd(nrOfSeekAds-1);
2136         }
2137 }
2138
2139 void
2140 EraseSeekDot(int i)
2141 {
2142     int x = xList[i], y = yList[i], d=squareSize/4, k;
2143     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2144     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2145     // now replot every dot that overlapped
2146     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2147         int xx = xList[k], yy = yList[k];
2148         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2149             DrawSeekDot(xx, yy, colorList[k]);
2150     }
2151 }
2152
2153 void
2154 RemoveSeekAd(int nr)
2155 {
2156         int i;
2157         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2158             EraseSeekDot(i);
2159             if(seekAdList[i]) free(seekAdList[i]);
2160             seekAdList[i] = seekAdList[--nrOfSeekAds];
2161             seekNrList[i] = seekNrList[nrOfSeekAds];
2162             ratingList[i] = ratingList[nrOfSeekAds];
2163             colorList[i]  = colorList[nrOfSeekAds];
2164             tcList[i] = tcList[nrOfSeekAds];
2165             xList[i]  = xList[nrOfSeekAds];
2166             yList[i]  = yList[nrOfSeekAds];
2167             zList[i]  = zList[nrOfSeekAds];
2168             seekAdList[nrOfSeekAds] = NULL;
2169             break;
2170         }
2171 }
2172
2173 Boolean
2174 MatchSoughtLine(char *line)
2175 {
2176     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2177     int nr, base, inc, u=0; char dummy;
2178
2179     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2180        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2181        (u=1) &&
2182        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2183         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2184         // match: compact and save the line
2185         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2186         return TRUE;
2187     }
2188     return FALSE;
2189 }
2190
2191 int
2192 DrawSeekGraph()
2193 {
2194     if(!seekGraphUp) return FALSE;
2195     int i;
2196     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2197     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2198
2199     DrawSeekBackground(0, 0, w, h);
2200     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2201     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2202     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2203         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2204         yy = h-1-yy;
2205         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2206         if(i%500 == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2210         }
2211     }
2212     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2213     for(i=1; i<100; i+=(i<10?1:5)) {
2214         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2215         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2216         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2217             char buf[MSG_SIZ];
2218             sprintf(buf, "%d", i);
2219             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2220         }
2221     }
2222     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2223     return TRUE;
2224 }
2225
2226 int SeekGraphClick(ClickType click, int x, int y, int moving)
2227 {
2228     static int lastDown = 0, displayed = 0, lastSecond;
2229     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2230         if(click == Release || moving) return FALSE;
2231         nrOfSeekAds = 0;
2232         soughtPending = TRUE;
2233         SendToICS(ics_prefix);
2234         SendToICS("sought\n"); // should this be "sought all"?
2235     } else { // issue challenge based on clicked ad
2236         int dist = 10000; int i, closest = 0, second = 0;
2237         for(i=0; i<nrOfSeekAds; i++) {
2238             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2239             if(d < dist) { dist = d; closest = i; }
2240             second += (d - zList[i] < 120); // count in-range ads
2241             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2242         }
2243         if(dist < 120) {
2244             char buf[MSG_SIZ];
2245             second = (second > 1);
2246             if(displayed != closest || second != lastSecond) {
2247                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2248                 lastSecond = second; displayed = closest;
2249             }
2250             if(click == Press) {
2251                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2252                 lastDown = closest;
2253                 return TRUE;
2254             } // on press 'hit', only show info
2255             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2256             sprintf(buf, "play %d\n", seekNrList[closest]);
2257             SendToICS(ics_prefix);
2258             SendToICS(buf);
2259             return TRUE; // let incoming board of started game pop down the graph
2260         } else if(click == Release) { // release 'miss' is ignored
2261             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2262             if(moving == 2) { // right up-click
2263                 nrOfSeekAds = 0; // refresh graph
2264                 soughtPending = TRUE;
2265                 SendToICS(ics_prefix);
2266                 SendToICS("sought\n"); // should this be "sought all"?
2267             }
2268             return TRUE;
2269         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2270         // press miss or release hit 'pop down' seek graph
2271         seekGraphUp = FALSE;
2272         DrawPosition(TRUE, NULL);
2273     }
2274     return TRUE;
2275 }
2276
2277 void
2278 read_from_ics(isr, closure, data, count, error)
2279      InputSourceRef isr;
2280      VOIDSTAR closure;
2281      char *data;
2282      int count;
2283      int error;
2284 {
2285 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2286 #define STARTED_NONE 0
2287 #define STARTED_MOVES 1
2288 #define STARTED_BOARD 2
2289 #define STARTED_OBSERVE 3
2290 #define STARTED_HOLDINGS 4
2291 #define STARTED_CHATTER 5
2292 #define STARTED_COMMENT 6
2293 #define STARTED_MOVES_NOHIDE 7
2294     
2295     static int started = STARTED_NONE;
2296     static char parse[20000];
2297     static int parse_pos = 0;
2298     static char buf[BUF_SIZE + 1];
2299     static int firstTime = TRUE, intfSet = FALSE;
2300     static ColorClass prevColor = ColorNormal;
2301     static int savingComment = FALSE;
2302     static int cmatch = 0; // continuation sequence match
2303     char *bp;
2304     char str[500];
2305     int i, oldi;
2306     int buf_len;
2307     int next_out;
2308     int tkind;
2309     int backup;    /* [DM] For zippy color lines */
2310     char *p;
2311     char talker[MSG_SIZ]; // [HGM] chat
2312     int channel;
2313
2314     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2315
2316     if (appData.debugMode) {
2317       if (!error) {
2318         fprintf(debugFP, "<ICS: ");
2319         show_bytes(debugFP, data, count);
2320         fprintf(debugFP, "\n");
2321       }
2322     }
2323
2324     if (appData.debugMode) { int f = forwardMostMove;
2325         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2326                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2327                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2328     }
2329     if (count > 0) {
2330         /* If last read ended with a partial line that we couldn't parse,
2331            prepend it to the new read and try again. */
2332         if (leftover_len > 0) {
2333             for (i=0; i<leftover_len; i++)
2334               buf[i] = buf[leftover_start + i];
2335         }
2336
2337     /* copy new characters into the buffer */
2338     bp = buf + leftover_len;
2339     buf_len=leftover_len;
2340     for (i=0; i<count; i++)
2341     {
2342         // ignore these
2343         if (data[i] == '\r')
2344             continue;
2345
2346         // join lines split by ICS?
2347         if (!appData.noJoin)
2348         {
2349             /*
2350                 Joining just consists of finding matches against the
2351                 continuation sequence, and discarding that sequence
2352                 if found instead of copying it.  So, until a match
2353                 fails, there's nothing to do since it might be the
2354                 complete sequence, and thus, something we don't want
2355                 copied.
2356             */
2357             if (data[i] == cont_seq[cmatch])
2358             {
2359                 cmatch++;
2360                 if (cmatch == strlen(cont_seq))
2361                 {
2362                     cmatch = 0; // complete match.  just reset the counter
2363
2364                     /*
2365                         it's possible for the ICS to not include the space
2366                         at the end of the last word, making our [correct]
2367                         join operation fuse two separate words.  the server
2368                         does this when the space occurs at the width setting.
2369                     */
2370                     if (!buf_len || buf[buf_len-1] != ' ')
2371                     {
2372                         *bp++ = ' ';
2373                         buf_len++;
2374                     }
2375                 }
2376                 continue;
2377             }
2378             else if (cmatch)
2379             {
2380                 /*
2381                     match failed, so we have to copy what matched before
2382                     falling through and copying this character.  In reality,
2383                     this will only ever be just the newline character, but
2384                     it doesn't hurt to be precise.
2385                 */
2386                 strncpy(bp, cont_seq, cmatch);
2387                 bp += cmatch;
2388                 buf_len += cmatch;
2389                 cmatch = 0;
2390             }
2391         }
2392
2393         // copy this char
2394         *bp++ = data[i];
2395         buf_len++;
2396     }
2397
2398         buf[buf_len] = NULLCHAR;
2399 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2400         next_out = 0;
2401         leftover_start = 0;
2402         
2403         i = 0;
2404         while (i < buf_len) {
2405             /* Deal with part of the TELNET option negotiation
2406                protocol.  We refuse to do anything beyond the
2407                defaults, except that we allow the WILL ECHO option,
2408                which ICS uses to turn off password echoing when we are
2409                directly connected to it.  We reject this option
2410                if localLineEditing mode is on (always on in xboard)
2411                and we are talking to port 23, which might be a real
2412                telnet server that will try to keep WILL ECHO on permanently.
2413              */
2414             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2415                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2416                 unsigned char option;
2417                 oldi = i;
2418                 switch ((unsigned char) buf[++i]) {
2419                   case TN_WILL:
2420                     if (appData.debugMode)
2421                       fprintf(debugFP, "\n<WILL ");
2422                     switch (option = (unsigned char) buf[++i]) {
2423                       case TN_ECHO:
2424                         if (appData.debugMode)
2425                           fprintf(debugFP, "ECHO ");
2426                         /* Reply only if this is a change, according
2427                            to the protocol rules. */
2428                         if (remoteEchoOption) break;
2429                         if (appData.localLineEditing &&
2430                             atoi(appData.icsPort) == TN_PORT) {
2431                             TelnetRequest(TN_DONT, TN_ECHO);
2432                         } else {
2433                             EchoOff();
2434                             TelnetRequest(TN_DO, TN_ECHO);
2435                             remoteEchoOption = TRUE;
2436                         }
2437                         break;
2438                       default:
2439                         if (appData.debugMode)
2440                           fprintf(debugFP, "%d ", option);
2441                         /* Whatever this is, we don't want it. */
2442                         TelnetRequest(TN_DONT, option);
2443                         break;
2444                     }
2445                     break;
2446                   case TN_WONT:
2447                     if (appData.debugMode)
2448                       fprintf(debugFP, "\n<WONT ");
2449                     switch (option = (unsigned char) buf[++i]) {
2450                       case TN_ECHO:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "ECHO ");
2453                         /* Reply only if this is a change, according
2454                            to the protocol rules. */
2455                         if (!remoteEchoOption) break;
2456                         EchoOn();
2457                         TelnetRequest(TN_DONT, TN_ECHO);
2458                         remoteEchoOption = FALSE;
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", (unsigned char) option);
2463                         /* Whatever this is, it must already be turned
2464                            off, because we never agree to turn on
2465                            anything non-default, so according to the
2466                            protocol rules, we don't reply. */
2467                         break;
2468                     }
2469                     break;
2470                   case TN_DO:
2471                     if (appData.debugMode)
2472                       fprintf(debugFP, "\n<DO ");
2473                     switch (option = (unsigned char) buf[++i]) {
2474                       default:
2475                         /* Whatever this is, we refuse to do it. */
2476                         if (appData.debugMode)
2477                           fprintf(debugFP, "%d ", option);
2478                         TelnetRequest(TN_WONT, option);
2479                         break;
2480                     }
2481                     break;
2482                   case TN_DONT:
2483                     if (appData.debugMode)
2484                       fprintf(debugFP, "\n<DONT ");
2485                     switch (option = (unsigned char) buf[++i]) {
2486                       default:
2487                         if (appData.debugMode)
2488                           fprintf(debugFP, "%d ", option);
2489                         /* Whatever this is, we are already not doing
2490                            it, because we never agree to do anything
2491                            non-default, so according to the protocol
2492                            rules, we don't reply. */
2493                         break;
2494                     }
2495                     break;
2496                   case TN_IAC:
2497                     if (appData.debugMode)
2498                       fprintf(debugFP, "\n<IAC ");
2499                     /* Doubled IAC; pass it through */
2500                     i--;
2501                     break;
2502                   default:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2505                     /* Drop all other telnet commands on the floor */
2506                     break;
2507                 }
2508                 if (oldi > next_out)
2509                   SendToPlayer(&buf[next_out], oldi - next_out);
2510                 if (++i > next_out)
2511                   next_out = i;
2512                 continue;
2513             }
2514                 
2515             /* OK, this at least will *usually* work */
2516             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2517                 loggedOn = TRUE;
2518             }
2519             
2520             if (loggedOn && !intfSet) {
2521                 if (ics_type == ICS_ICC) {
2522                   sprintf(str,
2523                           "/set-quietly interface %s\n/set-quietly style 12\n",
2524                           programVersion);
2525                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2526                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2527                 } else if (ics_type == ICS_CHESSNET) {
2528                   sprintf(str, "/style 12\n");
2529                 } else {
2530                   strcpy(str, "alias $ @\n$set interface ");
2531                   strcat(str, programVersion);
2532                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2533                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2534                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2535 #ifdef WIN32
2536                   strcat(str, "$iset nohighlight 1\n");
2537 #endif
2538                   strcat(str, "$iset lock 1\n$style 12\n");
2539                 }
2540                 SendToICS(str);
2541                 NotifyFrontendLogin();
2542                 intfSet = TRUE;
2543             }
2544
2545             if (started == STARTED_COMMENT) {
2546                 /* Accumulate characters in comment */
2547                 parse[parse_pos++] = buf[i];
2548                 if (buf[i] == '\n') {
2549                     parse[parse_pos] = NULLCHAR;
2550                     if(chattingPartner>=0) {
2551                         char mess[MSG_SIZ];
2552                         sprintf(mess, "%s%s", talker, parse);
2553                         OutputChatMessage(chattingPartner, mess);
2554                         chattingPartner = -1;
2555                         next_out = i+1; // [HGM] suppress printing in ICS window
2556                     } else
2557                     if(!suppressKibitz) // [HGM] kibitz
2558                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2559                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2560                         int nrDigit = 0, nrAlph = 0, j;
2561                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2562                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2563                         parse[parse_pos] = NULLCHAR;
2564                         // try to be smart: if it does not look like search info, it should go to
2565                         // ICS interaction window after all, not to engine-output window.
2566                         for(j=0; j<parse_pos; j++) { // count letters and digits
2567                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2568                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2569                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2570                         }
2571                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2572                             int depth=0; float score;
2573                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2574                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2575                                 pvInfoList[forwardMostMove-1].depth = depth;
2576                                 pvInfoList[forwardMostMove-1].score = 100*score;
2577                             }
2578                             OutputKibitz(suppressKibitz, parse);
2579                         } else {
2580                             char tmp[MSG_SIZ];
2581                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2582                             SendToPlayer(tmp, strlen(tmp));
2583                         }
2584                         next_out = i+1; // [HGM] suppress printing in ICS window
2585                     }
2586                     started = STARTED_NONE;
2587                 } else {
2588                     /* Don't match patterns against characters in comment */
2589                     i++;
2590                     continue;
2591                 }
2592             }
2593             if (started == STARTED_CHATTER) {
2594                 if (buf[i] != '\n') {
2595                     /* Don't match patterns against characters in chatter */
2596                     i++;
2597                     continue;
2598                 }
2599                 started = STARTED_NONE;
2600                 if(suppressKibitz) next_out = i+1;
2601             }
2602
2603             /* Kludge to deal with rcmd protocol */
2604             if (firstTime && looking_at(buf, &i, "\001*")) {
2605                 DisplayFatalError(&buf[1], 0, 1);
2606                 continue;
2607             } else {
2608                 firstTime = FALSE;
2609             }
2610
2611             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2612                 ics_type = ICS_ICC;
2613                 ics_prefix = "/";
2614                 if (appData.debugMode)
2615                   fprintf(debugFP, "ics_type %d\n", ics_type);
2616                 continue;
2617             }
2618             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2619                 ics_type = ICS_FICS;
2620                 ics_prefix = "$";
2621                 if (appData.debugMode)
2622                   fprintf(debugFP, "ics_type %d\n", ics_type);
2623                 continue;
2624             }
2625             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2626                 ics_type = ICS_CHESSNET;
2627                 ics_prefix = "/";
2628                 if (appData.debugMode)
2629                   fprintf(debugFP, "ics_type %d\n", ics_type);
2630                 continue;
2631             }
2632
2633             if (!loggedOn &&
2634                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2635                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2636                  looking_at(buf, &i, "will be \"*\""))) {
2637               strcpy(ics_handle, star_match[0]);
2638               continue;
2639             }
2640
2641             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2642               char buf[MSG_SIZ];
2643               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2644               DisplayIcsInteractionTitle(buf);
2645               have_set_title = TRUE;
2646             }
2647
2648             /* skip finger notes */
2649             if (started == STARTED_NONE &&
2650                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2651                  (buf[i] == '1' && buf[i+1] == '0')) &&
2652                 buf[i+2] == ':' && buf[i+3] == ' ') {
2653               started = STARTED_CHATTER;
2654               i += 3;
2655               continue;
2656             }
2657
2658             oldi = i;
2659             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2660             if(appData.seekGraph) {
2661                 if(soughtPending && MatchSoughtLine(buf+i)) {
2662                     i = strstr(buf+i, "rated") - buf;
2663                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2664                     next_out = leftover_start = i;
2665                     started = STARTED_CHATTER;
2666                     suppressKibitz = TRUE;
2667                     continue;
2668                 }
2669                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2670                         && looking_at(buf, &i, "* ads displayed")) {
2671                     soughtPending = FALSE;
2672                     seekGraphUp = TRUE;
2673                     DrawSeekGraph();
2674                     continue;
2675                 }
2676                 if(appData.autoRefresh) {
2677                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2678                         int s = (ics_type == ICS_ICC); // ICC format differs
2679                         if(seekGraphUp)
2680                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2681                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2682                         looking_at(buf, &i, "*% "); // eat prompt
2683                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2684                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2685                         next_out = i; // suppress
2686                         continue;
2687                     }
2688                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2689                         char *p = star_match[0];
2690                         while(*p) {
2691                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2692                             while(*p && *p++ != ' '); // next
2693                         }
2694                         looking_at(buf, &i, "*% "); // eat prompt
2695                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2696                         next_out = i;
2697                         continue;
2698                     }
2699                 }
2700             }
2701
2702             /* skip formula vars */
2703             if (started == STARTED_NONE &&
2704                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2705               started = STARTED_CHATTER;
2706               i += 3;
2707               continue;
2708             }
2709
2710             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2711             if (appData.autoKibitz && started == STARTED_NONE && 
2712                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2713                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2714                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2715                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2716                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2717                         suppressKibitz = TRUE;
2718                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2719                         next_out = i;
2720                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2721                                 && (gameMode == IcsPlayingWhite)) ||
2722                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2723                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2724                             started = STARTED_CHATTER; // own kibitz we simply discard
2725                         else {
2726                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2727                             parse_pos = 0; parse[0] = NULLCHAR;
2728                             savingComment = TRUE;
2729                             suppressKibitz = gameMode != IcsObserving ? 2 :
2730                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2731                         } 
2732                         continue;
2733                 } else
2734                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2735                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2736                          && atoi(star_match[0])) {
2737                     // suppress the acknowledgements of our own autoKibitz
2738                     char *p;
2739                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2740                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2741                     SendToPlayer(star_match[0], strlen(star_match[0]));
2742                     if(looking_at(buf, &i, "*% ")) // eat prompt
2743                         suppressKibitz = FALSE;
2744                     next_out = i;
2745                     continue;
2746                 }
2747             } // [HGM] kibitz: end of patch
2748
2749             // [HGM] chat: intercept tells by users for which we have an open chat window
2750             channel = -1;
2751             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2752                                            looking_at(buf, &i, "* whispers:") ||
2753                                            looking_at(buf, &i, "* kibitzes:") ||
2754                                            looking_at(buf, &i, "* shouts:") ||
2755                                            looking_at(buf, &i, "* c-shouts:") ||
2756                                            looking_at(buf, &i, "--> * ") ||
2757                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2758                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2759                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2760                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2761                 int p;
2762                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2763                 chattingPartner = -1;
2764
2765                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2766                 for(p=0; p<MAX_CHAT; p++) {
2767                     if(channel == atoi(chatPartner[p])) {
2768                     talker[0] = '['; strcat(talker, "] ");
2769                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2770                     chattingPartner = p; break;
2771                     }
2772                 } else
2773                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2774                 for(p=0; p<MAX_CHAT; p++) {
2775                     if(!strcmp("kibitzes", chatPartner[p])) {
2776                         talker[0] = '['; strcat(talker, "] ");
2777                         chattingPartner = p; break;
2778                     }
2779                 } else
2780                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2781                 for(p=0; p<MAX_CHAT; p++) {
2782                     if(!strcmp("whispers", chatPartner[p])) {
2783                         talker[0] = '['; strcat(talker, "] ");
2784                         chattingPartner = p; break;
2785                     }
2786                 } else
2787                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2788                   if(buf[i-8] == '-' && buf[i-3] == 't')
2789                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2790                     if(!strcmp("c-shouts", chatPartner[p])) {
2791                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2792                         chattingPartner = p; break;
2793                     }
2794                   }
2795                   if(chattingPartner < 0)
2796                   for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("shouts", chatPartner[p])) {
2798                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2799                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2800                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2801                         chattingPartner = p; break;
2802                     }
2803                   }
2804                 }
2805                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2806                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2807                     talker[0] = 0; Colorize(ColorTell, FALSE);
2808                     chattingPartner = p; break;
2809                 }
2810                 if(chattingPartner<0) i = oldi; else {
2811                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2812                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2813                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                     started = STARTED_COMMENT;
2815                     parse_pos = 0; parse[0] = NULLCHAR;
2816                     savingComment = 3 + chattingPartner; // counts as TRUE
2817                     suppressKibitz = TRUE;
2818                     continue;
2819                 }
2820             } // [HGM] chat: end of patch
2821
2822             if (appData.zippyTalk || appData.zippyPlay) {
2823                 /* [DM] Backup address for color zippy lines */
2824                 backup = i;
2825 #if ZIPPY
2826        #ifdef WIN32
2827                if (loggedOn == TRUE)
2828                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2829                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2830        #else
2831                 if (ZippyControl(buf, &i) ||
2832                     ZippyConverse(buf, &i) ||
2833                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2834                       loggedOn = TRUE;
2835                       if (!appData.colorize) continue;
2836                 }
2837        #endif
2838 #endif
2839             } // [DM] 'else { ' deleted
2840                 if (
2841                     /* Regular tells and says */
2842                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2843                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2844                     looking_at(buf, &i, "* says: ") ||
2845                     /* Don't color "message" or "messages" output */
2846                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2847                     looking_at(buf, &i, "*. * at *:*: ") ||
2848                     looking_at(buf, &i, "--* (*:*): ") ||
2849                     /* Message notifications (same color as tells) */
2850                     looking_at(buf, &i, "* has left a message ") ||
2851                     looking_at(buf, &i, "* just sent you a message:\n") ||
2852                     /* Whispers and kibitzes */
2853                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2854                     looking_at(buf, &i, "* kibitzes: ") ||
2855                     /* Channel tells */
2856                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2857
2858                   if (tkind == 1 && strchr(star_match[0], ':')) {
2859                       /* Avoid "tells you:" spoofs in channels */
2860                      tkind = 3;
2861                   }
2862                   if (star_match[0][0] == NULLCHAR ||
2863                       strchr(star_match[0], ' ') ||
2864                       (tkind == 3 && strchr(star_match[1], ' '))) {
2865                     /* Reject bogus matches */
2866                     i = oldi;
2867                   } else {
2868                     if (appData.colorize) {
2869                       if (oldi > next_out) {
2870                         SendToPlayer(&buf[next_out], oldi - next_out);
2871                         next_out = oldi;
2872                       }
2873                       switch (tkind) {
2874                       case 1:
2875                         Colorize(ColorTell, FALSE);
2876                         curColor = ColorTell;
2877                         break;
2878                       case 2:
2879                         Colorize(ColorKibitz, FALSE);
2880                         curColor = ColorKibitz;
2881                         break;
2882                       case 3:
2883                         p = strrchr(star_match[1], '(');
2884                         if (p == NULL) {
2885                           p = star_match[1];
2886                         } else {
2887                           p++;
2888                         }
2889                         if (atoi(p) == 1) {
2890                           Colorize(ColorChannel1, FALSE);
2891                           curColor = ColorChannel1;
2892                         } else {
2893                           Colorize(ColorChannel, FALSE);
2894                           curColor = ColorChannel;
2895                         }
2896                         break;
2897                       case 5:
2898                         curColor = ColorNormal;
2899                         break;
2900                       }
2901                     }
2902                     if (started == STARTED_NONE && appData.autoComment &&
2903                         (gameMode == IcsObserving ||
2904                          gameMode == IcsPlayingWhite ||
2905                          gameMode == IcsPlayingBlack)) {
2906                       parse_pos = i - oldi;
2907                       memcpy(parse, &buf[oldi], parse_pos);
2908                       parse[parse_pos] = NULLCHAR;
2909                       started = STARTED_COMMENT;
2910                       savingComment = TRUE;
2911                     } else {
2912                       started = STARTED_CHATTER;
2913                       savingComment = FALSE;
2914                     }
2915                     loggedOn = TRUE;
2916                     continue;
2917                   }
2918                 }
2919
2920                 if (looking_at(buf, &i, "* s-shouts: ") ||
2921                     looking_at(buf, &i, "* c-shouts: ")) {
2922                     if (appData.colorize) {
2923                         if (oldi > next_out) {
2924                             SendToPlayer(&buf[next_out], oldi - next_out);
2925                             next_out = oldi;
2926                         }
2927                         Colorize(ColorSShout, FALSE);
2928                         curColor = ColorSShout;
2929                     }
2930                     loggedOn = TRUE;
2931                     started = STARTED_CHATTER;
2932                     continue;
2933                 }
2934
2935                 if (looking_at(buf, &i, "--->")) {
2936                     loggedOn = TRUE;
2937                     continue;
2938                 }
2939
2940                 if (looking_at(buf, &i, "* shouts: ") ||
2941                     looking_at(buf, &i, "--> ")) {
2942                     if (appData.colorize) {
2943                         if (oldi > next_out) {
2944                             SendToPlayer(&buf[next_out], oldi - next_out);
2945                             next_out = oldi;
2946                         }
2947                         Colorize(ColorShout, FALSE);
2948                         curColor = ColorShout;
2949                     }
2950                     loggedOn = TRUE;
2951                     started = STARTED_CHATTER;
2952                     continue;
2953                 }
2954
2955                 if (looking_at( buf, &i, "Challenge:")) {
2956                     if (appData.colorize) {
2957                         if (oldi > next_out) {
2958                             SendToPlayer(&buf[next_out], oldi - next_out);
2959                             next_out = oldi;
2960                         }
2961                         Colorize(ColorChallenge, FALSE);
2962                         curColor = ColorChallenge;
2963                     }
2964                     loggedOn = TRUE;
2965                     continue;
2966                 }
2967
2968                 if (looking_at(buf, &i, "* offers you") ||
2969                     looking_at(buf, &i, "* offers to be") ||
2970                     looking_at(buf, &i, "* would like to") ||
2971                     looking_at(buf, &i, "* requests to") ||
2972                     looking_at(buf, &i, "Your opponent offers") ||
2973                     looking_at(buf, &i, "Your opponent requests")) {
2974
2975                     if (appData.colorize) {
2976                         if (oldi > next_out) {
2977                             SendToPlayer(&buf[next_out], oldi - next_out);
2978                             next_out = oldi;
2979                         }
2980                         Colorize(ColorRequest, FALSE);
2981                         curColor = ColorRequest;
2982                     }
2983                     continue;
2984                 }
2985
2986                 if (looking_at(buf, &i, "* (*) seeking")) {
2987                     if (appData.colorize) {
2988                         if (oldi > next_out) {
2989                             SendToPlayer(&buf[next_out], oldi - next_out);
2990                             next_out = oldi;
2991                         }
2992                         Colorize(ColorSeek, FALSE);
2993                         curColor = ColorSeek;
2994                     }
2995                     continue;
2996             }
2997
2998             if (looking_at(buf, &i, "\\   ")) {
2999                 if (prevColor != ColorNormal) {
3000                     if (oldi > next_out) {
3001                         SendToPlayer(&buf[next_out], oldi - next_out);
3002                         next_out = oldi;
3003                     }
3004                     Colorize(prevColor, TRUE);
3005                     curColor = prevColor;
3006                 }
3007                 if (savingComment) {
3008                     parse_pos = i - oldi;
3009                     memcpy(parse, &buf[oldi], parse_pos);
3010                     parse[parse_pos] = NULLCHAR;
3011                     started = STARTED_COMMENT;
3012                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3013                         chattingPartner = savingComment - 3; // kludge to remember the box
3014                 } else {
3015                     started = STARTED_CHATTER;
3016                 }
3017                 continue;
3018             }
3019
3020             if (looking_at(buf, &i, "Black Strength :") ||
3021                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3022                 looking_at(buf, &i, "<10>") ||
3023                 looking_at(buf, &i, "#@#")) {
3024                 /* Wrong board style */
3025                 loggedOn = TRUE;
3026                 SendToICS(ics_prefix);
3027                 SendToICS("set style 12\n");
3028                 SendToICS(ics_prefix);
3029                 SendToICS("refresh\n");
3030                 continue;
3031             }
3032             
3033             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3034                 ICSInitScript();
3035                 have_sent_ICS_logon = 1;
3036                 continue;
3037             }
3038               
3039             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3040                 (looking_at(buf, &i, "\n<12> ") ||
3041                  looking_at(buf, &i, "<12> "))) {
3042                 loggedOn = TRUE;
3043                 if (oldi > next_out) {
3044                     SendToPlayer(&buf[next_out], oldi - next_out);
3045                 }
3046                 next_out = i;
3047                 started = STARTED_BOARD;
3048                 parse_pos = 0;
3049                 continue;
3050             }
3051
3052             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3053                 looking_at(buf, &i, "<b1> ")) {
3054                 if (oldi > next_out) {
3055                     SendToPlayer(&buf[next_out], oldi - next_out);
3056                 }
3057                 next_out = i;
3058                 started = STARTED_HOLDINGS;
3059                 parse_pos = 0;
3060                 continue;
3061             }
3062
3063             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3064                 loggedOn = TRUE;
3065                 /* Header for a move list -- first line */
3066
3067                 switch (ics_getting_history) {
3068                   case H_FALSE:
3069                     switch (gameMode) {
3070                       case IcsIdle:
3071                       case BeginningOfGame:
3072                         /* User typed "moves" or "oldmoves" while we
3073                            were idle.  Pretend we asked for these
3074                            moves and soak them up so user can step
3075                            through them and/or save them.
3076                            */
3077                         Reset(FALSE, TRUE);
3078                         gameMode = IcsObserving;
3079                         ModeHighlight();
3080                         ics_gamenum = -1;
3081                         ics_getting_history = H_GOT_UNREQ_HEADER;
3082                         break;
3083                       case EditGame: /*?*/
3084                       case EditPosition: /*?*/
3085                         /* Should above feature work in these modes too? */
3086                         /* For now it doesn't */
3087                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3088                         break;
3089                       default:
3090                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3091                         break;
3092                     }
3093                     break;
3094                   case H_REQUESTED:
3095                     /* Is this the right one? */
3096                     if (gameInfo.white && gameInfo.black &&
3097                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3098                         strcmp(gameInfo.black, star_match[2]) == 0) {
3099                         /* All is well */
3100                         ics_getting_history = H_GOT_REQ_HEADER;
3101                     }
3102                     break;
3103                   case H_GOT_REQ_HEADER:
3104                   case H_GOT_UNREQ_HEADER:
3105                   case H_GOT_UNWANTED_HEADER:
3106                   case H_GETTING_MOVES:
3107                     /* Should not happen */
3108                     DisplayError(_("Error gathering move list: two headers"), 0);
3109                     ics_getting_history = H_FALSE;
3110                     break;
3111                 }
3112
3113                 /* Save player ratings into gameInfo if needed */
3114                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3115                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3116                     (gameInfo.whiteRating == -1 ||
3117                      gameInfo.blackRating == -1)) {
3118
3119                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3120                     gameInfo.blackRating = string_to_rating(star_match[3]);
3121                     if (appData.debugMode)
3122                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3123                               gameInfo.whiteRating, gameInfo.blackRating);
3124                 }
3125                 continue;
3126             }
3127
3128             if (looking_at(buf, &i,
3129               "* * match, initial time: * minute*, increment: * second")) {
3130                 /* Header for a move list -- second line */
3131                 /* Initial board will follow if this is a wild game */
3132                 if (gameInfo.event != NULL) free(gameInfo.event);
3133                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3134                 gameInfo.event = StrSave(str);
3135                 /* [HGM] we switched variant. Translate boards if needed. */
3136                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3137                 continue;
3138             }
3139
3140             if (looking_at(buf, &i, "Move  ")) {
3141                 /* Beginning of a move list */
3142                 switch (ics_getting_history) {
3143                   case H_FALSE:
3144                     /* Normally should not happen */
3145                     /* Maybe user hit reset while we were parsing */
3146                     break;
3147                   case H_REQUESTED:
3148                     /* Happens if we are ignoring a move list that is not
3149                      * the one we just requested.  Common if the user
3150                      * tries to observe two games without turning off
3151                      * getMoveList */
3152                     break;
3153                   case H_GETTING_MOVES:
3154                     /* Should not happen */
3155                     DisplayError(_("Error gathering move list: nested"), 0);
3156                     ics_getting_history = H_FALSE;
3157                     break;
3158                   case H_GOT_REQ_HEADER:
3159                     ics_getting_history = H_GETTING_MOVES;
3160                     started = STARTED_MOVES;
3161                     parse_pos = 0;
3162                     if (oldi > next_out) {
3163                         SendToPlayer(&buf[next_out], oldi - next_out);
3164                     }
3165                     break;
3166                   case H_GOT_UNREQ_HEADER:
3167                     ics_getting_history = H_GETTING_MOVES;
3168                     started = STARTED_MOVES_NOHIDE;
3169                     parse_pos = 0;
3170                     break;
3171                   case H_GOT_UNWANTED_HEADER:
3172                     ics_getting_history = H_FALSE;
3173                     break;
3174                 }
3175                 continue;
3176             }                           
3177             
3178             if (looking_at(buf, &i, "% ") ||
3179                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3180                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3181                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3182                     soughtPending = FALSE;
3183                     seekGraphUp = TRUE;
3184                     DrawSeekGraph();
3185                 }
3186                 if(suppressKibitz) next_out = i;
3187                 savingComment = FALSE;
3188                 suppressKibitz = 0;
3189                 switch (started) {
3190                   case STARTED_MOVES:
3191                   case STARTED_MOVES_NOHIDE:
3192                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3193                     parse[parse_pos + i - oldi] = NULLCHAR;
3194                     ParseGameHistory(parse);
3195 #if ZIPPY
3196                     if (appData.zippyPlay && first.initDone) {
3197                         FeedMovesToProgram(&first, forwardMostMove);
3198                         if (gameMode == IcsPlayingWhite) {
3199                             if (WhiteOnMove(forwardMostMove)) {
3200                                 if (first.sendTime) {
3201                                   if (first.useColors) {
3202                                     SendToProgram("black\n", &first); 
3203                                   }
3204                                   SendTimeRemaining(&first, TRUE);
3205                                 }
3206                                 if (first.useColors) {
3207                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3208                                 }
3209                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3210                                 first.maybeThinking = TRUE;
3211                             } else {
3212                                 if (first.usePlayother) {
3213                                   if (first.sendTime) {
3214                                     SendTimeRemaining(&first, TRUE);
3215                                   }
3216                                   SendToProgram("playother\n", &first);
3217                                   firstMove = FALSE;
3218                                 } else {
3219                                   firstMove = TRUE;
3220                                 }
3221                             }
3222                         } else if (gameMode == IcsPlayingBlack) {
3223                             if (!WhiteOnMove(forwardMostMove)) {
3224                                 if (first.sendTime) {
3225                                   if (first.useColors) {
3226                                     SendToProgram("white\n", &first);
3227                                   }
3228                                   SendTimeRemaining(&first, FALSE);
3229                                 }
3230                                 if (first.useColors) {
3231                                   SendToProgram("black\n", &first);
3232                                 }
3233                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3234                                 first.maybeThinking = TRUE;
3235                             } else {
3236                                 if (first.usePlayother) {
3237                                   if (first.sendTime) {
3238                                     SendTimeRemaining(&first, FALSE);
3239                                   }
3240                                   SendToProgram("playother\n", &first);
3241                                   firstMove = FALSE;
3242                                 } else {
3243                                   firstMove = TRUE;
3244                                 }
3245                             }
3246                         }                       
3247                     }
3248 #endif
3249                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3250                         /* Moves came from oldmoves or moves command
3251                            while we weren't doing anything else.
3252                            */
3253                         currentMove = forwardMostMove;
3254                         ClearHighlights();/*!!could figure this out*/
3255                         flipView = appData.flipView;
3256                         DrawPosition(TRUE, boards[currentMove]);
3257                         DisplayBothClocks();
3258                         sprintf(str, "%s vs. %s",
3259                                 gameInfo.white, gameInfo.black);
3260                         DisplayTitle(str);
3261                         gameMode = IcsIdle;
3262                     } else {
3263                         /* Moves were history of an active game */
3264                         if (gameInfo.resultDetails != NULL) {
3265                             free(gameInfo.resultDetails);
3266                             gameInfo.resultDetails = NULL;
3267                         }
3268                     }
3269                     HistorySet(parseList, backwardMostMove,
3270                                forwardMostMove, currentMove-1);
3271                     DisplayMove(currentMove - 1);
3272                     if (started == STARTED_MOVES) next_out = i;
3273                     started = STARTED_NONE;
3274                     ics_getting_history = H_FALSE;
3275                     break;
3276
3277                   case STARTED_OBSERVE:
3278                     started = STARTED_NONE;
3279                     SendToICS(ics_prefix);
3280                     SendToICS("refresh\n");
3281                     break;
3282
3283                   default:
3284                     break;
3285                 }
3286                 if(bookHit) { // [HGM] book: simulate book reply
3287                     static char bookMove[MSG_SIZ]; // a bit generous?
3288
3289                     programStats.nodes = programStats.depth = programStats.time = 
3290                     programStats.score = programStats.got_only_move = 0;
3291                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3292
3293                     strcpy(bookMove, "move ");
3294                     strcat(bookMove, bookHit);
3295                     HandleMachineMove(bookMove, &first);
3296                 }
3297                 continue;
3298             }
3299             
3300             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3301                  started == STARTED_HOLDINGS ||
3302                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3303                 /* Accumulate characters in move list or board */
3304                 parse[parse_pos++] = buf[i];
3305             }
3306             
3307             /* Start of game messages.  Mostly we detect start of game
3308                when the first board image arrives.  On some versions
3309                of the ICS, though, we need to do a "refresh" after starting
3310                to observe in order to get the current board right away. */
3311             if (looking_at(buf, &i, "Adding game * to observation list")) {
3312                 started = STARTED_OBSERVE;
3313                 continue;
3314             }
3315
3316             /* Handle auto-observe */
3317             if (appData.autoObserve &&
3318                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3319                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3320                 char *player;
3321                 /* Choose the player that was highlighted, if any. */
3322                 if (star_match[0][0] == '\033' ||
3323                     star_match[1][0] != '\033') {
3324                     player = star_match[0];
3325                 } else {
3326                     player = star_match[2];
3327                 }
3328                 sprintf(str, "%sobserve %s\n",
3329                         ics_prefix, StripHighlightAndTitle(player));
3330                 SendToICS(str);
3331
3332                 /* Save ratings from notify string */
3333                 strcpy(player1Name, star_match[0]);
3334                 player1Rating = string_to_rating(star_match[1]);
3335                 strcpy(player2Name, star_match[2]);
3336                 player2Rating = string_to_rating(star_match[3]);
3337
3338                 if (appData.debugMode)
3339                   fprintf(debugFP, 
3340                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3341                           player1Name, player1Rating,
3342                           player2Name, player2Rating);
3343
3344                 continue;
3345             }
3346
3347             /* Deal with automatic examine mode after a game,
3348                and with IcsObserving -> IcsExamining transition */
3349             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3350                 looking_at(buf, &i, "has made you an examiner of game *")) {
3351
3352                 int gamenum = atoi(star_match[0]);
3353                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3354                     gamenum == ics_gamenum) {
3355                     /* We were already playing or observing this game;
3356                        no need to refetch history */
3357                     gameMode = IcsExamining;
3358                     if (pausing) {
3359                         pauseExamForwardMostMove = forwardMostMove;
3360                     } else if (currentMove < forwardMostMove) {
3361                         ForwardInner(forwardMostMove);
3362                     }
3363                 } else {
3364                     /* I don't think this case really can happen */
3365                     SendToICS(ics_prefix);
3366                     SendToICS("refresh\n");
3367                 }
3368                 continue;
3369             }    
3370             
3371             /* Error messages */
3372 //          if (ics_user_moved) {
3373             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3374                 if (looking_at(buf, &i, "Illegal move") ||
3375                     looking_at(buf, &i, "Not a legal move") ||
3376                     looking_at(buf, &i, "Your king is in check") ||
3377                     looking_at(buf, &i, "It isn't your turn") ||
3378                     looking_at(buf, &i, "It is not your move")) {
3379                     /* Illegal move */
3380                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3381                         currentMove = forwardMostMove-1;
3382                         DisplayMove(currentMove - 1); /* before DMError */
3383                         DrawPosition(FALSE, boards[currentMove]);
3384                         SwitchClocks(forwardMostMove-1); // [HGM] race
3385                         DisplayBothClocks();
3386                     }
3387                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3388                     ics_user_moved = 0;
3389                     continue;
3390                 }
3391             }
3392
3393             if (looking_at(buf, &i, "still have time") ||
3394                 looking_at(buf, &i, "not out of time") ||
3395                 looking_at(buf, &i, "either player is out of time") ||
3396                 looking_at(buf, &i, "has timeseal; checking")) {
3397                 /* We must have called his flag a little too soon */
3398                 whiteFlag = blackFlag = FALSE;
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "added * seconds to") ||
3403                 looking_at(buf, &i, "seconds were added to")) {
3404                 /* Update the clocks */
3405                 SendToICS(ics_prefix);
3406                 SendToICS("refresh\n");
3407                 continue;
3408             }
3409
3410             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3411                 ics_clock_paused = TRUE;
3412                 StopClocks();
3413                 continue;
3414             }
3415
3416             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3417                 ics_clock_paused = FALSE;
3418                 StartClocks();
3419                 continue;
3420             }
3421
3422             /* Grab player ratings from the Creating: message.
3423                Note we have to check for the special case when
3424                the ICS inserts things like [white] or [black]. */
3425             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3426                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3427                 /* star_matches:
3428                    0    player 1 name (not necessarily white)
3429                    1    player 1 rating
3430                    2    empty, white, or black (IGNORED)
3431                    3    player 2 name (not necessarily black)
3432                    4    player 2 rating
3433                    
3434                    The names/ratings are sorted out when the game
3435                    actually starts (below).
3436                 */
3437                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3438                 player1Rating = string_to_rating(star_match[1]);
3439                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3440                 player2Rating = string_to_rating(star_match[4]);
3441
3442                 if (appData.debugMode)
3443                   fprintf(debugFP, 
3444                           "Ratings from 'Creating:' %s %d, %s %d\n",
3445                           player1Name, player1Rating,
3446                           player2Name, player2Rating);
3447
3448                 continue;
3449             }
3450             
3451             /* Improved generic start/end-of-game messages */
3452             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3453                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3454                 /* If tkind == 0: */
3455                 /* star_match[0] is the game number */
3456                 /*           [1] is the white player's name */
3457                 /*           [2] is the black player's name */
3458                 /* For end-of-game: */
3459                 /*           [3] is the reason for the game end */
3460                 /*           [4] is a PGN end game-token, preceded by " " */
3461                 /* For start-of-game: */
3462                 /*           [3] begins with "Creating" or "Continuing" */
3463                 /*           [4] is " *" or empty (don't care). */
3464                 int gamenum = atoi(star_match[0]);
3465                 char *whitename, *blackname, *why, *endtoken;
3466                 ChessMove endtype = (ChessMove) 0;
3467
3468                 if (tkind == 0) {
3469                   whitename = star_match[1];
3470                   blackname = star_match[2];
3471                   why = star_match[3];
3472                   endtoken = star_match[4];
3473                 } else {
3474                   whitename = star_match[1];
3475                   blackname = star_match[3];
3476                   why = star_match[5];
3477                   endtoken = star_match[6];
3478                 }
3479
3480                 /* Game start messages */
3481                 if (strncmp(why, "Creating ", 9) == 0 ||
3482                     strncmp(why, "Continuing ", 11) == 0) {
3483                     gs_gamenum = gamenum;
3484                     strcpy(gs_kind, strchr(why, ' ') + 1);
3485                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3486 #if ZIPPY
3487                     if (appData.zippyPlay) {
3488                         ZippyGameStart(whitename, blackname);
3489                     }
3490 #endif /*ZIPPY*/
3491                     partnerBoardValid = FALSE; // [HGM] bughouse
3492                     continue;
3493                 }
3494
3495                 /* Game end messages */
3496                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3497                     ics_gamenum != gamenum) {
3498                     continue;
3499                 }
3500                 while (endtoken[0] == ' ') endtoken++;
3501                 switch (endtoken[0]) {
3502                   case '*':
3503                   default:
3504                     endtype = GameUnfinished;
3505                     break;
3506                   case '0':
3507                     endtype = BlackWins;
3508                     break;
3509                   case '1':
3510                     if (endtoken[1] == '/')
3511                       endtype = GameIsDrawn;
3512                     else
3513                       endtype = WhiteWins;
3514                     break;
3515                 }
3516                 GameEnds(endtype, why, GE_ICS);
3517 #if ZIPPY
3518                 if (appData.zippyPlay && first.initDone) {
3519                     ZippyGameEnd(endtype, why);
3520                     if (first.pr == NULL) {
3521                       /* Start the next process early so that we'll
3522                          be ready for the next challenge */
3523                       StartChessProgram(&first);
3524                     }
3525                     /* Send "new" early, in case this command takes
3526                        a long time to finish, so that we'll be ready
3527                        for the next challenge. */
3528                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3529                     Reset(TRUE, TRUE);
3530                 }
3531 #endif /*ZIPPY*/
3532                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "Removing game * from observation") ||
3537                 looking_at(buf, &i, "no longer observing game *") ||
3538                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3539                 if (gameMode == IcsObserving &&
3540                     atoi(star_match[0]) == ics_gamenum)
3541                   {
3542                       /* icsEngineAnalyze */
3543                       if (appData.icsEngineAnalyze) {
3544                             ExitAnalyzeMode();
3545                             ModeHighlight();
3546                       }
3547                       StopClocks();
3548                       gameMode = IcsIdle;
3549                       ics_gamenum = -1;
3550                       ics_user_moved = FALSE;
3551                   }
3552                 continue;
3553             }
3554
3555             if (looking_at(buf, &i, "no longer examining game *")) {
3556                 if (gameMode == IcsExamining &&
3557                     atoi(star_match[0]) == ics_gamenum)
3558                   {
3559                       gameMode = IcsIdle;
3560                       ics_gamenum = -1;
3561                       ics_user_moved = FALSE;
3562                   }
3563                 continue;
3564             }
3565
3566             /* Advance leftover_start past any newlines we find,
3567                so only partial lines can get reparsed */
3568             if (looking_at(buf, &i, "\n")) {
3569                 prevColor = curColor;
3570                 if (curColor != ColorNormal) {
3571                     if (oldi > next_out) {
3572                         SendToPlayer(&buf[next_out], oldi - next_out);
3573                         next_out = oldi;
3574                     }
3575                     Colorize(ColorNormal, FALSE);
3576                     curColor = ColorNormal;
3577                 }
3578                 if (started == STARTED_BOARD) {
3579                     started = STARTED_NONE;
3580                     parse[parse_pos] = NULLCHAR;
3581                     ParseBoard12(parse);
3582                     ics_user_moved = 0;
3583
3584                     /* Send premove here */
3585                     if (appData.premove) {
3586                       char str[MSG_SIZ];
3587                       if (currentMove == 0 &&
3588                           gameMode == IcsPlayingWhite &&
3589                           appData.premoveWhite) {
3590                         sprintf(str, "%s\n", appData.premoveWhiteText);
3591                         if (appData.debugMode)
3592                           fprintf(debugFP, "Sending premove:\n");
3593                         SendToICS(str);
3594                       } else if (currentMove == 1 &&
3595                                  gameMode == IcsPlayingBlack &&
3596                                  appData.premoveBlack) {
3597                         sprintf(str, "%s\n", appData.premoveBlackText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (gotPremove) {
3602                         gotPremove = 0;
3603                         ClearPremoveHighlights();
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                           UserMoveEvent(premoveFromX, premoveFromY, 
3607                                         premoveToX, premoveToY, 
3608                                         premovePromoChar);
3609                       }
3610                     }
3611
3612                     /* Usually suppress following prompt */
3613                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3614                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3615                         if (looking_at(buf, &i, "*% ")) {
3616                             savingComment = FALSE;
3617                             suppressKibitz = 0;
3618                         }
3619                     }
3620                     next_out = i;
3621                 } else if (started == STARTED_HOLDINGS) {
3622                     int gamenum;
3623                     char new_piece[MSG_SIZ];
3624                     started = STARTED_NONE;
3625                     parse[parse_pos] = NULLCHAR;
3626                     if (appData.debugMode)
3627                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3628                                                         parse, currentMove);
3629                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3630                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3631                         if (gameInfo.variant == VariantNormal) {
3632                           /* [HGM] We seem to switch variant during a game!
3633                            * Presumably no holdings were displayed, so we have
3634                            * to move the position two files to the right to
3635                            * create room for them!
3636                            */
3637                           VariantClass newVariant;
3638                           switch(gameInfo.boardWidth) { // base guess on board width
3639                                 case 9:  newVariant = VariantShogi; break;
3640                                 case 10: newVariant = VariantGreat; break;
3641                                 default: newVariant = VariantCrazyhouse; break;
3642                           }
3643                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3644                           /* Get a move list just to see the header, which
3645                              will tell us whether this is really bug or zh */
3646                           if (ics_getting_history == H_FALSE) {
3647                             ics_getting_history = H_REQUESTED;
3648                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649                             SendToICS(str);
3650                           }
3651                         }
3652                         new_piece[0] = NULLCHAR;
3653                         sscanf(parse, "game %d white [%s black [%s <- %s",
3654                                &gamenum, white_holding, black_holding,
3655                                new_piece);
3656                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3657                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3658                         /* [HGM] copy holdings to board holdings area */
3659                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3660                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3661                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3662 #if ZIPPY
3663                         if (appData.zippyPlay && first.initDone) {
3664                             ZippyHoldings(white_holding, black_holding,
3665                                           new_piece);
3666                         }
3667 #endif /*ZIPPY*/
3668                         if (tinyLayout || smallLayout) {
3669                             char wh[16], bh[16];
3670                             PackHolding(wh, white_holding);
3671                             PackHolding(bh, black_holding);
3672                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3673                                     gameInfo.white, gameInfo.black);
3674                         } else {
3675                             sprintf(str, "%s [%s] vs. %s [%s]",
3676                                     gameInfo.white, white_holding,
3677                                     gameInfo.black, black_holding);
3678                         }
3679                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3680                         DrawPosition(FALSE, boards[currentMove]);
3681                         DisplayTitle(str);
3682                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3683                         sscanf(parse, "game %d white [%s black [%s <- %s",
3684                                &gamenum, white_holding, black_holding,
3685                                new_piece);
3686                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3687                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3688                         /* [HGM] copy holdings to partner-board holdings area */
3689                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3690                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3691                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3692                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3693                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3694                       }
3695                     }
3696                     /* Suppress following prompt */
3697                     if (looking_at(buf, &i, "*% ")) {
3698                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3699                         savingComment = FALSE;
3700                         suppressKibitz = 0;
3701                     }
3702                     next_out = i;
3703                 }
3704                 continue;
3705             }
3706
3707             i++;                /* skip unparsed character and loop back */
3708         }
3709         
3710         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3711 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3712 //          SendToPlayer(&buf[next_out], i - next_out);
3713             started != STARTED_HOLDINGS && leftover_start > next_out) {
3714             SendToPlayer(&buf[next_out], leftover_start - next_out);
3715             next_out = i;
3716         }
3717         
3718         leftover_len = buf_len - leftover_start;
3719         /* if buffer ends with something we couldn't parse,
3720            reparse it after appending the next read */
3721         
3722     } else if (count == 0) {
3723         RemoveInputSource(isr);
3724         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3725     } else {
3726         DisplayFatalError(_("Error reading from ICS"), error, 1);
3727     }
3728 }
3729
3730
3731 /* Board style 12 looks like this:
3732    
3733    <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
3734    
3735  * The "<12> " is stripped before it gets to this routine.  The two
3736  * trailing 0's (flip state and clock ticking) are later addition, and
3737  * some chess servers may not have them, or may have only the first.
3738  * Additional trailing fields may be added in the future.  
3739  */
3740
3741 #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"
3742
3743 #define RELATION_OBSERVING_PLAYED    0
3744 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3745 #define RELATION_PLAYING_MYMOVE      1
3746 #define RELATION_PLAYING_NOTMYMOVE  -1
3747 #define RELATION_EXAMINING           2
3748 #define RELATION_ISOLATED_BOARD     -3
3749 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3750
3751 void
3752 ParseBoard12(string)
3753      char *string;
3754
3755     GameMode newGameMode;
3756     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3757     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3758     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3759     char to_play, board_chars[200];
3760     char move_str[500], str[500], elapsed_time[500];
3761     char black[32], white[32];
3762     Board board;
3763     int prevMove = currentMove;
3764     int ticking = 2;
3765     ChessMove moveType;
3766     int fromX, fromY, toX, toY;
3767     char promoChar;
3768     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3769     char *bookHit = NULL; // [HGM] book
3770     Boolean weird = FALSE, reqFlag = FALSE;
3771
3772     fromX = fromY = toX = toY = -1;
3773     
3774     newGame = FALSE;
3775
3776     if (appData.debugMode)
3777       fprintf(debugFP, _("Parsing board: %s\n"), string);
3778
3779     move_str[0] = NULLCHAR;
3780     elapsed_time[0] = NULLCHAR;
3781     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3782         int  i = 0, j;
3783         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3784             if(string[i] == ' ') { ranks++; files = 0; }
3785             else files++;
3786             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3787             i++;
3788         }
3789         for(j = 0; j <i; j++) board_chars[j] = string[j];
3790         board_chars[i] = '\0';
3791         string += i + 1;
3792     }
3793     n = sscanf(string, PATTERN, &to_play, &double_push,
3794                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3795                &gamenum, white, black, &relation, &basetime, &increment,
3796                &white_stren, &black_stren, &white_time, &black_time,
3797                &moveNum, str, elapsed_time, move_str, &ics_flip,
3798                &ticking);
3799
3800     if (n < 21) {
3801         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3802         DisplayError(str, 0);
3803         return;
3804     }
3805
3806     /* Convert the move number to internal form */
3807     moveNum = (moveNum - 1) * 2;
3808     if (to_play == 'B') moveNum++;
3809     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3810       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3811                         0, 1);
3812       return;
3813     }
3814     
3815     switch (relation) {
3816       case RELATION_OBSERVING_PLAYED:
3817       case RELATION_OBSERVING_STATIC:
3818         if (gamenum == -1) {
3819             /* Old ICC buglet */
3820             relation = RELATION_OBSERVING_STATIC;
3821         }
3822         newGameMode = IcsObserving;
3823         break;
3824       case RELATION_PLAYING_MYMOVE:
3825       case RELATION_PLAYING_NOTMYMOVE:
3826         newGameMode =
3827           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3828             IcsPlayingWhite : IcsPlayingBlack;
3829         break;
3830       case RELATION_EXAMINING:
3831         newGameMode = IcsExamining;
3832         break;
3833       case RELATION_ISOLATED_BOARD:
3834       default:
3835         /* Just display this board.  If user was doing something else,
3836            we will forget about it until the next board comes. */ 
3837         newGameMode = IcsIdle;
3838         break;
3839       case RELATION_STARTING_POSITION:
3840         newGameMode = gameMode;
3841         break;
3842     }
3843     
3844     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3845          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3846       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3847       char *toSqr;
3848       for (k = 0; k < ranks; k++) {
3849         for (j = 0; j < files; j++)
3850           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3851         if(gameInfo.holdingsWidth > 1) {
3852              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3853              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3854         }
3855       }
3856       CopyBoard(partnerBoard, board);
3857       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3858         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3859         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3860       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3861       if(toSqr = strchr(str, '-')) {
3862         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3863         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3864       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3865       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3866       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3867       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3868       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3869       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3870                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3871       DisplayMessage(partnerStatus, "");
3872         partnerBoardValid = TRUE;
3873       return;
3874     }
3875
3876     /* Modify behavior for initial board display on move listing
3877        of wild games.
3878        */
3879     switch (ics_getting_history) {
3880       case H_FALSE:
3881       case H_REQUESTED:
3882         break;
3883       case H_GOT_REQ_HEADER:
3884       case H_GOT_UNREQ_HEADER:
3885         /* This is the initial position of the current game */
3886         gamenum = ics_gamenum;
3887         moveNum = 0;            /* old ICS bug workaround */
3888         if (to_play == 'B') {
3889           startedFromSetupPosition = TRUE;
3890           blackPlaysFirst = TRUE;
3891           moveNum = 1;
3892           if (forwardMostMove == 0) forwardMostMove = 1;
3893           if (backwardMostMove == 0) backwardMostMove = 1;
3894           if (currentMove == 0) currentMove = 1;
3895         }
3896         newGameMode = gameMode;
3897         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3898         break;
3899       case H_GOT_UNWANTED_HEADER:
3900         /* This is an initial board that we don't want */
3901         return;
3902       case H_GETTING_MOVES:
3903         /* Should not happen */
3904         DisplayError(_("Error gathering move list: extra board"), 0);
3905         ics_getting_history = H_FALSE;
3906         return;
3907     }
3908
3909    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3910                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3911      /* [HGM] We seem to have switched variant unexpectedly
3912       * Try to guess new variant from board size
3913       */
3914           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3915           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3916           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3917           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3918           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3919           if(!weird) newVariant = VariantNormal;
3920           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3921           /* Get a move list just to see the header, which
3922              will tell us whether this is really bug or zh */
3923           if (ics_getting_history == H_FALSE) {
3924             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3925             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3926             SendToICS(str);
3927           }
3928     }
3929     
3930     /* Take action if this is the first board of a new game, or of a
3931        different game than is currently being displayed.  */
3932     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3933         relation == RELATION_ISOLATED_BOARD) {
3934         
3935         /* Forget the old game and get the history (if any) of the new one */
3936         if (gameMode != BeginningOfGame) {
3937           Reset(TRUE, TRUE);
3938         }
3939         newGame = TRUE;
3940         if (appData.autoRaiseBoard) BoardToTop();
3941         prevMove = -3;
3942         if (gamenum == -1) {
3943             newGameMode = IcsIdle;
3944         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3945                    appData.getMoveList && !reqFlag) {
3946             /* Need to get game history */
3947             ics_getting_history = H_REQUESTED;
3948             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3949             SendToICS(str);
3950         }
3951         
3952         /* Initially flip the board to have black on the bottom if playing
3953            black or if the ICS flip flag is set, but let the user change
3954            it with the Flip View button. */
3955         flipView = appData.autoFlipView ? 
3956           (newGameMode == IcsPlayingBlack) || ics_flip :
3957           appData.flipView;
3958         
3959         /* Done with values from previous mode; copy in new ones */
3960         gameMode = newGameMode;
3961         ModeHighlight();
3962         ics_gamenum = gamenum;
3963         if (gamenum == gs_gamenum) {
3964             int klen = strlen(gs_kind);
3965             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3966             sprintf(str, "ICS %s", gs_kind);
3967             gameInfo.event = StrSave(str);
3968         } else {
3969             gameInfo.event = StrSave("ICS game");
3970         }
3971         gameInfo.site = StrSave(appData.icsHost);
3972         gameInfo.date = PGNDate();
3973         gameInfo.round = StrSave("-");
3974         gameInfo.white = StrSave(white);
3975         gameInfo.black = StrSave(black);
3976         timeControl = basetime * 60 * 1000;
3977         timeControl_2 = 0;
3978         timeIncrement = increment * 1000;
3979         movesPerSession = 0;
3980         gameInfo.timeControl = TimeControlTagValue();
3981         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3982   if (appData.debugMode) {
3983     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3984     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3985     setbuf(debugFP, NULL);
3986   }
3987
3988         gameInfo.outOfBook = NULL;
3989         
3990         /* Do we have the ratings? */
3991         if (strcmp(player1Name, white) == 0 &&
3992             strcmp(player2Name, black) == 0) {
3993             if (appData.debugMode)
3994               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3995                       player1Rating, player2Rating);
3996             gameInfo.whiteRating = player1Rating;
3997             gameInfo.blackRating = player2Rating;
3998         } else if (strcmp(player2Name, white) == 0 &&
3999                    strcmp(player1Name, black) == 0) {
4000             if (appData.debugMode)
4001               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4002                       player2Rating, player1Rating);
4003             gameInfo.whiteRating = player2Rating;
4004             gameInfo.blackRating = player1Rating;
4005         }
4006         player1Name[0] = player2Name[0] = NULLCHAR;
4007
4008         /* Silence shouts if requested */
4009         if (appData.quietPlay &&
4010             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4011             SendToICS(ics_prefix);
4012             SendToICS("set shout 0\n");
4013         }
4014     }
4015     
4016     /* Deal with midgame name changes */
4017     if (!newGame) {
4018         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4019             if (gameInfo.white) free(gameInfo.white);
4020             gameInfo.white = StrSave(white);
4021         }
4022         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4023             if (gameInfo.black) free(gameInfo.black);
4024             gameInfo.black = StrSave(black);
4025         }
4026     }
4027     
4028     /* Throw away game result if anything actually changes in examine mode */
4029     if (gameMode == IcsExamining && !newGame) {
4030         gameInfo.result = GameUnfinished;
4031         if (gameInfo.resultDetails != NULL) {
4032             free(gameInfo.resultDetails);
4033             gameInfo.resultDetails = NULL;
4034         }
4035     }
4036     
4037     /* In pausing && IcsExamining mode, we ignore boards coming
4038        in if they are in a different variation than we are. */
4039     if (pauseExamInvalid) return;
4040     if (pausing && gameMode == IcsExamining) {
4041         if (moveNum <= pauseExamForwardMostMove) {
4042             pauseExamInvalid = TRUE;
4043             forwardMostMove = pauseExamForwardMostMove;
4044             return;
4045         }
4046     }
4047     
4048   if (appData.debugMode) {
4049     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4050   }
4051     /* Parse the board */
4052     for (k = 0; k < ranks; k++) {
4053       for (j = 0; j < files; j++)
4054         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4055       if(gameInfo.holdingsWidth > 1) {
4056            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4057            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4058       }
4059     }
4060     CopyBoard(boards[moveNum], board);
4061     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4062     if (moveNum == 0) {
4063         startedFromSetupPosition =
4064           !CompareBoards(board, initialPosition);
4065         if(startedFromSetupPosition)
4066             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4067     }
4068
4069     /* [HGM] Set castling rights. Take the outermost Rooks,
4070        to make it also work for FRC opening positions. Note that board12
4071        is really defective for later FRC positions, as it has no way to
4072        indicate which Rook can castle if they are on the same side of King.
4073        For the initial position we grant rights to the outermost Rooks,
4074        and remember thos rights, and we then copy them on positions
4075        later in an FRC game. This means WB might not recognize castlings with
4076        Rooks that have moved back to their original position as illegal,
4077        but in ICS mode that is not its job anyway.
4078     */
4079     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4080     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4081
4082         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4083             if(board[0][i] == WhiteRook) j = i;
4084         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4085         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4086             if(board[0][i] == WhiteRook) j = i;
4087         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4088         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4089             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4090         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4091         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4092             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4093         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4094
4095         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4096         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4097             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4098         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4099             if(board[BOARD_HEIGHT-1][k] == bKing)
4100                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4101         if(gameInfo.variant == VariantTwoKings) {
4102             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4103             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4104             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4105         }
4106     } else { int r;
4107         r = boards[moveNum][CASTLING][0] = initialRights[0];
4108         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4109         r = boards[moveNum][CASTLING][1] = initialRights[1];
4110         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4111         r = boards[moveNum][CASTLING][3] = initialRights[3];
4112         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4113         r = boards[moveNum][CASTLING][4] = initialRights[4];
4114         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4115         /* wildcastle kludge: always assume King has rights */
4116         r = boards[moveNum][CASTLING][2] = initialRights[2];
4117         r = boards[moveNum][CASTLING][5] = initialRights[5];
4118     }
4119     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4120     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4121
4122     
4123     if (ics_getting_history == H_GOT_REQ_HEADER ||
4124         ics_getting_history == H_GOT_UNREQ_HEADER) {
4125         /* This was an initial position from a move list, not
4126            the current position */
4127         return;
4128     }
4129     
4130     /* Update currentMove and known move number limits */
4131     newMove = newGame || moveNum > forwardMostMove;
4132
4133     if (newGame) {
4134         forwardMostMove = backwardMostMove = currentMove = moveNum;
4135         if (gameMode == IcsExamining && moveNum == 0) {
4136           /* Workaround for ICS limitation: we are not told the wild
4137              type when starting to examine a game.  But if we ask for
4138              the move list, the move list header will tell us */
4139             ics_getting_history = H_REQUESTED;
4140             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4141             SendToICS(str);
4142         }
4143     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4144                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4145 #if ZIPPY
4146         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4147         /* [HGM] applied this also to an engine that is silently watching        */
4148         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4149             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4150             gameInfo.variant == currentlyInitializedVariant) {
4151           takeback = forwardMostMove - moveNum;
4152           for (i = 0; i < takeback; i++) {
4153             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4154             SendToProgram("undo\n", &first);
4155           }
4156         }
4157 #endif
4158
4159         forwardMostMove = moveNum;
4160         if (!pausing || currentMove > forwardMostMove)
4161           currentMove = forwardMostMove;
4162     } else {
4163         /* New part of history that is not contiguous with old part */ 
4164         if (pausing && gameMode == IcsExamining) {
4165             pauseExamInvalid = TRUE;
4166             forwardMostMove = pauseExamForwardMostMove;
4167             return;
4168         }
4169         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4170 #if ZIPPY
4171             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4172                 // [HGM] when we will receive the move list we now request, it will be
4173                 // fed to the engine from the first move on. So if the engine is not
4174                 // in the initial position now, bring it there.
4175                 InitChessProgram(&first, 0);
4176             }
4177 #endif
4178             ics_getting_history = H_REQUESTED;
4179             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4180             SendToICS(str);
4181         }
4182         forwardMostMove = backwardMostMove = currentMove = moveNum;
4183     }
4184     
4185     /* Update the clocks */
4186     if (strchr(elapsed_time, '.')) {
4187       /* Time is in ms */
4188       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4189       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4190     } else {
4191       /* Time is in seconds */
4192       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4193       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4194     }
4195       
4196
4197 #if ZIPPY
4198     if (appData.zippyPlay && newGame &&
4199         gameMode != IcsObserving && gameMode != IcsIdle &&
4200         gameMode != IcsExamining)
4201       ZippyFirstBoard(moveNum, basetime, increment);
4202 #endif
4203     
4204     /* Put the move on the move list, first converting
4205        to canonical algebraic form. */
4206     if (moveNum > 0) {
4207   if (appData.debugMode) {
4208     if (appData.debugMode) { int f = forwardMostMove;
4209         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4210                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4211                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4212     }
4213     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4214     fprintf(debugFP, "moveNum = %d\n", moveNum);
4215     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4216     setbuf(debugFP, NULL);
4217   }
4218         if (moveNum <= backwardMostMove) {
4219             /* We don't know what the board looked like before
4220                this move.  Punt. */
4221             strcpy(parseList[moveNum - 1], move_str);
4222             strcat(parseList[moveNum - 1], " ");
4223             strcat(parseList[moveNum - 1], elapsed_time);
4224             moveList[moveNum - 1][0] = NULLCHAR;
4225         } else if (strcmp(move_str, "none") == 0) {
4226             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4227             /* Again, we don't know what the board looked like;
4228                this is really the start of the game. */
4229             parseList[moveNum - 1][0] = NULLCHAR;
4230             moveList[moveNum - 1][0] = NULLCHAR;
4231             backwardMostMove = moveNum;
4232             startedFromSetupPosition = TRUE;
4233             fromX = fromY = toX = toY = -1;
4234         } else {
4235           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4236           //                 So we parse the long-algebraic move string in stead of the SAN move
4237           int valid; char buf[MSG_SIZ], *prom;
4238
4239           // str looks something like "Q/a1-a2"; kill the slash
4240           if(str[1] == '/') 
4241                 sprintf(buf, "%c%s", str[0], str+2);
4242           else  strcpy(buf, str); // might be castling
4243           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4244                 strcat(buf, prom); // long move lacks promo specification!
4245           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4246                 if(appData.debugMode) 
4247                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4248                 strcpy(move_str, buf);
4249           }
4250           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4251                                 &fromX, &fromY, &toX, &toY, &promoChar)
4252                || ParseOneMove(buf, moveNum - 1, &moveType,
4253                                 &fromX, &fromY, &toX, &toY, &promoChar);
4254           // end of long SAN patch
4255           if (valid) {
4256             (void) CoordsToAlgebraic(boards[moveNum - 1],
4257                                      PosFlags(moveNum - 1),
4258                                      fromY, fromX, toY, toX, promoChar,
4259                                      parseList[moveNum-1]);
4260             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4261               case MT_NONE:
4262               case MT_STALEMATE:
4263               default:
4264                 break;
4265               case MT_CHECK:
4266                 if(gameInfo.variant != VariantShogi)
4267                     strcat(parseList[moveNum - 1], "+");
4268                 break;
4269               case MT_CHECKMATE:
4270               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4271                 strcat(parseList[moveNum - 1], "#");
4272                 break;
4273             }
4274             strcat(parseList[moveNum - 1], " ");
4275             strcat(parseList[moveNum - 1], elapsed_time);
4276             /* currentMoveString is set as a side-effect of ParseOneMove */
4277             strcpy(moveList[moveNum - 1], currentMoveString);
4278             strcat(moveList[moveNum - 1], "\n");
4279           } else {
4280             /* Move from ICS was illegal!?  Punt. */
4281   if (appData.debugMode) {
4282     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4283     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4284   }
4285             strcpy(parseList[moveNum - 1], move_str);
4286             strcat(parseList[moveNum - 1], " ");
4287             strcat(parseList[moveNum - 1], elapsed_time);
4288             moveList[moveNum - 1][0] = NULLCHAR;
4289             fromX = fromY = toX = toY = -1;
4290           }
4291         }
4292   if (appData.debugMode) {
4293     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4294     setbuf(debugFP, NULL);
4295   }
4296
4297 #if ZIPPY
4298         /* Send move to chess program (BEFORE animating it). */
4299         if (appData.zippyPlay && !newGame && newMove && 
4300            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4301
4302             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4303                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4304                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4305                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4306                             move_str);
4307                     DisplayError(str, 0);
4308                 } else {
4309                     if (first.sendTime) {
4310                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4311                     }
4312                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4313                     if (firstMove && !bookHit) {
4314                         firstMove = FALSE;
4315                         if (first.useColors) {
4316                           SendToProgram(gameMode == IcsPlayingWhite ?
4317                                         "white\ngo\n" :
4318                                         "black\ngo\n", &first);
4319                         } else {
4320                           SendToProgram("go\n", &first);
4321                         }
4322                         first.maybeThinking = TRUE;
4323                     }
4324                 }
4325             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4326               if (moveList[moveNum - 1][0] == NULLCHAR) {
4327                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4328                 DisplayError(str, 0);
4329               } else {
4330                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4331                 SendMoveToProgram(moveNum - 1, &first);
4332               }
4333             }
4334         }
4335 #endif
4336     }
4337
4338     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4339         /* If move comes from a remote source, animate it.  If it
4340            isn't remote, it will have already been animated. */
4341         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4342             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4343         }
4344         if (!pausing && appData.highlightLastMove) {
4345             SetHighlights(fromX, fromY, toX, toY);
4346         }
4347     }
4348     
4349     /* Start the clocks */
4350     whiteFlag = blackFlag = FALSE;
4351     appData.clockMode = !(basetime == 0 && increment == 0);
4352     if (ticking == 0) {
4353       ics_clock_paused = TRUE;
4354       StopClocks();
4355     } else if (ticking == 1) {
4356       ics_clock_paused = FALSE;
4357     }
4358     if (gameMode == IcsIdle ||
4359         relation == RELATION_OBSERVING_STATIC ||
4360         relation == RELATION_EXAMINING ||
4361         ics_clock_paused)
4362       DisplayBothClocks();
4363     else
4364       StartClocks();
4365     
4366     /* Display opponents and material strengths */
4367     if (gameInfo.variant != VariantBughouse &&
4368         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4369         if (tinyLayout || smallLayout) {
4370             if(gameInfo.variant == VariantNormal)
4371                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4372                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4373                     basetime, increment);
4374             else
4375                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4376                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4377                     basetime, increment, (int) gameInfo.variant);
4378         } else {
4379             if(gameInfo.variant == VariantNormal)
4380                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4381                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4382                     basetime, increment);
4383             else
4384                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment, VariantName(gameInfo.variant));
4387         }
4388         DisplayTitle(str);
4389   if (appData.debugMode) {
4390     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4391   }
4392     }
4393
4394
4395     /* Display the board */
4396     if (!pausing && !appData.noGUI) {
4397       
4398       if (appData.premove)
4399           if (!gotPremove || 
4400              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4401              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4402               ClearPremoveHighlights();
4403
4404       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4405         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4406       DrawPosition(j, boards[currentMove]);
4407
4408       DisplayMove(moveNum - 1);
4409       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4410             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4411               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4412         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4413       }
4414     }
4415
4416     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4417 #if ZIPPY
4418     if(bookHit) { // [HGM] book: simulate book reply
4419         static char bookMove[MSG_SIZ]; // a bit generous?
4420
4421         programStats.nodes = programStats.depth = programStats.time = 
4422         programStats.score = programStats.got_only_move = 0;
4423         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4424
4425         strcpy(bookMove, "move ");
4426         strcat(bookMove, bookHit);
4427         HandleMachineMove(bookMove, &first);
4428     }
4429 #endif
4430 }
4431
4432 void
4433 GetMoveListEvent()
4434 {
4435     char buf[MSG_SIZ];
4436     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4437         ics_getting_history = H_REQUESTED;
4438         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4439         SendToICS(buf);
4440     }
4441 }
4442
4443 void
4444 AnalysisPeriodicEvent(force)
4445      int force;
4446 {
4447     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4448          && !force) || !appData.periodicUpdates)
4449       return;
4450
4451     /* Send . command to Crafty to collect stats */
4452     SendToProgram(".\n", &first);
4453
4454     /* Don't send another until we get a response (this makes
4455        us stop sending to old Crafty's which don't understand
4456        the "." command (sending illegal cmds resets node count & time,
4457        which looks bad)) */
4458     programStats.ok_to_send = 0;
4459 }
4460
4461 void ics_update_width(new_width)
4462         int new_width;
4463 {
4464         ics_printf("set width %d\n", new_width);
4465 }
4466
4467 void
4468 SendMoveToProgram(moveNum, cps)
4469      int moveNum;
4470      ChessProgramState *cps;
4471 {
4472     char buf[MSG_SIZ];
4473
4474     if (cps->useUsermove) {
4475       SendToProgram("usermove ", cps);
4476     }
4477     if (cps->useSAN) {
4478       char *space;
4479       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4480         int len = space - parseList[moveNum];
4481         memcpy(buf, parseList[moveNum], len);
4482         buf[len++] = '\n';
4483         buf[len] = NULLCHAR;
4484       } else {
4485         sprintf(buf, "%s\n", parseList[moveNum]);
4486       }
4487       SendToProgram(buf, cps);
4488     } else {
4489       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4490         AlphaRank(moveList[moveNum], 4);
4491         SendToProgram(moveList[moveNum], cps);
4492         AlphaRank(moveList[moveNum], 4); // and back
4493       } else
4494       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4495        * the engine. It would be nice to have a better way to identify castle 
4496        * moves here. */
4497       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4498                                                                          && cps->useOOCastle) {
4499         int fromX = moveList[moveNum][0] - AAA; 
4500         int fromY = moveList[moveNum][1] - ONE;
4501         int toX = moveList[moveNum][2] - AAA; 
4502         int toY = moveList[moveNum][3] - ONE;
4503         if((boards[moveNum][fromY][fromX] == WhiteKing 
4504             && boards[moveNum][toY][toX] == WhiteRook)
4505            || (boards[moveNum][fromY][fromX] == BlackKing 
4506                && boards[moveNum][toY][toX] == BlackRook)) {
4507           if(toX > fromX) SendToProgram("O-O\n", cps);
4508           else SendToProgram("O-O-O\n", cps);
4509         }
4510         else SendToProgram(moveList[moveNum], cps);
4511       }
4512       else SendToProgram(moveList[moveNum], cps);
4513       /* End of additions by Tord */
4514     }
4515
4516     /* [HGM] setting up the opening has brought engine in force mode! */
4517     /*       Send 'go' if we are in a mode where machine should play. */
4518     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4519         (gameMode == TwoMachinesPlay   ||
4520 #ifdef ZIPPY
4521          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4522 #endif
4523          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4524         SendToProgram("go\n", cps);
4525   if (appData.debugMode) {
4526     fprintf(debugFP, "(extra)\n");
4527   }
4528     }
4529     setboardSpoiledMachineBlack = 0;
4530 }
4531
4532 void
4533 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4534      ChessMove moveType;
4535      int fromX, fromY, toX, toY;
4536 {
4537     char user_move[MSG_SIZ];
4538
4539     switch (moveType) {
4540       default:
4541         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4542                 (int)moveType, fromX, fromY, toX, toY);
4543         DisplayError(user_move + strlen("say "), 0);
4544         break;
4545       case WhiteKingSideCastle:
4546       case BlackKingSideCastle:
4547       case WhiteQueenSideCastleWild:
4548       case BlackQueenSideCastleWild:
4549       /* PUSH Fabien */
4550       case WhiteHSideCastleFR:
4551       case BlackHSideCastleFR:
4552       /* POP Fabien */
4553         sprintf(user_move, "o-o\n");
4554         break;
4555       case WhiteQueenSideCastle:
4556       case BlackQueenSideCastle:
4557       case WhiteKingSideCastleWild:
4558       case BlackKingSideCastleWild:
4559       /* PUSH Fabien */
4560       case WhiteASideCastleFR:
4561       case BlackASideCastleFR:
4562       /* POP Fabien */
4563         sprintf(user_move, "o-o-o\n");
4564         break;
4565       case WhitePromotionQueen:
4566       case BlackPromotionQueen:
4567       case WhitePromotionRook:
4568       case BlackPromotionRook:
4569       case WhitePromotionBishop:
4570       case BlackPromotionBishop:
4571       case WhitePromotionKnight:
4572       case BlackPromotionKnight:
4573       case WhitePromotionKing:
4574       case BlackPromotionKing:
4575       case WhitePromotionChancellor:
4576       case BlackPromotionChancellor:
4577       case WhitePromotionArchbishop:
4578       case BlackPromotionArchbishop:
4579         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4580             sprintf(user_move, "%c%c%c%c=%c\n",
4581                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4582                 PieceToChar(WhiteFerz));
4583         else if(gameInfo.variant == VariantGreat)
4584             sprintf(user_move, "%c%c%c%c=%c\n",
4585                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4586                 PieceToChar(WhiteMan));
4587         else
4588             sprintf(user_move, "%c%c%c%c=%c\n",
4589                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4590                 PieceToChar(PromoPiece(moveType)));
4591         break;
4592       case WhiteDrop:
4593       case BlackDrop:
4594         sprintf(user_move, "%c@%c%c\n",
4595                 ToUpper(PieceToChar((ChessSquare) fromX)),
4596                 AAA + toX, ONE + toY);
4597         break;
4598       case NormalMove:
4599       case WhiteCapturesEnPassant:
4600       case BlackCapturesEnPassant:
4601       case IllegalMove:  /* could be a variant we don't quite understand */
4602         sprintf(user_move, "%c%c%c%c\n",
4603                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4604         break;
4605     }
4606     SendToICS(user_move);
4607     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4608         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4609 }
4610
4611 void
4612 UploadGameEvent()
4613 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4614     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4615     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4616     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4617         DisplayError("You cannot do this while you are playing or observing", 0);
4618         return;
4619     }
4620     if(gameMode != IcsExamining) { // is this ever not the case?
4621         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4622
4623         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4624             sprintf(command, "match %s", ics_handle);
4625         } else { // on FICS we must first go to general examine mode
4626             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4627         }
4628         if(gameInfo.variant != VariantNormal) {
4629             // try figure out wild number, as xboard names are not always valid on ICS
4630             for(i=1; i<=36; i++) {
4631                 sprintf(buf, "wild/%d", i);
4632                 if(StringToVariant(buf) == gameInfo.variant) break;
4633             }
4634             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4635             else if(i == 22) sprintf(buf, "%s fr\n", command);
4636             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4637         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4638         SendToICS(ics_prefix);
4639         SendToICS(buf);
4640         if(startedFromSetupPosition || backwardMostMove != 0) {
4641           fen = PositionToFEN(backwardMostMove, NULL);
4642           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4643             sprintf(buf, "loadfen %s\n", fen);
4644             SendToICS(buf);
4645           } else { // FICS: everything has to set by separate bsetup commands
4646             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4647             sprintf(buf, "bsetup fen %s\n", fen);
4648             SendToICS(buf);
4649             if(!WhiteOnMove(backwardMostMove)) {
4650                 SendToICS("bsetup tomove black\n");
4651             }
4652             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4653             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4654             SendToICS(buf);
4655             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4656             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4657             SendToICS(buf);
4658             i = boards[backwardMostMove][EP_STATUS];
4659             if(i >= 0) { // set e.p.
4660                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4661                 SendToICS(buf);
4662             }
4663             bsetup++;
4664           }
4665         }
4666       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4667             SendToICS("bsetup done\n"); // switch to normal examining.
4668     }
4669     for(i = backwardMostMove; i<last; i++) {
4670         char buf[20];
4671         sprintf(buf, "%s\n", parseList[i]);
4672         SendToICS(buf);
4673     }
4674     SendToICS(ics_prefix);
4675     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4676 }
4677
4678 void
4679 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4680      int rf, ff, rt, ft;
4681      char promoChar;
4682      char move[7];
4683 {
4684     if (rf == DROP_RANK) {
4685         sprintf(move, "%c@%c%c\n",
4686                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4687     } else {
4688         if (promoChar == 'x' || promoChar == NULLCHAR) {
4689             sprintf(move, "%c%c%c%c\n",
4690                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4691         } else {
4692             sprintf(move, "%c%c%c%c%c\n",
4693                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4694         }
4695     }
4696 }
4697
4698 void
4699 ProcessICSInitScript(f)
4700      FILE *f;
4701 {
4702     char buf[MSG_SIZ];
4703
4704     while (fgets(buf, MSG_SIZ, f)) {
4705         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4706     }
4707
4708     fclose(f);
4709 }
4710
4711
4712 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4713 void
4714 AlphaRank(char *move, int n)
4715 {
4716 //    char *p = move, c; int x, y;
4717
4718     if (appData.debugMode) {
4719         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4720     }
4721
4722     if(move[1]=='*' && 
4723        move[2]>='0' && move[2]<='9' &&
4724        move[3]>='a' && move[3]<='x'    ) {
4725         move[1] = '@';
4726         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4727         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4728     } else
4729     if(move[0]>='0' && move[0]<='9' &&
4730        move[1]>='a' && move[1]<='x' &&
4731        move[2]>='0' && move[2]<='9' &&
4732        move[3]>='a' && move[3]<='x'    ) {
4733         /* input move, Shogi -> normal */
4734         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4735         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4736         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4737         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4738     } else
4739     if(move[1]=='@' &&
4740        move[3]>='0' && move[3]<='9' &&
4741        move[2]>='a' && move[2]<='x'    ) {
4742         move[1] = '*';
4743         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4745     } else
4746     if(
4747        move[0]>='a' && move[0]<='x' &&
4748        move[3]>='0' && move[3]<='9' &&
4749        move[2]>='a' && move[2]<='x'    ) {
4750          /* output move, normal -> Shogi */
4751         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4752         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4753         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4754         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4755         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4756     }
4757     if (appData.debugMode) {
4758         fprintf(debugFP, "   out = '%s'\n", move);
4759     }
4760 }
4761
4762 char yy_textstr[8000];
4763
4764 /* Parser for moves from gnuchess, ICS, or user typein box */
4765 Boolean
4766 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4767      char *move;
4768      int moveNum;
4769      ChessMove *moveType;
4770      int *fromX, *fromY, *toX, *toY;
4771      char *promoChar;
4772 {       
4773     if (appData.debugMode) {
4774         fprintf(debugFP, "move to parse: %s\n", move);
4775     }
4776     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4777
4778     switch (*moveType) {
4779       case WhitePromotionChancellor:
4780       case BlackPromotionChancellor:
4781       case WhitePromotionArchbishop:
4782       case BlackPromotionArchbishop:
4783       case WhitePromotionQueen:
4784       case BlackPromotionQueen:
4785       case WhitePromotionRook:
4786       case BlackPromotionRook:
4787       case WhitePromotionBishop:
4788       case BlackPromotionBishop:
4789       case WhitePromotionKnight:
4790       case BlackPromotionKnight:
4791       case WhitePromotionKing:
4792       case BlackPromotionKing:
4793       case NormalMove:
4794       case WhiteCapturesEnPassant:
4795       case BlackCapturesEnPassant:
4796       case WhiteKingSideCastle:
4797       case WhiteQueenSideCastle:
4798       case BlackKingSideCastle:
4799       case BlackQueenSideCastle:
4800       case WhiteKingSideCastleWild:
4801       case WhiteQueenSideCastleWild:
4802       case BlackKingSideCastleWild:
4803       case BlackQueenSideCastleWild:
4804       /* Code added by Tord: */
4805       case WhiteHSideCastleFR:
4806       case WhiteASideCastleFR:
4807       case BlackHSideCastleFR:
4808       case BlackASideCastleFR:
4809       /* End of code added by Tord */
4810       case IllegalMove:         /* bug or odd chess variant */
4811         *fromX = currentMoveString[0] - AAA;
4812         *fromY = currentMoveString[1] - ONE;
4813         *toX = currentMoveString[2] - AAA;
4814         *toY = currentMoveString[3] - ONE;
4815         *promoChar = currentMoveString[4];
4816         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4817             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4818     if (appData.debugMode) {
4819         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4820     }
4821             *fromX = *fromY = *toX = *toY = 0;
4822             return FALSE;
4823         }
4824         if (appData.testLegality) {
4825           return (*moveType != IllegalMove);
4826         } else {
4827           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4828                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4829         }
4830
4831       case WhiteDrop:
4832       case BlackDrop:
4833         *fromX = *moveType == WhiteDrop ?
4834           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4835           (int) CharToPiece(ToLower(currentMoveString[0]));
4836         *fromY = DROP_RANK;
4837         *toX = currentMoveString[2] - AAA;
4838         *toY = currentMoveString[3] - ONE;
4839         *promoChar = NULLCHAR;
4840         return TRUE;
4841
4842       case AmbiguousMove:
4843       case ImpossibleMove:
4844       case (ChessMove) 0:       /* end of file */
4845       case ElapsedTime:
4846       case Comment:
4847       case PGNTag:
4848       case NAG:
4849       case WhiteWins:
4850       case BlackWins:
4851       case GameIsDrawn:
4852       default:
4853     if (appData.debugMode) {
4854         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4855     }
4856         /* bug? */
4857         *fromX = *fromY = *toX = *toY = 0;
4858         *promoChar = NULLCHAR;
4859         return FALSE;
4860     }
4861 }
4862
4863
4864 void
4865 ParsePV(char *pv, Boolean storeComments)
4866 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4867   int fromX, fromY, toX, toY; char promoChar;
4868   ChessMove moveType;
4869   Boolean valid;
4870   int nr = 0;
4871
4872   endPV = forwardMostMove;
4873   do {
4874     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4875     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4876     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4877 if(appData.debugMode){
4878 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);
4879 }
4880     if(!valid && nr == 0 &&
4881        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4882         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4883         // Hande case where played move is different from leading PV move
4884         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4885         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4886         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4887         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4888           endPV += 2; // if position different, keep this
4889           moveList[endPV-1][0] = fromX + AAA;
4890           moveList[endPV-1][1] = fromY + ONE;
4891           moveList[endPV-1][2] = toX + AAA;
4892           moveList[endPV-1][3] = toY + ONE;
4893           parseList[endPV-1][0] = NULLCHAR;
4894           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4895         }
4896       }
4897     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4898     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4899     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4900     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4901         valid++; // allow comments in PV
4902         continue;
4903     }
4904     nr++;
4905     if(endPV+1 > framePtr) break; // no space, truncate
4906     if(!valid) break;
4907     endPV++;
4908     CopyBoard(boards[endPV], boards[endPV-1]);
4909     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4910     moveList[endPV-1][0] = fromX + AAA;
4911     moveList[endPV-1][1] = fromY + ONE;
4912     moveList[endPV-1][2] = toX + AAA;
4913     moveList[endPV-1][3] = toY + ONE;
4914     if(storeComments)
4915         CoordsToAlgebraic(boards[endPV - 1],
4916                              PosFlags(endPV - 1),
4917                              fromY, fromX, toY, toX, promoChar,
4918                              parseList[endPV - 1]);
4919     else
4920         parseList[endPV-1][0] = NULLCHAR;
4921   } while(valid);
4922   currentMove = endPV;
4923   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4924   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4925                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4926   DrawPosition(TRUE, boards[currentMove]);
4927 }
4928
4929 static int lastX, lastY;
4930
4931 Boolean
4932 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4933 {
4934         int startPV;
4935         char *p;
4936
4937         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4938         lastX = x; lastY = y;
4939         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4940         startPV = index;
4941         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4942         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4943         index = startPV;
4944         do{ while(buf[index] && buf[index] != '\n') index++;
4945         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4946         buf[index] = 0;
4947         ParsePV(buf+startPV, FALSE);
4948         *start = startPV; *end = index-1;
4949         return TRUE;
4950 }
4951
4952 Boolean
4953 LoadPV(int x, int y)
4954 { // called on right mouse click to load PV
4955   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4956   lastX = x; lastY = y;
4957   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4958   return TRUE;
4959 }
4960
4961 void
4962 UnLoadPV()
4963 {
4964   if(endPV < 0) return;
4965   endPV = -1;
4966   currentMove = forwardMostMove;
4967   ClearPremoveHighlights();
4968   DrawPosition(TRUE, boards[currentMove]);
4969 }
4970
4971 void
4972 MovePV(int x, int y, int h)
4973 { // step through PV based on mouse coordinates (called on mouse move)
4974   int margin = h>>3, step = 0;
4975
4976   if(endPV < 0) return;
4977   // we must somehow check if right button is still down (might be released off board!)
4978   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4979   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4980   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4981   if(!step) return;
4982   lastX = x; lastY = y;
4983   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4984   currentMove += step;
4985   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4986   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4987                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4988   DrawPosition(FALSE, boards[currentMove]);
4989 }
4990
4991
4992 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4993 // All positions will have equal probability, but the current method will not provide a unique
4994 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4995 #define DARK 1
4996 #define LITE 2
4997 #define ANY 3
4998
4999 int squaresLeft[4];
5000 int piecesLeft[(int)BlackPawn];
5001 int seed, nrOfShuffles;
5002
5003 void GetPositionNumber()
5004 {       // sets global variable seed
5005         int i;
5006
5007         seed = appData.defaultFrcPosition;
5008         if(seed < 0) { // randomize based on time for negative FRC position numbers
5009                 for(i=0; i<50; i++) seed += random();
5010                 seed = random() ^ random() >> 8 ^ random() << 8;
5011                 if(seed<0) seed = -seed;
5012         }
5013 }
5014
5015 int put(Board board, int pieceType, int rank, int n, int shade)
5016 // put the piece on the (n-1)-th empty squares of the given shade
5017 {
5018         int i;
5019
5020         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5021                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5022                         board[rank][i] = (ChessSquare) pieceType;
5023                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5024                         squaresLeft[ANY]--;
5025                         piecesLeft[pieceType]--; 
5026                         return i;
5027                 }
5028         }
5029         return -1;
5030 }
5031
5032
5033 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5034 // calculate where the next piece goes, (any empty square), and put it there
5035 {
5036         int i;
5037
5038         i = seed % squaresLeft[shade];
5039         nrOfShuffles *= squaresLeft[shade];
5040         seed /= squaresLeft[shade];
5041         put(board, pieceType, rank, i, shade);
5042 }
5043
5044 void AddTwoPieces(Board board, int pieceType, int rank)
5045 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5046 {
5047         int i, n=squaresLeft[ANY], j=n-1, k;
5048
5049         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5050         i = seed % k;  // pick one
5051         nrOfShuffles *= k;
5052         seed /= k;
5053         while(i >= j) i -= j--;
5054         j = n - 1 - j; i += j;
5055         put(board, pieceType, rank, j, ANY);
5056         put(board, pieceType, rank, i, ANY);
5057 }
5058
5059 void SetUpShuffle(Board board, int number)
5060 {
5061         int i, p, first=1;
5062
5063         GetPositionNumber(); nrOfShuffles = 1;
5064
5065         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5066         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5067         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5068
5069         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5070
5071         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5072             p = (int) board[0][i];
5073             if(p < (int) BlackPawn) piecesLeft[p] ++;
5074             board[0][i] = EmptySquare;
5075         }
5076
5077         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5078             // shuffles restricted to allow normal castling put KRR first
5079             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5080                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5081             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5082                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5083             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5084                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5085             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5086                 put(board, WhiteRook, 0, 0, ANY);
5087             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5088         }
5089
5090         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5091             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5092             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5093                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5094                 while(piecesLeft[p] >= 2) {
5095                     AddOnePiece(board, p, 0, LITE);
5096                     AddOnePiece(board, p, 0, DARK);
5097                 }
5098                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5099             }
5100
5101         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5102             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5103             // but we leave King and Rooks for last, to possibly obey FRC restriction
5104             if(p == (int)WhiteRook) continue;
5105             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5106             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5107         }
5108
5109         // now everything is placed, except perhaps King (Unicorn) and Rooks
5110
5111         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5112             // Last King gets castling rights
5113             while(piecesLeft[(int)WhiteUnicorn]) {
5114                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5115                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5116             }
5117
5118             while(piecesLeft[(int)WhiteKing]) {
5119                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5120                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5121             }
5122
5123
5124         } else {
5125             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5126             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5127         }
5128
5129         // Only Rooks can be left; simply place them all
5130         while(piecesLeft[(int)WhiteRook]) {
5131                 i = put(board, WhiteRook, 0, 0, ANY);
5132                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5133                         if(first) {
5134                                 first=0;
5135                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5136                         }
5137                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5138                 }
5139         }
5140         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5141             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5142         }
5143
5144         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5145 }
5146
5147 int SetCharTable( char *table, const char * map )
5148 /* [HGM] moved here from winboard.c because of its general usefulness */
5149 /*       Basically a safe strcpy that uses the last character as King */
5150 {
5151     int result = FALSE; int NrPieces;
5152
5153     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5154                     && NrPieces >= 12 && !(NrPieces&1)) {
5155         int i; /* [HGM] Accept even length from 12 to 34 */
5156
5157         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5158         for( i=0; i<NrPieces/2-1; i++ ) {
5159             table[i] = map[i];
5160             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5161         }
5162         table[(int) WhiteKing]  = map[NrPieces/2-1];
5163         table[(int) BlackKing]  = map[NrPieces-1];
5164
5165         result = TRUE;
5166     }
5167
5168     return result;
5169 }
5170
5171 void Prelude(Board board)
5172 {       // [HGM] superchess: random selection of exo-pieces
5173         int i, j, k; ChessSquare p; 
5174         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5175
5176         GetPositionNumber(); // use FRC position number
5177
5178         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5179             SetCharTable(pieceToChar, appData.pieceToCharTable);
5180             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5181                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5182         }
5183
5184         j = seed%4;                 seed /= 4; 
5185         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5186         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5187         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5188         j = seed%3 + (seed%3 >= j); seed /= 3; 
5189         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5190         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5191         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5192         j = seed%3;                 seed /= 3; 
5193         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5194         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5195         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5196         j = seed%2 + (seed%2 >= j); seed /= 2; 
5197         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5198         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5199         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5200         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5201         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5202         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5203         put(board, exoPieces[0],    0, 0, ANY);
5204         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5205 }
5206
5207 void
5208 InitPosition(redraw)
5209      int redraw;
5210 {
5211     ChessSquare (* pieces)[BOARD_FILES];
5212     int i, j, pawnRow, overrule,
5213     oldx = gameInfo.boardWidth,
5214     oldy = gameInfo.boardHeight,
5215     oldh = gameInfo.holdingsWidth,
5216     oldv = gameInfo.variant;
5217
5218     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5219
5220     /* [AS] Initialize pv info list [HGM] and game status */
5221     {
5222         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5223             pvInfoList[i].depth = 0;
5224             boards[i][EP_STATUS] = EP_NONE;
5225             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5226         }
5227
5228         initialRulePlies = 0; /* 50-move counter start */
5229
5230         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5231         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5232     }
5233
5234     
5235     /* [HGM] logic here is completely changed. In stead of full positions */
5236     /* the initialized data only consist of the two backranks. The switch */
5237     /* selects which one we will use, which is than copied to the Board   */
5238     /* initialPosition, which for the rest is initialized by Pawns and    */
5239     /* empty squares. This initial position is then copied to boards[0],  */
5240     /* possibly after shuffling, so that it remains available.            */
5241
5242     gameInfo.holdingsWidth = 0; /* default board sizes */
5243     gameInfo.boardWidth    = 8;
5244     gameInfo.boardHeight   = 8;
5245     gameInfo.holdingsSize  = 0;
5246     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5247     for(i=0; i<BOARD_FILES-2; i++)
5248       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5249     initialPosition[EP_STATUS] = EP_NONE;
5250     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5251
5252     switch (gameInfo.variant) {
5253     case VariantFischeRandom:
5254       shuffleOpenings = TRUE;
5255     default:
5256       pieces = FIDEArray;
5257       break;
5258     case VariantShatranj:
5259       pieces = ShatranjArray;
5260       nrCastlingRights = 0;
5261       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5262       break;
5263     case VariantMakruk:
5264       pieces = makrukArray;
5265       nrCastlingRights = 0;
5266       startedFromSetupPosition = TRUE;
5267       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5268       break;
5269     case VariantTwoKings:
5270       pieces = twoKingsArray;
5271       break;
5272     case VariantCapaRandom:
5273       shuffleOpenings = TRUE;
5274     case VariantCapablanca:
5275       pieces = CapablancaArray;
5276       gameInfo.boardWidth = 10;
5277       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5278       break;
5279     case VariantGothic:
5280       pieces = GothicArray;
5281       gameInfo.boardWidth = 10;
5282       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5283       break;
5284     case VariantJanus:
5285       pieces = JanusArray;
5286       gameInfo.boardWidth = 10;
5287       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5288       nrCastlingRights = 6;
5289         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5290         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5291         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5292         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5293         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5294         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5295       break;
5296     case VariantFalcon:
5297       pieces = FalconArray;
5298       gameInfo.boardWidth = 10;
5299       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5300       break;
5301     case VariantXiangqi:
5302       pieces = XiangqiArray;
5303       gameInfo.boardWidth  = 9;
5304       gameInfo.boardHeight = 10;
5305       nrCastlingRights = 0;
5306       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5307       break;
5308     case VariantShogi:
5309       pieces = ShogiArray;
5310       gameInfo.boardWidth  = 9;
5311       gameInfo.boardHeight = 9;
5312       gameInfo.holdingsSize = 7;
5313       nrCastlingRights = 0;
5314       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5315       break;
5316     case VariantCourier:
5317       pieces = CourierArray;
5318       gameInfo.boardWidth  = 12;
5319       nrCastlingRights = 0;
5320       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5321       break;
5322     case VariantKnightmate:
5323       pieces = KnightmateArray;
5324       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5325       break;
5326     case VariantFairy:
5327       pieces = fairyArray;
5328       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5329       break;
5330     case VariantGreat:
5331       pieces = GreatArray;
5332       gameInfo.boardWidth = 10;
5333       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5334       gameInfo.holdingsSize = 8;
5335       break;
5336     case VariantSuper:
5337       pieces = FIDEArray;
5338       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5339       gameInfo.holdingsSize = 8;
5340       startedFromSetupPosition = TRUE;
5341       break;
5342     case VariantCrazyhouse:
5343     case VariantBughouse:
5344       pieces = FIDEArray;
5345       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5346       gameInfo.holdingsSize = 5;
5347       break;
5348     case VariantWildCastle:
5349       pieces = FIDEArray;
5350       /* !!?shuffle with kings guaranteed to be on d or e file */
5351       shuffleOpenings = 1;
5352       break;
5353     case VariantNoCastle:
5354       pieces = FIDEArray;
5355       nrCastlingRights = 0;
5356       /* !!?unconstrained back-rank shuffle */
5357       shuffleOpenings = 1;
5358       break;
5359     }
5360
5361     overrule = 0;
5362     if(appData.NrFiles >= 0) {
5363         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5364         gameInfo.boardWidth = appData.NrFiles;
5365     }
5366     if(appData.NrRanks >= 0) {
5367         gameInfo.boardHeight = appData.NrRanks;
5368     }
5369     if(appData.holdingsSize >= 0) {
5370         i = appData.holdingsSize;
5371         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5372         gameInfo.holdingsSize = i;
5373     }
5374     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5375     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5376         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5377
5378     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5379     if(pawnRow < 1) pawnRow = 1;
5380     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5381
5382     /* User pieceToChar list overrules defaults */
5383     if(appData.pieceToCharTable != NULL)
5384         SetCharTable(pieceToChar, appData.pieceToCharTable);
5385
5386     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5387
5388         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5389             s = (ChessSquare) 0; /* account holding counts in guard band */
5390         for( i=0; i<BOARD_HEIGHT; i++ )
5391             initialPosition[i][j] = s;
5392
5393         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5394         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5395         initialPosition[pawnRow][j] = WhitePawn;
5396         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5397         if(gameInfo.variant == VariantXiangqi) {
5398             if(j&1) {
5399                 initialPosition[pawnRow][j] = 
5400                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5401                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5402                    initialPosition[2][j] = WhiteCannon;
5403                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5404                 }
5405             }
5406         }
5407         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5408     }
5409     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5410
5411             j=BOARD_LEFT+1;
5412             initialPosition[1][j] = WhiteBishop;
5413             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5414             j=BOARD_RGHT-2;
5415             initialPosition[1][j] = WhiteRook;
5416             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5417     }
5418
5419     if( nrCastlingRights == -1) {
5420         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5421         /*       This sets default castling rights from none to normal corners   */
5422         /* Variants with other castling rights must set them themselves above    */
5423         nrCastlingRights = 6;
5424        
5425         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5426         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5427         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5428         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5429         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5430         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5431      }
5432
5433      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5434      if(gameInfo.variant == VariantGreat) { // promotion commoners
5435         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5437         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5439      }
5440   if (appData.debugMode) {
5441     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5442   }
5443     if(shuffleOpenings) {
5444         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5445         startedFromSetupPosition = TRUE;
5446     }
5447     if(startedFromPositionFile) {
5448       /* [HGM] loadPos: use PositionFile for every new game */
5449       CopyBoard(initialPosition, filePosition);
5450       for(i=0; i<nrCastlingRights; i++)
5451           initialRights[i] = filePosition[CASTLING][i];
5452       startedFromSetupPosition = TRUE;
5453     }
5454
5455     CopyBoard(boards[0], initialPosition);
5456
5457     if(oldx != gameInfo.boardWidth ||
5458        oldy != gameInfo.boardHeight ||
5459        oldh != gameInfo.holdingsWidth
5460 #ifdef GOTHIC
5461        || oldv == VariantGothic ||        // For licensing popups
5462        gameInfo.variant == VariantGothic
5463 #endif
5464 #ifdef FALCON
5465        || oldv == VariantFalcon ||
5466        gameInfo.variant == VariantFalcon
5467 #endif
5468                                          )
5469             InitDrawingSizes(-2 ,0);
5470
5471     if (redraw)
5472       DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 SendBoard(cps, moveNum)
5477      ChessProgramState *cps;
5478      int moveNum;
5479 {
5480     char message[MSG_SIZ];
5481     
5482     if (cps->useSetboard) {
5483       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5484       sprintf(message, "setboard %s\n", fen);
5485       SendToProgram(message, cps);
5486       free(fen);
5487
5488     } else {
5489       ChessSquare *bp;
5490       int i, j;
5491       /* Kludge to set black to move, avoiding the troublesome and now
5492        * deprecated "black" command.
5493        */
5494       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5495
5496       SendToProgram("edit\n", cps);
5497       SendToProgram("#\n", cps);
5498       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5499         bp = &boards[moveNum][i][BOARD_LEFT];
5500         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5501           if ((int) *bp < (int) BlackPawn) {
5502             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5503                     AAA + j, ONE + i);
5504             if(message[0] == '+' || message[0] == '~') {
5505                 sprintf(message, "%c%c%c+\n",
5506                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5507                         AAA + j, ONE + i);
5508             }
5509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5510                 message[1] = BOARD_RGHT   - 1 - j + '1';
5511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5512             }
5513             SendToProgram(message, cps);
5514           }
5515         }
5516       }
5517     
5518       SendToProgram("c\n", cps);
5519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5520         bp = &boards[moveNum][i][BOARD_LEFT];
5521         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5522           if (((int) *bp != (int) EmptySquare)
5523               && ((int) *bp >= (int) BlackPawn)) {
5524             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5525                     AAA + j, ONE + i);
5526             if(message[0] == '+' || message[0] == '~') {
5527                 sprintf(message, "%c%c%c+\n",
5528                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5529                         AAA + j, ONE + i);
5530             }
5531             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5532                 message[1] = BOARD_RGHT   - 1 - j + '1';
5533                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5534             }
5535             SendToProgram(message, cps);
5536           }
5537         }
5538       }
5539     
5540       SendToProgram(".\n", cps);
5541     }
5542     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5543 }
5544
5545 static int autoQueen; // [HGM] oneclick
5546
5547 int
5548 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5549 {
5550     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5551     /* [HGM] add Shogi promotions */
5552     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5553     ChessSquare piece;
5554     ChessMove moveType;
5555     Boolean premove;
5556
5557     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5558     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5559
5560     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5561       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5562         return FALSE;
5563
5564     piece = boards[currentMove][fromY][fromX];
5565     if(gameInfo.variant == VariantShogi) {
5566         promotionZoneSize = 3;
5567         highestPromotingPiece = (int)WhiteFerz;
5568     } else if(gameInfo.variant == VariantMakruk) {
5569         promotionZoneSize = 3;
5570     }
5571
5572     // next weed out all moves that do not touch the promotion zone at all
5573     if((int)piece >= BlackPawn) {
5574         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5575              return FALSE;
5576         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5577     } else {
5578         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5579            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5580     }
5581
5582     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5583
5584     // weed out mandatory Shogi promotions
5585     if(gameInfo.variant == VariantShogi) {
5586         if(piece >= BlackPawn) {
5587             if(toY == 0 && piece == BlackPawn ||
5588                toY == 0 && piece == BlackQueen ||
5589                toY <= 1 && piece == BlackKnight) {
5590                 *promoChoice = '+';
5591                 return FALSE;
5592             }
5593         } else {
5594             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5595                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5596                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5597                 *promoChoice = '+';
5598                 return FALSE;
5599             }
5600         }
5601     }
5602
5603     // weed out obviously illegal Pawn moves
5604     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5605         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5606         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5607         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5608         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5609         // note we are not allowed to test for valid (non-)capture, due to premove
5610     }
5611
5612     // we either have a choice what to promote to, or (in Shogi) whether to promote
5613     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5614         *promoChoice = PieceToChar(BlackFerz);  // no choice
5615         return FALSE;
5616     }
5617     if(autoQueen) { // predetermined
5618         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5619              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5620         else *promoChoice = PieceToChar(BlackQueen);
5621         return FALSE;
5622     }
5623
5624     // suppress promotion popup on illegal moves that are not premoves
5625     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5626               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5627     if(appData.testLegality && !premove) {
5628         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5629                         fromY, fromX, toY, toX, NULLCHAR);
5630         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5631            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5632             return FALSE;
5633     }
5634
5635     return TRUE;
5636 }
5637
5638 int
5639 InPalace(row, column)
5640      int row, column;
5641 {   /* [HGM] for Xiangqi */
5642     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5643          column < (BOARD_WIDTH + 4)/2 &&
5644          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5645     return FALSE;
5646 }
5647
5648 int
5649 PieceForSquare (x, y)
5650      int x;
5651      int y;
5652 {
5653   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5654      return -1;
5655   else
5656      return boards[currentMove][y][x];
5657 }
5658
5659 int
5660 OKToStartUserMove(x, y)
5661      int x, y;
5662 {
5663     ChessSquare from_piece;
5664     int white_piece;
5665
5666     if (matchMode) return FALSE;
5667     if (gameMode == EditPosition) return TRUE;
5668
5669     if (x >= 0 && y >= 0)
5670       from_piece = boards[currentMove][y][x];
5671     else
5672       from_piece = EmptySquare;
5673
5674     if (from_piece == EmptySquare) return FALSE;
5675
5676     white_piece = (int)from_piece >= (int)WhitePawn &&
5677       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5678
5679     switch (gameMode) {
5680       case PlayFromGameFile:
5681       case AnalyzeFile:
5682       case TwoMachinesPlay:
5683       case EndOfGame:
5684         return FALSE;
5685
5686       case IcsObserving:
5687       case IcsIdle:
5688         return FALSE;
5689
5690       case MachinePlaysWhite:
5691       case IcsPlayingBlack:
5692         if (appData.zippyPlay) return FALSE;
5693         if (white_piece) {
5694             DisplayMoveError(_("You are playing Black"));
5695             return FALSE;
5696         }
5697         break;
5698
5699       case MachinePlaysBlack:
5700       case IcsPlayingWhite:
5701         if (appData.zippyPlay) return FALSE;
5702         if (!white_piece) {
5703             DisplayMoveError(_("You are playing White"));
5704             return FALSE;
5705         }
5706         break;
5707
5708       case EditGame:
5709         if (!white_piece && WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return FALSE;
5712         }           
5713         if (white_piece && !WhiteOnMove(currentMove)) {
5714             DisplayMoveError(_("It is Black's turn"));
5715             return FALSE;
5716         }           
5717         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5718             /* Editing correspondence game history */
5719             /* Could disallow this or prompt for confirmation */
5720             cmailOldMove = -1;
5721         }
5722         break;
5723
5724       case BeginningOfGame:
5725         if (appData.icsActive) return FALSE;
5726         if (!appData.noChessProgram) {
5727             if (!white_piece) {
5728                 DisplayMoveError(_("You are playing White"));
5729                 return FALSE;
5730             }
5731         }
5732         break;
5733         
5734       case Training:
5735         if (!white_piece && WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is White's turn"));
5737             return FALSE;
5738         }           
5739         if (white_piece && !WhiteOnMove(currentMove)) {
5740             DisplayMoveError(_("It is Black's turn"));
5741             return FALSE;
5742         }           
5743         break;
5744
5745       default:
5746       case IcsExamining:
5747         break;
5748     }
5749     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5750         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5751         && gameMode != AnalyzeFile && gameMode != Training) {
5752         DisplayMoveError(_("Displayed position is not current"));
5753         return FALSE;
5754     }
5755     return TRUE;
5756 }
5757
5758 Boolean
5759 OnlyMove(int *x, int *y, Boolean captures) {
5760     DisambiguateClosure cl;
5761     if (appData.zippyPlay) return FALSE;
5762     switch(gameMode) {
5763       case MachinePlaysBlack:
5764       case IcsPlayingWhite:
5765       case BeginningOfGame:
5766         if(!WhiteOnMove(currentMove)) return FALSE;
5767         break;
5768       case MachinePlaysWhite:
5769       case IcsPlayingBlack:
5770         if(WhiteOnMove(currentMove)) return FALSE;
5771         break;
5772       default:
5773         return FALSE;
5774     }
5775     cl.pieceIn = EmptySquare; 
5776     cl.rfIn = *y;
5777     cl.ffIn = *x;
5778     cl.rtIn = -1;
5779     cl.ftIn = -1;
5780     cl.promoCharIn = NULLCHAR;
5781     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5782     if( cl.kind == NormalMove ||
5783         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5784         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5785         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5786         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5787       fromX = cl.ff;
5788       fromY = cl.rf;
5789       *x = cl.ft;
5790       *y = cl.rt;
5791       return TRUE;
5792     }
5793     if(cl.kind != ImpossibleMove) return FALSE;
5794     cl.pieceIn = EmptySquare;
5795     cl.rfIn = -1;
5796     cl.ffIn = -1;
5797     cl.rtIn = *y;
5798     cl.ftIn = *x;
5799     cl.promoCharIn = NULLCHAR;
5800     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5801     if( cl.kind == NormalMove ||
5802         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5803         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5804         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5805         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5806       fromX = cl.ff;
5807       fromY = cl.rf;
5808       *x = cl.ft;
5809       *y = cl.rt;
5810       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5811       return TRUE;
5812     }
5813     return FALSE;
5814 }
5815
5816 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5817 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5818 int lastLoadGameUseList = FALSE;
5819 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5820 ChessMove lastLoadGameStart = (ChessMove) 0;
5821
5822 ChessMove
5823 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5824      int fromX, fromY, toX, toY;
5825      int promoChar;
5826      Boolean captureOwn;
5827 {
5828     ChessMove moveType;
5829     ChessSquare pdown, pup;
5830
5831     /* Check if the user is playing in turn.  This is complicated because we
5832        let the user "pick up" a piece before it is his turn.  So the piece he
5833        tried to pick up may have been captured by the time he puts it down!
5834        Therefore we use the color the user is supposed to be playing in this
5835        test, not the color of the piece that is currently on the starting
5836        square---except in EditGame mode, where the user is playing both
5837        sides; fortunately there the capture race can't happen.  (It can
5838        now happen in IcsExamining mode, but that's just too bad.  The user
5839        will get a somewhat confusing message in that case.)
5840        */
5841
5842     switch (gameMode) {
5843       case PlayFromGameFile:
5844       case AnalyzeFile:
5845       case TwoMachinesPlay:
5846       case EndOfGame:
5847       case IcsObserving:
5848       case IcsIdle:
5849         /* We switched into a game mode where moves are not accepted,
5850            perhaps while the mouse button was down. */
5851         return ImpossibleMove;
5852
5853       case MachinePlaysWhite:
5854         /* User is moving for Black */
5855         if (WhiteOnMove(currentMove)) {
5856             DisplayMoveError(_("It is White's turn"));
5857             return ImpossibleMove;
5858         }
5859         break;
5860
5861       case MachinePlaysBlack:
5862         /* User is moving for White */
5863         if (!WhiteOnMove(currentMove)) {
5864             DisplayMoveError(_("It is Black's turn"));
5865             return ImpossibleMove;
5866         }
5867         break;
5868
5869       case EditGame:
5870       case IcsExamining:
5871       case BeginningOfGame:
5872       case AnalyzeMode:
5873       case Training:
5874         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5875             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5876             /* User is moving for Black */
5877             if (WhiteOnMove(currentMove)) {
5878                 DisplayMoveError(_("It is White's turn"));
5879                 return ImpossibleMove;
5880             }
5881         } else {
5882             /* User is moving for White */
5883             if (!WhiteOnMove(currentMove)) {
5884                 DisplayMoveError(_("It is Black's turn"));
5885                 return ImpossibleMove;
5886             }
5887         }
5888         break;
5889
5890       case IcsPlayingBlack:
5891         /* User is moving for Black */
5892         if (WhiteOnMove(currentMove)) {
5893             if (!appData.premove) {
5894                 DisplayMoveError(_("It is White's turn"));
5895             } else if (toX >= 0 && toY >= 0) {
5896                 premoveToX = toX;
5897                 premoveToY = toY;
5898                 premoveFromX = fromX;
5899                 premoveFromY = fromY;
5900                 premovePromoChar = promoChar;
5901                 gotPremove = 1;
5902                 if (appData.debugMode) 
5903                     fprintf(debugFP, "Got premove: fromX %d,"
5904                             "fromY %d, toX %d, toY %d\n",
5905                             fromX, fromY, toX, toY);
5906             }
5907             return ImpossibleMove;
5908         }
5909         break;
5910
5911       case IcsPlayingWhite:
5912         /* User is moving for White */
5913         if (!WhiteOnMove(currentMove)) {
5914             if (!appData.premove) {
5915                 DisplayMoveError(_("It is Black's turn"));
5916             } else if (toX >= 0 && toY >= 0) {
5917                 premoveToX = toX;
5918                 premoveToY = toY;
5919                 premoveFromX = fromX;
5920                 premoveFromY = fromY;
5921                 premovePromoChar = promoChar;
5922                 gotPremove = 1;
5923                 if (appData.debugMode) 
5924                     fprintf(debugFP, "Got premove: fromX %d,"
5925                             "fromY %d, toX %d, toY %d\n",
5926                             fromX, fromY, toX, toY);
5927             }
5928             return ImpossibleMove;
5929         }
5930         break;
5931
5932       default:
5933         break;
5934
5935       case EditPosition:
5936         /* EditPosition, empty square, or different color piece;
5937            click-click move is possible */
5938         if (toX == -2 || toY == -2) {
5939             boards[0][fromY][fromX] = EmptySquare;
5940             return AmbiguousMove;
5941         } else if (toX >= 0 && toY >= 0) {
5942             boards[0][toY][toX] = boards[0][fromY][fromX];
5943             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5944                 if(boards[0][fromY][0] != EmptySquare) {
5945                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5946                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5947                 }
5948             } else
5949             if(fromX == BOARD_RGHT+1) {
5950                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5951                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5952                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5953                 }
5954             } else
5955             boards[0][fromY][fromX] = EmptySquare;
5956             return AmbiguousMove;
5957         }
5958         return ImpossibleMove;
5959     }
5960
5961     if(toX < 0 || toY < 0) return ImpossibleMove;
5962     pdown = boards[currentMove][fromY][fromX];
5963     pup = boards[currentMove][toY][toX];
5964
5965     /* [HGM] If move started in holdings, it means a drop */
5966     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5967          if( pup != EmptySquare ) return ImpossibleMove;
5968          if(appData.testLegality) {
5969              /* it would be more logical if LegalityTest() also figured out
5970               * which drops are legal. For now we forbid pawns on back rank.
5971               * Shogi is on its own here...
5972               */
5973              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5974                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5975                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5976          }
5977          return WhiteDrop; /* Not needed to specify white or black yet */
5978     }
5979
5980     /* [HGM] always test for legality, to get promotion info */
5981     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5982                                          fromY, fromX, toY, toX, promoChar);
5983     /* [HGM] but possibly ignore an IllegalMove result */
5984     if (appData.testLegality) {
5985         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5986             DisplayMoveError(_("Illegal move"));
5987             return ImpossibleMove;
5988         }
5989     }
5990
5991     return moveType;
5992     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5993        function is made into one that returns an OK move type if FinishMove
5994        should be called. This to give the calling driver routine the
5995        opportunity to finish the userMove input with a promotion popup,
5996        without bothering the user with this for invalid or illegal moves */
5997
5998 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5999 }
6000
6001 /* Common tail of UserMoveEvent and DropMenuEvent */
6002 int
6003 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6004      ChessMove moveType;
6005      int fromX, fromY, toX, toY;
6006      /*char*/int promoChar;
6007 {
6008     char *bookHit = 0;
6009
6010     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6011         // [HGM] superchess: suppress promotions to non-available piece
6012         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6013         if(WhiteOnMove(currentMove)) {
6014             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6015         } else {
6016             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6017         }
6018     }
6019
6020     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6021        move type in caller when we know the move is a legal promotion */
6022     if(moveType == NormalMove && promoChar)
6023         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6024
6025     /* [HGM] convert drag-and-drop piece drops to standard form */
6026     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6027          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6028            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6029                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6030            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6031            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6032            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6033            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6034          fromY = DROP_RANK;
6035     }
6036
6037     /* [HGM] <popupFix> The following if has been moved here from
6038        UserMoveEvent(). Because it seemed to belong here (why not allow
6039        piece drops in training games?), and because it can only be
6040        performed after it is known to what we promote. */
6041     if (gameMode == Training) {
6042       /* compare the move played on the board to the next move in the
6043        * game. If they match, display the move and the opponent's response. 
6044        * If they don't match, display an error message.
6045        */
6046       int saveAnimate;
6047       Board testBoard;
6048       CopyBoard(testBoard, boards[currentMove]);
6049       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6050
6051       if (CompareBoards(testBoard, boards[currentMove+1])) {
6052         ForwardInner(currentMove+1);
6053
6054         /* Autoplay the opponent's response.
6055          * if appData.animate was TRUE when Training mode was entered,
6056          * the response will be animated.
6057          */
6058         saveAnimate = appData.animate;
6059         appData.animate = animateTraining;
6060         ForwardInner(currentMove+1);
6061         appData.animate = saveAnimate;
6062
6063         /* check for the end of the game */
6064         if (currentMove >= forwardMostMove) {
6065           gameMode = PlayFromGameFile;
6066           ModeHighlight();
6067           SetTrainingModeOff();
6068           DisplayInformation(_("End of game"));
6069         }
6070       } else {
6071         DisplayError(_("Incorrect move"), 0);
6072       }
6073       return 1;
6074     }
6075
6076   /* Ok, now we know that the move is good, so we can kill
6077      the previous line in Analysis Mode */
6078   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6079                                 && currentMove < forwardMostMove) {
6080     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6081   }
6082
6083   /* If we need the chess program but it's dead, restart it */
6084   ResurrectChessProgram();
6085
6086   /* A user move restarts a paused game*/
6087   if (pausing)
6088     PauseEvent();
6089
6090   thinkOutput[0] = NULLCHAR;
6091
6092   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6093
6094   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6095
6096   if (gameMode == BeginningOfGame) {
6097     if (appData.noChessProgram) {
6098       gameMode = EditGame;
6099       SetGameInfo();
6100     } else {
6101       char buf[MSG_SIZ];
6102       gameMode = MachinePlaysBlack;
6103       StartClocks();
6104       SetGameInfo();
6105       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6106       DisplayTitle(buf);
6107       if (first.sendName) {
6108         sprintf(buf, "name %s\n", gameInfo.white);
6109         SendToProgram(buf, &first);
6110       }
6111       StartClocks();
6112     }
6113     ModeHighlight();
6114   }
6115
6116   /* Relay move to ICS or chess engine */
6117   if (appData.icsActive) {
6118     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6119         gameMode == IcsExamining) {
6120       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6121         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6122         SendToICS("draw ");
6123         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6124       }
6125       // also send plain move, in case ICS does not understand atomic claims
6126       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6127       ics_user_moved = 1;
6128     }
6129   } else {
6130     if (first.sendTime && (gameMode == BeginningOfGame ||
6131                            gameMode == MachinePlaysWhite ||
6132                            gameMode == MachinePlaysBlack)) {
6133       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6134     }
6135     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6136          // [HGM] book: if program might be playing, let it use book
6137         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6138         first.maybeThinking = TRUE;
6139     } else SendMoveToProgram(forwardMostMove-1, &first);
6140     if (currentMove == cmailOldMove + 1) {
6141       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6142     }
6143   }
6144
6145   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146
6147   switch (gameMode) {
6148   case EditGame:
6149     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6150     case MT_NONE:
6151     case MT_CHECK:
6152       break;
6153     case MT_CHECKMATE:
6154     case MT_STAINMATE:
6155       if (WhiteOnMove(currentMove)) {
6156         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6157       } else {
6158         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6159       }
6160       break;
6161     case MT_STALEMATE:
6162       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6163       break;
6164     }
6165     break;
6166     
6167   case MachinePlaysBlack:
6168   case MachinePlaysWhite:
6169     /* disable certain menu options while machine is thinking */
6170     SetMachineThinkingEnables();
6171     break;
6172
6173   default:
6174     break;
6175   }
6176
6177   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6178         
6179   if(bookHit) { // [HGM] book: simulate book reply
6180         static char bookMove[MSG_SIZ]; // a bit generous?
6181
6182         programStats.nodes = programStats.depth = programStats.time = 
6183         programStats.score = programStats.got_only_move = 0;
6184         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6185
6186         strcpy(bookMove, "move ");
6187         strcat(bookMove, bookHit);
6188         HandleMachineMove(bookMove, &first);
6189   }
6190   return 1;
6191 }
6192
6193 void
6194 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6195      int fromX, fromY, toX, toY;
6196      int promoChar;
6197 {
6198     /* [HGM] This routine was added to allow calling of its two logical
6199        parts from other modules in the old way. Before, UserMoveEvent()
6200        automatically called FinishMove() if the move was OK, and returned
6201        otherwise. I separated the two, in order to make it possible to
6202        slip a promotion popup in between. But that it always needs two
6203        calls, to the first part, (now called UserMoveTest() ), and to
6204        FinishMove if the first part succeeded. Calls that do not need
6205        to do anything in between, can call this routine the old way. 
6206     */
6207     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6208 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6209     if(moveType == AmbiguousMove)
6210         DrawPosition(FALSE, boards[currentMove]);
6211     else if(moveType != ImpossibleMove && moveType != Comment)
6212         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6213 }
6214
6215 void
6216 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6217      Board board;
6218      int flags;
6219      ChessMove kind;
6220      int rf, ff, rt, ft;
6221      VOIDSTAR closure;
6222 {
6223     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6224     Markers *m = (Markers *) closure;
6225     if(rf == fromY && ff == fromX)
6226         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6227                          || kind == WhiteCapturesEnPassant
6228                          || kind == BlackCapturesEnPassant);
6229     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6230 }
6231
6232 void
6233 MarkTargetSquares(int clear)
6234 {
6235   int x, y;
6236   if(!appData.markers || !appData.highlightDragging || 
6237      !appData.testLegality || gameMode == EditPosition) return;
6238   if(clear) {
6239     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6240   } else {
6241     int capt = 0;
6242     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6243     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6244       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6245       if(capt)
6246       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6247     }
6248   }
6249   DrawPosition(TRUE, NULL);
6250 }
6251
6252 void LeftClick(ClickType clickType, int xPix, int yPix)
6253 {
6254     int x, y;
6255     Boolean saveAnimate;
6256     static int second = 0, promotionChoice = 0;
6257     char promoChoice = NULLCHAR;
6258
6259     if(appData.seekGraph && appData.icsActive && loggedOn &&
6260         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6261         SeekGraphClick(clickType, xPix, yPix, 0);
6262         return;
6263     }
6264
6265     if (clickType == Press) ErrorPopDown();
6266     MarkTargetSquares(1);
6267
6268     x = EventToSquare(xPix, BOARD_WIDTH);
6269     y = EventToSquare(yPix, BOARD_HEIGHT);
6270     if (!flipView && y >= 0) {
6271         y = BOARD_HEIGHT - 1 - y;
6272     }
6273     if (flipView && x >= 0) {
6274         x = BOARD_WIDTH - 1 - x;
6275     }
6276
6277     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6278         if(clickType == Release) return; // ignore upclick of click-click destination
6279         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6280         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6281         if(gameInfo.holdingsWidth && 
6282                 (WhiteOnMove(currentMove) 
6283                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6284                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6285             // click in right holdings, for determining promotion piece
6286             ChessSquare p = boards[currentMove][y][x];
6287             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6288             if(p != EmptySquare) {
6289                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6290                 fromX = fromY = -1;
6291                 return;
6292             }
6293         }
6294         DrawPosition(FALSE, boards[currentMove]);
6295         return;
6296     }
6297
6298     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6299     if(clickType == Press
6300             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6301               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6302               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6303         return;
6304
6305     autoQueen = appData.alwaysPromoteToQueen;
6306
6307     if (fromX == -1) {
6308       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6309         if (clickType == Press) {
6310             /* First square */
6311             if (OKToStartUserMove(x, y)) {
6312                 fromX = x;
6313                 fromY = y;
6314                 second = 0;
6315                 MarkTargetSquares(0);
6316                 DragPieceBegin(xPix, yPix);
6317                 if (appData.highlightDragging) {
6318                     SetHighlights(x, y, -1, -1);
6319                 }
6320             }
6321         }
6322         return;
6323       }
6324     }
6325
6326     /* fromX != -1 */
6327     if (clickType == Press && gameMode != EditPosition) {
6328         ChessSquare fromP;
6329         ChessSquare toP;
6330         int frc;
6331
6332         // ignore off-board to clicks
6333         if(y < 0 || x < 0) return;
6334
6335         /* Check if clicking again on the same color piece */
6336         fromP = boards[currentMove][fromY][fromX];
6337         toP = boards[currentMove][y][x];
6338         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6339         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6340              WhitePawn <= toP && toP <= WhiteKing &&
6341              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6342              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6343             (BlackPawn <= fromP && fromP <= BlackKing && 
6344              BlackPawn <= toP && toP <= BlackKing &&
6345              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6346              !(fromP == BlackKing && toP == BlackRook && frc))) {
6347             /* Clicked again on same color piece -- changed his mind */
6348             second = (x == fromX && y == fromY);
6349            if(!second || !OnlyMove(&x, &y, TRUE)) {
6350             if (appData.highlightDragging) {
6351                 SetHighlights(x, y, -1, -1);
6352             } else {
6353                 ClearHighlights();
6354             }
6355             if (OKToStartUserMove(x, y)) {
6356                 fromX = x;
6357                 fromY = y;
6358                 MarkTargetSquares(0);
6359                 DragPieceBegin(xPix, yPix);
6360             }
6361             return;
6362            }
6363         }
6364         // ignore clicks on holdings
6365         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6366     }
6367
6368     if (clickType == Release && x == fromX && y == fromY) {
6369         DragPieceEnd(xPix, yPix);
6370         if (appData.animateDragging) {
6371             /* Undo animation damage if any */
6372             DrawPosition(FALSE, NULL);
6373         }
6374         if (second) {
6375             /* Second up/down in same square; just abort move */
6376             second = 0;
6377             fromX = fromY = -1;
6378             ClearHighlights();
6379             gotPremove = 0;
6380             ClearPremoveHighlights();
6381         } else {
6382             /* First upclick in same square; start click-click mode */
6383             SetHighlights(x, y, -1, -1);
6384         }
6385         return;
6386     }
6387
6388     /* we now have a different from- and (possibly off-board) to-square */
6389     /* Completed move */
6390     toX = x;
6391     toY = y;
6392     saveAnimate = appData.animate;
6393     if (clickType == Press) {
6394         /* Finish clickclick move */
6395         if (appData.animate || appData.highlightLastMove) {
6396             SetHighlights(fromX, fromY, toX, toY);
6397         } else {
6398             ClearHighlights();
6399         }
6400     } else {
6401         /* Finish drag move */
6402         if (appData.highlightLastMove) {
6403             SetHighlights(fromX, fromY, toX, toY);
6404         } else {
6405             ClearHighlights();
6406         }
6407         DragPieceEnd(xPix, yPix);
6408         /* Don't animate move and drag both */
6409         appData.animate = FALSE;
6410     }
6411
6412     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6413     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6414         ChessSquare piece = boards[currentMove][fromY][fromX];
6415         if(gameMode == EditPosition && piece != EmptySquare &&
6416            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6417             int n;
6418              
6419             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6420                 n = PieceToNumber(piece - (int)BlackPawn);
6421                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6422                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6423                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6424             } else
6425             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6426                 n = PieceToNumber(piece);
6427                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6428                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6429                 boards[currentMove][n][BOARD_WIDTH-2]++;
6430             }
6431             boards[currentMove][fromY][fromX] = EmptySquare;
6432         }
6433         ClearHighlights();
6434         fromX = fromY = -1;
6435         DrawPosition(TRUE, boards[currentMove]);
6436         return;
6437     }
6438
6439     // off-board moves should not be highlighted
6440     if(x < 0 || x < 0) ClearHighlights();
6441
6442     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6443         SetHighlights(fromX, fromY, toX, toY);
6444         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6445             // [HGM] super: promotion to captured piece selected from holdings
6446             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6447             promotionChoice = TRUE;
6448             // kludge follows to temporarily execute move on display, without promoting yet
6449             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6450             boards[currentMove][toY][toX] = p;
6451             DrawPosition(FALSE, boards[currentMove]);
6452             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6453             boards[currentMove][toY][toX] = q;
6454             DisplayMessage("Click in holdings to choose piece", "");
6455             return;
6456         }
6457         PromotionPopUp();
6458     } else {
6459         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6460         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6461         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6462         fromX = fromY = -1;
6463     }
6464     appData.animate = saveAnimate;
6465     if (appData.animate || appData.animateDragging) {
6466         /* Undo animation damage if needed */
6467         DrawPosition(FALSE, NULL);
6468     }
6469 }
6470
6471 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6472 {   // front-end-free part taken out of PieceMenuPopup
6473     int whichMenu; int xSqr, ySqr;
6474
6475     if(seekGraphUp) { // [HGM] seekgraph
6476         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6477         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6478         return -2;
6479     }
6480
6481     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6482          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6483         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6484         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6485         if(action == Press)   {
6486             originalFlip = flipView;
6487             flipView = !flipView; // temporarily flip board to see game from partners perspective
6488             DrawPosition(TRUE, partnerBoard);
6489             DisplayMessage(partnerStatus, "");
6490             partnerUp = TRUE;
6491         } else if(action == Release) {
6492             flipView = originalFlip;
6493             DrawPosition(TRUE, boards[currentMove]);
6494             partnerUp = FALSE;
6495         }
6496         return -2;
6497     }
6498
6499     xSqr = EventToSquare(x, BOARD_WIDTH);
6500     ySqr = EventToSquare(y, BOARD_HEIGHT);
6501     if (action == Release) UnLoadPV(); // [HGM] pv
6502     if (action != Press) return -2; // return code to be ignored
6503     switch (gameMode) {
6504       case IcsExamining:
6505         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6506       case EditPosition:
6507         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6508         if (xSqr < 0 || ySqr < 0) return -1;\r
6509         whichMenu = 0; // edit-position menu
6510         break;
6511       case IcsObserving:
6512         if(!appData.icsEngineAnalyze) return -1;
6513       case IcsPlayingWhite:
6514       case IcsPlayingBlack:
6515         if(!appData.zippyPlay) goto noZip;
6516       case AnalyzeMode:
6517       case AnalyzeFile:
6518       case MachinePlaysWhite:
6519       case MachinePlaysBlack:
6520       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6521         if (!appData.dropMenu) {
6522           LoadPV(x, y);
6523           return 2; // flag front-end to grab mouse events
6524         }
6525         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6526            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6527       case EditGame:
6528       noZip:
6529         if (xSqr < 0 || ySqr < 0) return -1;
6530         if (!appData.dropMenu || appData.testLegality &&
6531             gameInfo.variant != VariantBughouse &&
6532             gameInfo.variant != VariantCrazyhouse) return -1;
6533         whichMenu = 1; // drop menu
6534         break;
6535       default:
6536         return -1;
6537     }
6538
6539     if (((*fromX = xSqr) < 0) ||
6540         ((*fromY = ySqr) < 0)) {
6541         *fromX = *fromY = -1;
6542         return -1;
6543     }
6544     if (flipView)
6545       *fromX = BOARD_WIDTH - 1 - *fromX;
6546     else
6547       *fromY = BOARD_HEIGHT - 1 - *fromY;
6548
6549     return whichMenu;
6550 }
6551
6552 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6553 {
6554 //    char * hint = lastHint;
6555     FrontEndProgramStats stats;
6556
6557     stats.which = cps == &first ? 0 : 1;
6558     stats.depth = cpstats->depth;
6559     stats.nodes = cpstats->nodes;
6560     stats.score = cpstats->score;
6561     stats.time = cpstats->time;
6562     stats.pv = cpstats->movelist;
6563     stats.hint = lastHint;
6564     stats.an_move_index = 0;
6565     stats.an_move_count = 0;
6566
6567     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6568         stats.hint = cpstats->move_name;
6569         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6570         stats.an_move_count = cpstats->nr_moves;
6571     }
6572
6573     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6574
6575     SetProgramStats( &stats );
6576 }
6577
6578 int
6579 Adjudicate(ChessProgramState *cps)
6580 {       // [HGM] some adjudications useful with buggy engines
6581         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6582         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6583         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6584         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6585         int k, count = 0; static int bare = 1;
6586         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6587         Boolean canAdjudicate = !appData.icsActive;
6588
6589         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6590         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6591             if( appData.testLegality )
6592             {   /* [HGM] Some more adjudications for obstinate engines */
6593                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6594                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6595                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6596                 static int moveCount = 6;
6597                 ChessMove result;
6598                 char *reason = NULL;
6599
6600                 /* Count what is on board. */
6601                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6602                 {   ChessSquare p = boards[forwardMostMove][i][j];
6603                     int m=i;
6604
6605                     switch((int) p)
6606                     {   /* count B,N,R and other of each side */
6607                         case WhiteKing:
6608                         case BlackKing:
6609                              NrK++; break; // [HGM] atomic: count Kings
6610                         case WhiteKnight:
6611                              NrWN++; break;
6612                         case WhiteBishop:
6613                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6614                              bishopsColor |= 1 << ((i^j)&1);
6615                              NrWB++; break;
6616                         case BlackKnight:
6617                              NrBN++; break;
6618                         case BlackBishop:
6619                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6620                              bishopsColor |= 1 << ((i^j)&1);
6621                              NrBB++; break;
6622                         case WhiteRook:
6623                              NrWR++; break;
6624                         case BlackRook:
6625                              NrBR++; break;
6626                         case WhiteQueen:
6627                              NrWQ++; break;
6628                         case BlackQueen:
6629                              NrBQ++; break;
6630                         case EmptySquare: 
6631                              break;
6632                         case BlackPawn:
6633                              m = 7-i;
6634                         case WhitePawn:
6635                              PawnAdvance += m; NrPawns++;
6636                     }
6637                     NrPieces += (p != EmptySquare);
6638                     NrW += ((int)p < (int)BlackPawn);
6639                     if(gameInfo.variant == VariantXiangqi && 
6640                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6641                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6642                         NrW -= ((int)p < (int)BlackPawn);
6643                     }
6644                 }
6645
6646                 /* Some material-based adjudications that have to be made before stalemate test */
6647                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6648                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6649                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6650                      if(canAdjudicate && appData.checkMates) {
6651                          if(engineOpponent)
6652                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6653                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6654                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6655                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6656                          return 1;
6657                      }
6658                 }
6659
6660                 /* Bare King in Shatranj (loses) or Losers (wins) */
6661                 if( NrW == 1 || NrPieces - NrW == 1) {
6662                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6663                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6664                      if(canAdjudicate && appData.checkMates) {
6665                          if(engineOpponent)
6666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6667                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6668                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6669                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6670                          return 1;
6671                      }
6672                   } else
6673                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6674                   {    /* bare King */
6675                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6676                         if(canAdjudicate && appData.checkMates) {
6677                             /* but only adjudicate if adjudication enabled */
6678                             if(engineOpponent)
6679                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6680                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6681                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6682                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6683                             return 1;
6684                         }
6685                   }
6686                 } else bare = 1;
6687
6688
6689             // don't wait for engine to announce game end if we can judge ourselves
6690             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6691               case MT_CHECK:
6692                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6693                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6694                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6695                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6696                             checkCnt++;
6697                         if(checkCnt >= 2) {
6698                             reason = "Xboard adjudication: 3rd check";
6699                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6700                             break;
6701                         }
6702                     }
6703                 }
6704               case MT_NONE:
6705               default:
6706                 break;
6707               case MT_STALEMATE:
6708               case MT_STAINMATE:
6709                 reason = "Xboard adjudication: Stalemate";
6710                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6711                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6712                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6713                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6714                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6715                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6716                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6717                                                                         EP_CHECKMATE : EP_WINS);
6718                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6719                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6720                 }
6721                 break;
6722               case MT_CHECKMATE:
6723                 reason = "Xboard adjudication: Checkmate";
6724                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6725                 break;
6726             }
6727
6728                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6729                     case EP_STALEMATE:
6730                         result = GameIsDrawn; break;
6731                     case EP_CHECKMATE:
6732                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6733                     case EP_WINS:
6734                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6735                     default:
6736                         result = (ChessMove) 0;
6737                 }
6738                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6739                     if(engineOpponent)
6740                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6741                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6742                     GameEnds( result, reason, GE_XBOARD );
6743                     return 1;
6744                 }
6745
6746                 /* Next absolutely insufficient mating material. */
6747                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6748                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6749                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6750                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6751                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6752
6753                      /* always flag draws, for judging claims */
6754                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6755
6756                      if(canAdjudicate && appData.materialDraws) {
6757                          /* but only adjudicate them if adjudication enabled */
6758                          if(engineOpponent) {
6759                            SendToProgram("force\n", engineOpponent); // suppress reply
6760                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6761                          }
6762                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6763                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6764                          return 1;
6765                      }
6766                 }
6767
6768                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6769                 if(NrPieces == 4 && 
6770                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6771                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6772                    || NrWN==2 || NrBN==2     /* KNNK */
6773                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6774                   ) ) {
6775                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6776                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6777                           if(engineOpponent) {
6778                             SendToProgram("force\n", engineOpponent); // suppress reply
6779                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6780                           }
6781                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6782                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6783                           return 1;
6784                      }
6785                 } else moveCount = 6;
6786             }
6787         }
6788           
6789         if (appData.debugMode) { int i;
6790             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6791                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6792                     appData.drawRepeats);
6793             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6794               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6795             
6796         }
6797
6798         // Repetition draws and 50-move rule can be applied independently of legality testing
6799
6800                 /* Check for rep-draws */
6801                 count = 0;
6802                 for(k = forwardMostMove-2;
6803                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6804                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6805                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6806                     k-=2)
6807                 {   int rights=0;
6808                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6809                         /* compare castling rights */
6810                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6811                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6812                                 rights++; /* King lost rights, while rook still had them */
6813                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6814                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6815                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6816                                    rights++; /* but at least one rook lost them */
6817                         }
6818                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6819                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6820                                 rights++; 
6821                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6822                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6823                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6824                                    rights++;
6825                         }
6826                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6827                             && appData.drawRepeats > 1) {
6828                              /* adjudicate after user-specified nr of repeats */
6829                              if(engineOpponent) {
6830                                SendToProgram("force\n", engineOpponent); // suppress reply
6831                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6832                              }
6833                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6834                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6835                                 // [HGM] xiangqi: check for forbidden perpetuals
6836                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6837                                 for(m=forwardMostMove; m>k; m-=2) {
6838                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6839                                         ourPerpetual = 0; // the current mover did not always check
6840                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6841                                         hisPerpetual = 0; // the opponent did not always check
6842                                 }
6843                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6844                                                                         ourPerpetual, hisPerpetual);
6845                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6846                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6847                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6848                                     return 1;
6849                                 }
6850                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6851                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6852                                 // Now check for perpetual chases
6853                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6854                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6855                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6856                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6857                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6858                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6859                                         return 1;
6860                                     }
6861                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6862                                         break; // Abort repetition-checking loop.
6863                                 }
6864                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6865                              }
6866                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6867                              return 1;
6868                         }
6869                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6870                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6871                     }
6872                 }
6873
6874                 /* Now we test for 50-move draws. Determine ply count */
6875                 count = forwardMostMove;
6876                 /* look for last irreversble move */
6877                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6878                     count--;
6879                 /* if we hit starting position, add initial plies */
6880                 if( count == backwardMostMove )
6881                     count -= initialRulePlies;
6882                 count = forwardMostMove - count; 
6883                 if( count >= 100)
6884                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6885                          /* this is used to judge if draw claims are legal */
6886                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6887                          if(engineOpponent) {
6888                            SendToProgram("force\n", engineOpponent); // suppress reply
6889                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6890                          }
6891                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6892                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6893                          return 1;
6894                 }
6895
6896                 /* if draw offer is pending, treat it as a draw claim
6897                  * when draw condition present, to allow engines a way to
6898                  * claim draws before making their move to avoid a race
6899                  * condition occurring after their move
6900                  */
6901                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6902                          char *p = NULL;
6903                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6904                              p = "Draw claim: 50-move rule";
6905                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6906                              p = "Draw claim: 3-fold repetition";
6907                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6908                              p = "Draw claim: insufficient mating material";
6909                          if( p != NULL && canAdjudicate) {
6910                              if(engineOpponent) {
6911                                SendToProgram("force\n", engineOpponent); // suppress reply
6912                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6913                              }
6914                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6915                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6916                              return 1;
6917                          }
6918                 }
6919
6920                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6921                     if(engineOpponent) {
6922                       SendToProgram("force\n", engineOpponent); // suppress reply
6923                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6924                     }
6925                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6926                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6927                     return 1;
6928                 }
6929         return 0;
6930 }
6931
6932 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6933 {   // [HGM] book: this routine intercepts moves to simulate book replies
6934     char *bookHit = NULL;
6935
6936     //first determine if the incoming move brings opponent into his book
6937     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6938         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6939     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6940     if(bookHit != NULL && !cps->bookSuspend) {
6941         // make sure opponent is not going to reply after receiving move to book position
6942         SendToProgram("force\n", cps);
6943         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6944     }
6945     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6946     // now arrange restart after book miss
6947     if(bookHit) {
6948         // after a book hit we never send 'go', and the code after the call to this routine
6949         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6950         char buf[MSG_SIZ];
6951         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6952         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6953         SendToProgram(buf, cps);
6954         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6955     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6956         SendToProgram("go\n", cps);
6957         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6958     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6959         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6960             SendToProgram("go\n", cps); 
6961         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6962     }
6963     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6964 }
6965
6966 char *savedMessage;
6967 ChessProgramState *savedState;
6968 void DeferredBookMove(void)
6969 {
6970         if(savedState->lastPing != savedState->lastPong)
6971                     ScheduleDelayedEvent(DeferredBookMove, 10);
6972         else
6973         HandleMachineMove(savedMessage, savedState);
6974 }
6975
6976 void
6977 HandleMachineMove(message, cps)
6978      char *message;
6979      ChessProgramState *cps;
6980 {
6981     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6982     char realname[MSG_SIZ];
6983     int fromX, fromY, toX, toY;
6984     ChessMove moveType;
6985     char promoChar;
6986     char *p;
6987     int machineWhite;
6988     char *bookHit;
6989
6990     cps->userError = 0;
6991
6992 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6993     /*
6994      * Kludge to ignore BEL characters
6995      */
6996     while (*message == '\007') message++;
6997
6998     /*
6999      * [HGM] engine debug message: ignore lines starting with '#' character
7000      */
7001     if(cps->debug && *message == '#') return;
7002
7003     /*
7004      * Look for book output
7005      */
7006     if (cps == &first && bookRequested) {
7007         if (message[0] == '\t' || message[0] == ' ') {
7008             /* Part of the book output is here; append it */
7009             strcat(bookOutput, message);
7010             strcat(bookOutput, "  \n");
7011             return;
7012         } else if (bookOutput[0] != NULLCHAR) {
7013             /* All of book output has arrived; display it */
7014             char *p = bookOutput;
7015             while (*p != NULLCHAR) {
7016                 if (*p == '\t') *p = ' ';
7017                 p++;
7018             }
7019             DisplayInformation(bookOutput);
7020             bookRequested = FALSE;
7021             /* Fall through to parse the current output */
7022         }
7023     }
7024
7025     /*
7026      * Look for machine move.
7027      */
7028     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7029         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7030     {
7031         /* This method is only useful on engines that support ping */
7032         if (cps->lastPing != cps->lastPong) {
7033           if (gameMode == BeginningOfGame) {
7034             /* Extra move from before last new; ignore */
7035             if (appData.debugMode) {
7036                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7037             }
7038           } else {
7039             if (appData.debugMode) {
7040                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7041                         cps->which, gameMode);
7042             }
7043
7044             SendToProgram("undo\n", cps);
7045           }
7046           return;
7047         }
7048
7049         switch (gameMode) {
7050           case BeginningOfGame:
7051             /* Extra move from before last reset; ignore */
7052             if (appData.debugMode) {
7053                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7054             }
7055             return;
7056
7057           case EndOfGame:
7058           case IcsIdle:
7059           default:
7060             /* Extra move after we tried to stop.  The mode test is
7061                not a reliable way of detecting this problem, but it's
7062                the best we can do on engines that don't support ping.
7063             */
7064             if (appData.debugMode) {
7065                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7066                         cps->which, gameMode);
7067             }
7068             SendToProgram("undo\n", cps);
7069             return;
7070
7071           case MachinePlaysWhite:
7072           case IcsPlayingWhite:
7073             machineWhite = TRUE;
7074             break;
7075
7076           case MachinePlaysBlack:
7077           case IcsPlayingBlack:
7078             machineWhite = FALSE;
7079             break;
7080
7081           case TwoMachinesPlay:
7082             machineWhite = (cps->twoMachinesColor[0] == 'w');
7083             break;
7084         }
7085         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7086             if (appData.debugMode) {
7087                 fprintf(debugFP,
7088                         "Ignoring move out of turn by %s, gameMode %d"
7089                         ", forwardMost %d\n",
7090                         cps->which, gameMode, forwardMostMove);
7091             }
7092             return;
7093         }
7094
7095     if (appData.debugMode) { int f = forwardMostMove;
7096         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7097                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7098                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7099     }
7100         if(cps->alphaRank) AlphaRank(machineMove, 4);
7101         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7102                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7103             /* Machine move could not be parsed; ignore it. */
7104             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7105                     machineMove, cps->which);
7106             DisplayError(buf1, 0);
7107             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7108                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7109             if (gameMode == TwoMachinesPlay) {
7110               GameEnds(machineWhite ? BlackWins : WhiteWins,
7111                        buf1, GE_XBOARD);
7112             }
7113             return;
7114         }
7115
7116         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7117         /* So we have to redo legality test with true e.p. status here,  */
7118         /* to make sure an illegal e.p. capture does not slip through,   */
7119         /* to cause a forfeit on a justified illegal-move complaint      */
7120         /* of the opponent.                                              */
7121         if( gameMode==TwoMachinesPlay && appData.testLegality
7122             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7123                                                               ) {
7124            ChessMove moveType;
7125            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7126                              fromY, fromX, toY, toX, promoChar);
7127             if (appData.debugMode) {
7128                 int i;
7129                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7130                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7131                 fprintf(debugFP, "castling rights\n");
7132             }
7133             if(moveType == IllegalMove) {
7134                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7135                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7136                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7137                            buf1, GE_XBOARD);
7138                 return;
7139            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7140            /* [HGM] Kludge to handle engines that send FRC-style castling
7141               when they shouldn't (like TSCP-Gothic) */
7142            switch(moveType) {
7143              case WhiteASideCastleFR:
7144              case BlackASideCastleFR:
7145                toX+=2;
7146                currentMoveString[2]++;
7147                break;
7148              case WhiteHSideCastleFR:
7149              case BlackHSideCastleFR:
7150                toX--;
7151                currentMoveString[2]--;
7152                break;
7153              default: ; // nothing to do, but suppresses warning of pedantic compilers
7154            }
7155         }
7156         hintRequested = FALSE;
7157         lastHint[0] = NULLCHAR;
7158         bookRequested = FALSE;
7159         /* Program may be pondering now */
7160         cps->maybeThinking = TRUE;
7161         if (cps->sendTime == 2) cps->sendTime = 1;
7162         if (cps->offeredDraw) cps->offeredDraw--;
7163
7164         /* currentMoveString is set as a side-effect of ParseOneMove */
7165         strcpy(machineMove, currentMoveString);
7166         strcat(machineMove, "\n");
7167         strcpy(moveList[forwardMostMove], machineMove);
7168
7169         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7170
7171         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7172         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7173             int count = 0;
7174
7175             while( count < adjudicateLossPlies ) {
7176                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7177
7178                 if( count & 1 ) {
7179                     score = -score; /* Flip score for winning side */
7180                 }
7181
7182                 if( score > adjudicateLossThreshold ) {
7183                     break;
7184                 }
7185
7186                 count++;
7187             }
7188
7189             if( count >= adjudicateLossPlies ) {
7190                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7191
7192                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7193                     "Xboard adjudication", 
7194                     GE_XBOARD );
7195
7196                 return;
7197             }
7198         }
7199
7200         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7201
7202 #if ZIPPY
7203         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7204             first.initDone) {
7205           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7206                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7207                 SendToICS("draw ");
7208                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7209           }
7210           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7211           ics_user_moved = 1;
7212           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7213                 char buf[3*MSG_SIZ];
7214
7215                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7216                         programStats.score / 100.,
7217                         programStats.depth,
7218                         programStats.time / 100.,
7219                         (unsigned int)programStats.nodes,
7220                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7221                         programStats.movelist);
7222                 SendToICS(buf);
7223 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7224           }
7225         }
7226 #endif
7227
7228         /* [AS] Save move info and clear stats for next move */
7229         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7230         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7231         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7232         ClearProgramStats();
7233         thinkOutput[0] = NULLCHAR;
7234         hiddenThinkOutputState = 0;
7235
7236         bookHit = NULL;
7237         if (gameMode == TwoMachinesPlay) {
7238             /* [HGM] relaying draw offers moved to after reception of move */
7239             /* and interpreting offer as claim if it brings draw condition */
7240             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7241                 SendToProgram("draw\n", cps->other);
7242             }
7243             if (cps->other->sendTime) {
7244                 SendTimeRemaining(cps->other,
7245                                   cps->other->twoMachinesColor[0] == 'w');
7246             }
7247             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7248             if (firstMove && !bookHit) {
7249                 firstMove = FALSE;
7250                 if (cps->other->useColors) {
7251                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7252                 }
7253                 SendToProgram("go\n", cps->other);
7254             }
7255             cps->other->maybeThinking = TRUE;
7256         }
7257
7258         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7259         
7260         if (!pausing && appData.ringBellAfterMoves) {
7261             RingBell();
7262         }
7263
7264         /* 
7265          * Reenable menu items that were disabled while
7266          * machine was thinking
7267          */
7268         if (gameMode != TwoMachinesPlay)
7269             SetUserThinkingEnables();
7270
7271         // [HGM] book: after book hit opponent has received move and is now in force mode
7272         // force the book reply into it, and then fake that it outputted this move by jumping
7273         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7274         if(bookHit) {
7275                 static char bookMove[MSG_SIZ]; // a bit generous?
7276
7277                 strcpy(bookMove, "move ");
7278                 strcat(bookMove, bookHit);
7279                 message = bookMove;
7280                 cps = cps->other;
7281                 programStats.nodes = programStats.depth = programStats.time = 
7282                 programStats.score = programStats.got_only_move = 0;
7283                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7284
7285                 if(cps->lastPing != cps->lastPong) {
7286                     savedMessage = message; // args for deferred call
7287                     savedState = cps;
7288                     ScheduleDelayedEvent(DeferredBookMove, 10);
7289                     return;
7290                 }
7291                 goto FakeBookMove;
7292         }
7293
7294         return;
7295     }
7296
7297     /* Set special modes for chess engines.  Later something general
7298      *  could be added here; for now there is just one kludge feature,
7299      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7300      *  when "xboard" is given as an interactive command.
7301      */
7302     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7303         cps->useSigint = FALSE;
7304         cps->useSigterm = FALSE;
7305     }
7306     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7307       ParseFeatures(message+8, cps);
7308       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7309     }
7310
7311     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7312      * want this, I was asked to put it in, and obliged.
7313      */
7314     if (!strncmp(message, "setboard ", 9)) {
7315         Board initial_position;
7316
7317         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7318
7319         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7320             DisplayError(_("Bad FEN received from engine"), 0);
7321             return ;
7322         } else {
7323            Reset(TRUE, FALSE);
7324            CopyBoard(boards[0], initial_position);
7325            initialRulePlies = FENrulePlies;
7326            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7327            else gameMode = MachinePlaysBlack;                 
7328            DrawPosition(FALSE, boards[currentMove]);
7329         }
7330         return;
7331     }
7332
7333     /*
7334      * Look for communication commands
7335      */
7336     if (!strncmp(message, "telluser ", 9)) {
7337         DisplayNote(message + 9);
7338         return;
7339     }
7340     if (!strncmp(message, "tellusererror ", 14)) {
7341         cps->userError = 1;
7342         DisplayError(message + 14, 0);
7343         return;
7344     }
7345     if (!strncmp(message, "tellopponent ", 13)) {
7346       if (appData.icsActive) {
7347         if (loggedOn) {
7348           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7349           SendToICS(buf1);
7350         }
7351       } else {
7352         DisplayNote(message + 13);
7353       }
7354       return;
7355     }
7356     if (!strncmp(message, "tellothers ", 11)) {
7357       if (appData.icsActive) {
7358         if (loggedOn) {
7359           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7360           SendToICS(buf1);
7361         }
7362       }
7363       return;
7364     }
7365     if (!strncmp(message, "tellall ", 8)) {
7366       if (appData.icsActive) {
7367         if (loggedOn) {
7368           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7369           SendToICS(buf1);
7370         }
7371       } else {
7372         DisplayNote(message + 8);
7373       }
7374       return;
7375     }
7376     if (strncmp(message, "warning", 7) == 0) {
7377         /* Undocumented feature, use tellusererror in new code */
7378         DisplayError(message, 0);
7379         return;
7380     }
7381     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7382         strcpy(realname, cps->tidy);
7383         strcat(realname, " query");
7384         AskQuestion(realname, buf2, buf1, cps->pr);
7385         return;
7386     }
7387     /* Commands from the engine directly to ICS.  We don't allow these to be 
7388      *  sent until we are logged on. Crafty kibitzes have been known to 
7389      *  interfere with the login process.
7390      */
7391     if (loggedOn) {
7392         if (!strncmp(message, "tellics ", 8)) {
7393             SendToICS(message + 8);
7394             SendToICS("\n");
7395             return;
7396         }
7397         if (!strncmp(message, "tellicsnoalias ", 15)) {
7398             SendToICS(ics_prefix);
7399             SendToICS(message + 15);
7400             SendToICS("\n");
7401             return;
7402         }
7403         /* The following are for backward compatibility only */
7404         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7405             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7406             SendToICS(ics_prefix);
7407             SendToICS(message);
7408             SendToICS("\n");
7409             return;
7410         }
7411     }
7412     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7413         return;
7414     }
7415     /*
7416      * If the move is illegal, cancel it and redraw the board.
7417      * Also deal with other error cases.  Matching is rather loose
7418      * here to accommodate engines written before the spec.
7419      */
7420     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7421         strncmp(message, "Error", 5) == 0) {
7422         if (StrStr(message, "name") || 
7423             StrStr(message, "rating") || StrStr(message, "?") ||
7424             StrStr(message, "result") || StrStr(message, "board") ||
7425             StrStr(message, "bk") || StrStr(message, "computer") ||
7426             StrStr(message, "variant") || StrStr(message, "hint") ||
7427             StrStr(message, "random") || StrStr(message, "depth") ||
7428             StrStr(message, "accepted")) {
7429             return;
7430         }
7431         if (StrStr(message, "protover")) {
7432           /* Program is responding to input, so it's apparently done
7433              initializing, and this error message indicates it is
7434              protocol version 1.  So we don't need to wait any longer
7435              for it to initialize and send feature commands. */
7436           FeatureDone(cps, 1);
7437           cps->protocolVersion = 1;
7438           return;
7439         }
7440         cps->maybeThinking = FALSE;
7441
7442         if (StrStr(message, "draw")) {
7443             /* Program doesn't have "draw" command */
7444             cps->sendDrawOffers = 0;
7445             return;
7446         }
7447         if (cps->sendTime != 1 &&
7448             (StrStr(message, "time") || StrStr(message, "otim"))) {
7449           /* Program apparently doesn't have "time" or "otim" command */
7450           cps->sendTime = 0;
7451           return;
7452         }
7453         if (StrStr(message, "analyze")) {
7454             cps->analysisSupport = FALSE;
7455             cps->analyzing = FALSE;
7456             Reset(FALSE, TRUE);
7457             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7458             DisplayError(buf2, 0);
7459             return;
7460         }
7461         if (StrStr(message, "(no matching move)st")) {
7462           /* Special kludge for GNU Chess 4 only */
7463           cps->stKludge = TRUE;
7464           SendTimeControl(cps, movesPerSession, timeControl,
7465                           timeIncrement, appData.searchDepth,
7466                           searchTime);
7467           return;
7468         }
7469         if (StrStr(message, "(no matching move)sd")) {
7470           /* Special kludge for GNU Chess 4 only */
7471           cps->sdKludge = TRUE;
7472           SendTimeControl(cps, movesPerSession, timeControl,
7473                           timeIncrement, appData.searchDepth,
7474                           searchTime);
7475           return;
7476         }
7477         if (!StrStr(message, "llegal")) {
7478             return;
7479         }
7480         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7481             gameMode == IcsIdle) return;
7482         if (forwardMostMove <= backwardMostMove) return;
7483         if (pausing) PauseEvent();
7484       if(appData.forceIllegal) {
7485             // [HGM] illegal: machine refused move; force position after move into it
7486           SendToProgram("force\n", cps);
7487           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7488                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7489                 // when black is to move, while there might be nothing on a2 or black
7490                 // might already have the move. So send the board as if white has the move.
7491                 // But first we must change the stm of the engine, as it refused the last move
7492                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7493                 if(WhiteOnMove(forwardMostMove)) {
7494                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7495                     SendBoard(cps, forwardMostMove); // kludgeless board
7496                 } else {
7497                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7498                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7499                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7500                 }
7501           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7502             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7503                  gameMode == TwoMachinesPlay)
7504               SendToProgram("go\n", cps);
7505             return;
7506       } else
7507         if (gameMode == PlayFromGameFile) {
7508             /* Stop reading this game file */
7509             gameMode = EditGame;
7510             ModeHighlight();
7511         }
7512         currentMove = forwardMostMove-1;
7513         DisplayMove(currentMove-1); /* before DisplayMoveError */
7514         SwitchClocks(forwardMostMove-1); // [HGM] race
7515         DisplayBothClocks();
7516         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7517                 parseList[currentMove], cps->which);
7518         DisplayMoveError(buf1);
7519         DrawPosition(FALSE, boards[currentMove]);
7520
7521         /* [HGM] illegal-move claim should forfeit game when Xboard */
7522         /* only passes fully legal moves                            */
7523         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7524             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7525                                 "False illegal-move claim", GE_XBOARD );
7526         }
7527         return;
7528     }
7529     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7530         /* Program has a broken "time" command that
7531            outputs a string not ending in newline.
7532            Don't use it. */
7533         cps->sendTime = 0;
7534     }
7535     
7536     /*
7537      * If chess program startup fails, exit with an error message.
7538      * Attempts to recover here are futile.
7539      */
7540     if ((StrStr(message, "unknown host") != NULL)
7541         || (StrStr(message, "No remote directory") != NULL)
7542         || (StrStr(message, "not found") != NULL)
7543         || (StrStr(message, "No such file") != NULL)
7544         || (StrStr(message, "can't alloc") != NULL)
7545         || (StrStr(message, "Permission denied") != NULL)) {
7546
7547         cps->maybeThinking = FALSE;
7548         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7549                 cps->which, cps->program, cps->host, message);
7550         RemoveInputSource(cps->isr);
7551         DisplayFatalError(buf1, 0, 1);
7552         return;
7553     }
7554     
7555     /* 
7556      * Look for hint output
7557      */
7558     if (sscanf(message, "Hint: %s", buf1) == 1) {
7559         if (cps == &first && hintRequested) {
7560             hintRequested = FALSE;
7561             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7562                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7563                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7564                                     PosFlags(forwardMostMove),
7565                                     fromY, fromX, toY, toX, promoChar, buf1);
7566                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7567                 DisplayInformation(buf2);
7568             } else {
7569                 /* Hint move could not be parsed!? */
7570               snprintf(buf2, sizeof(buf2),
7571                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7572                         buf1, cps->which);
7573                 DisplayError(buf2, 0);
7574             }
7575         } else {
7576             strcpy(lastHint, buf1);
7577         }
7578         return;
7579     }
7580
7581     /*
7582      * Ignore other messages if game is not in progress
7583      */
7584     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7585         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7586
7587     /*
7588      * look for win, lose, draw, or draw offer
7589      */
7590     if (strncmp(message, "1-0", 3) == 0) {
7591         char *p, *q, *r = "";
7592         p = strchr(message, '{');
7593         if (p) {
7594             q = strchr(p, '}');
7595             if (q) {
7596                 *q = NULLCHAR;
7597                 r = p + 1;
7598             }
7599         }
7600         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7601         return;
7602     } else if (strncmp(message, "0-1", 3) == 0) {
7603         char *p, *q, *r = "";
7604         p = strchr(message, '{');
7605         if (p) {
7606             q = strchr(p, '}');
7607             if (q) {
7608                 *q = NULLCHAR;
7609                 r = p + 1;
7610             }
7611         }
7612         /* Kludge for Arasan 4.1 bug */
7613         if (strcmp(r, "Black resigns") == 0) {
7614             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7615             return;
7616         }
7617         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7618         return;
7619     } else if (strncmp(message, "1/2", 3) == 0) {
7620         char *p, *q, *r = "";
7621         p = strchr(message, '{');
7622         if (p) {
7623             q = strchr(p, '}');
7624             if (q) {
7625                 *q = NULLCHAR;
7626                 r = p + 1;
7627             }
7628         }
7629             
7630         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7631         return;
7632
7633     } else if (strncmp(message, "White resign", 12) == 0) {
7634         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7635         return;
7636     } else if (strncmp(message, "Black resign", 12) == 0) {
7637         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7638         return;
7639     } else if (strncmp(message, "White matches", 13) == 0 ||
7640                strncmp(message, "Black matches", 13) == 0   ) {
7641         /* [HGM] ignore GNUShogi noises */
7642         return;
7643     } else if (strncmp(message, "White", 5) == 0 &&
7644                message[5] != '(' &&
7645                StrStr(message, "Black") == NULL) {
7646         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7647         return;
7648     } else if (strncmp(message, "Black", 5) == 0 &&
7649                message[5] != '(') {
7650         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7651         return;
7652     } else if (strcmp(message, "resign") == 0 ||
7653                strcmp(message, "computer resigns") == 0) {
7654         switch (gameMode) {
7655           case MachinePlaysBlack:
7656           case IcsPlayingBlack:
7657             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7658             break;
7659           case MachinePlaysWhite:
7660           case IcsPlayingWhite:
7661             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7662             break;
7663           case TwoMachinesPlay:
7664             if (cps->twoMachinesColor[0] == 'w')
7665               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7666             else
7667               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7668             break;
7669           default:
7670             /* can't happen */
7671             break;
7672         }
7673         return;
7674     } else if (strncmp(message, "opponent mates", 14) == 0) {
7675         switch (gameMode) {
7676           case MachinePlaysBlack:
7677           case IcsPlayingBlack:
7678             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7679             break;
7680           case MachinePlaysWhite:
7681           case IcsPlayingWhite:
7682             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7683             break;
7684           case TwoMachinesPlay:
7685             if (cps->twoMachinesColor[0] == 'w')
7686               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7687             else
7688               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7689             break;
7690           default:
7691             /* can't happen */
7692             break;
7693         }
7694         return;
7695     } else if (strncmp(message, "computer mates", 14) == 0) {
7696         switch (gameMode) {
7697           case MachinePlaysBlack:
7698           case IcsPlayingBlack:
7699             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7700             break;
7701           case MachinePlaysWhite:
7702           case IcsPlayingWhite:
7703             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7704             break;
7705           case TwoMachinesPlay:
7706             if (cps->twoMachinesColor[0] == 'w')
7707               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7708             else
7709               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7710             break;
7711           default:
7712             /* can't happen */
7713             break;
7714         }
7715         return;
7716     } else if (strncmp(message, "checkmate", 9) == 0) {
7717         if (WhiteOnMove(forwardMostMove)) {
7718             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7719         } else {
7720             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7721         }
7722         return;
7723     } else if (strstr(message, "Draw") != NULL ||
7724                strstr(message, "game is a draw") != NULL) {
7725         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7726         return;
7727     } else if (strstr(message, "offer") != NULL &&
7728                strstr(message, "draw") != NULL) {
7729 #if ZIPPY
7730         if (appData.zippyPlay && first.initDone) {
7731             /* Relay offer to ICS */
7732             SendToICS(ics_prefix);
7733             SendToICS("draw\n");
7734         }
7735 #endif
7736         cps->offeredDraw = 2; /* valid until this engine moves twice */
7737         if (gameMode == TwoMachinesPlay) {
7738             if (cps->other->offeredDraw) {
7739                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7740             /* [HGM] in two-machine mode we delay relaying draw offer      */
7741             /* until after we also have move, to see if it is really claim */
7742             }
7743         } else if (gameMode == MachinePlaysWhite ||
7744                    gameMode == MachinePlaysBlack) {
7745           if (userOfferedDraw) {
7746             DisplayInformation(_("Machine accepts your draw offer"));
7747             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7748           } else {
7749             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7750           }
7751         }
7752     }
7753
7754     
7755     /*
7756      * Look for thinking output
7757      */
7758     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7759           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7760                                 ) {
7761         int plylev, mvleft, mvtot, curscore, time;
7762         char mvname[MOVE_LEN];
7763         u64 nodes; // [DM]
7764         char plyext;
7765         int ignore = FALSE;
7766         int prefixHint = FALSE;
7767         mvname[0] = NULLCHAR;
7768
7769         switch (gameMode) {
7770           case MachinePlaysBlack:
7771           case IcsPlayingBlack:
7772             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7773             break;
7774           case MachinePlaysWhite:
7775           case IcsPlayingWhite:
7776             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7777             break;
7778           case AnalyzeMode:
7779           case AnalyzeFile:
7780             break;
7781           case IcsObserving: /* [DM] icsEngineAnalyze */
7782             if (!appData.icsEngineAnalyze) ignore = TRUE;
7783             break;
7784           case TwoMachinesPlay:
7785             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7786                 ignore = TRUE;
7787             }
7788             break;
7789           default:
7790             ignore = TRUE;
7791             break;
7792         }
7793
7794         if (!ignore) {
7795             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7796             buf1[0] = NULLCHAR;
7797             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7798                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7799
7800                 if (plyext != ' ' && plyext != '\t') {
7801                     time *= 100;
7802                 }
7803
7804                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7805                 if( cps->scoreIsAbsolute && 
7806                     ( gameMode == MachinePlaysBlack ||
7807                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7808                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7809                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7810                      !WhiteOnMove(currentMove)
7811                     ) )
7812                 {
7813                     curscore = -curscore;
7814                 }
7815
7816
7817                 tempStats.depth = plylev;
7818                 tempStats.nodes = nodes;
7819                 tempStats.time = time;
7820                 tempStats.score = curscore;
7821                 tempStats.got_only_move = 0;
7822
7823                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7824                         int ticklen;
7825
7826                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7827                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7828                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7829                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7830                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7831                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7832                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7833                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7834                 }
7835
7836                 /* Buffer overflow protection */
7837                 if (buf1[0] != NULLCHAR) {
7838                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7839                         && appData.debugMode) {
7840                         fprintf(debugFP,
7841                                 "PV is too long; using the first %u bytes.\n",
7842                                 (unsigned) sizeof(tempStats.movelist) - 1);
7843                     }
7844
7845                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7846                 } else {
7847                     sprintf(tempStats.movelist, " no PV\n");
7848                 }
7849
7850                 if (tempStats.seen_stat) {
7851                     tempStats.ok_to_send = 1;
7852                 }
7853
7854                 if (strchr(tempStats.movelist, '(') != NULL) {
7855                     tempStats.line_is_book = 1;
7856                     tempStats.nr_moves = 0;
7857                     tempStats.moves_left = 0;
7858                 } else {
7859                     tempStats.line_is_book = 0;
7860                 }
7861
7862                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7863                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7864
7865                 SendProgramStatsToFrontend( cps, &tempStats );
7866
7867                 /* 
7868                     [AS] Protect the thinkOutput buffer from overflow... this
7869                     is only useful if buf1 hasn't overflowed first!
7870                 */
7871                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7872                         plylev, 
7873                         (gameMode == TwoMachinesPlay ?
7874                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7875                         ((double) curscore) / 100.0,
7876                         prefixHint ? lastHint : "",
7877                         prefixHint ? " " : "" );
7878
7879                 if( buf1[0] != NULLCHAR ) {
7880                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7881
7882                     if( strlen(buf1) > max_len ) {
7883                         if( appData.debugMode) {
7884                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7885                         }
7886                         buf1[max_len+1] = '\0';
7887                     }
7888
7889                     strcat( thinkOutput, buf1 );
7890                 }
7891
7892                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7893                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7894                     DisplayMove(currentMove - 1);
7895                 }
7896                 return;
7897
7898             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7899                 /* crafty (9.25+) says "(only move) <move>"
7900                  * if there is only 1 legal move
7901                  */
7902                 sscanf(p, "(only move) %s", buf1);
7903                 sprintf(thinkOutput, "%s (only move)", buf1);
7904                 sprintf(programStats.movelist, "%s (only move)", buf1);
7905                 programStats.depth = 1;
7906                 programStats.nr_moves = 1;
7907                 programStats.moves_left = 1;
7908                 programStats.nodes = 1;
7909                 programStats.time = 1;
7910                 programStats.got_only_move = 1;
7911
7912                 /* Not really, but we also use this member to
7913                    mean "line isn't going to change" (Crafty
7914                    isn't searching, so stats won't change) */
7915                 programStats.line_is_book = 1;
7916
7917                 SendProgramStatsToFrontend( cps, &programStats );
7918                 
7919                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7920                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7921                     DisplayMove(currentMove - 1);
7922                 }
7923                 return;
7924             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7925                               &time, &nodes, &plylev, &mvleft,
7926                               &mvtot, mvname) >= 5) {
7927                 /* The stat01: line is from Crafty (9.29+) in response
7928                    to the "." command */
7929                 programStats.seen_stat = 1;
7930                 cps->maybeThinking = TRUE;
7931
7932                 if (programStats.got_only_move || !appData.periodicUpdates)
7933                   return;
7934
7935                 programStats.depth = plylev;
7936                 programStats.time = time;
7937                 programStats.nodes = nodes;
7938                 programStats.moves_left = mvleft;
7939                 programStats.nr_moves = mvtot;
7940                 strcpy(programStats.move_name, mvname);
7941                 programStats.ok_to_send = 1;
7942                 programStats.movelist[0] = '\0';
7943
7944                 SendProgramStatsToFrontend( cps, &programStats );
7945
7946                 return;
7947
7948             } else if (strncmp(message,"++",2) == 0) {
7949                 /* Crafty 9.29+ outputs this */
7950                 programStats.got_fail = 2;
7951                 return;
7952
7953             } else if (strncmp(message,"--",2) == 0) {
7954                 /* Crafty 9.29+ outputs this */
7955                 programStats.got_fail = 1;
7956                 return;
7957
7958             } else if (thinkOutput[0] != NULLCHAR &&
7959                        strncmp(message, "    ", 4) == 0) {
7960                 unsigned message_len;
7961
7962                 p = message;
7963                 while (*p && *p == ' ') p++;
7964
7965                 message_len = strlen( p );
7966
7967                 /* [AS] Avoid buffer overflow */
7968                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7969                     strcat(thinkOutput, " ");
7970                     strcat(thinkOutput, p);
7971                 }
7972
7973                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7974                     strcat(programStats.movelist, " ");
7975                     strcat(programStats.movelist, p);
7976                 }
7977
7978                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7979                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7980                     DisplayMove(currentMove - 1);
7981                 }
7982                 return;
7983             }
7984         }
7985         else {
7986             buf1[0] = NULLCHAR;
7987
7988             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7989                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7990             {
7991                 ChessProgramStats cpstats;
7992
7993                 if (plyext != ' ' && plyext != '\t') {
7994                     time *= 100;
7995                 }
7996
7997                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7998                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7999                     curscore = -curscore;
8000                 }
8001
8002                 cpstats.depth = plylev;
8003                 cpstats.nodes = nodes;
8004                 cpstats.time = time;
8005                 cpstats.score = curscore;
8006                 cpstats.got_only_move = 0;
8007                 cpstats.movelist[0] = '\0';
8008
8009                 if (buf1[0] != NULLCHAR) {
8010                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8011                 }
8012
8013                 cpstats.ok_to_send = 0;
8014                 cpstats.line_is_book = 0;
8015                 cpstats.nr_moves = 0;
8016                 cpstats.moves_left = 0;
8017
8018                 SendProgramStatsToFrontend( cps, &cpstats );
8019             }
8020         }
8021     }
8022 }
8023
8024
8025 /* Parse a game score from the character string "game", and
8026    record it as the history of the current game.  The game
8027    score is NOT assumed to start from the standard position. 
8028    The display is not updated in any way.
8029    */
8030 void
8031 ParseGameHistory(game)
8032      char *game;
8033 {
8034     ChessMove moveType;
8035     int fromX, fromY, toX, toY, boardIndex;
8036     char promoChar;
8037     char *p, *q;
8038     char buf[MSG_SIZ];
8039
8040     if (appData.debugMode)
8041       fprintf(debugFP, "Parsing game history: %s\n", game);
8042
8043     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8044     gameInfo.site = StrSave(appData.icsHost);
8045     gameInfo.date = PGNDate();
8046     gameInfo.round = StrSave("-");
8047
8048     /* Parse out names of players */
8049     while (*game == ' ') game++;
8050     p = buf;
8051     while (*game != ' ') *p++ = *game++;
8052     *p = NULLCHAR;
8053     gameInfo.white = StrSave(buf);
8054     while (*game == ' ') game++;
8055     p = buf;
8056     while (*game != ' ' && *game != '\n') *p++ = *game++;
8057     *p = NULLCHAR;
8058     gameInfo.black = StrSave(buf);
8059
8060     /* Parse moves */
8061     boardIndex = blackPlaysFirst ? 1 : 0;
8062     yynewstr(game);
8063     for (;;) {
8064         yyboardindex = boardIndex;
8065         moveType = (ChessMove) yylex();
8066         switch (moveType) {
8067           case IllegalMove:             /* maybe suicide chess, etc. */
8068   if (appData.debugMode) {
8069     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8070     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8071     setbuf(debugFP, NULL);
8072   }
8073           case WhitePromotionChancellor:
8074           case BlackPromotionChancellor:
8075           case WhitePromotionArchbishop:
8076           case BlackPromotionArchbishop:
8077           case WhitePromotionQueen:
8078           case BlackPromotionQueen:
8079           case WhitePromotionRook:
8080           case BlackPromotionRook:
8081           case WhitePromotionBishop:
8082           case BlackPromotionBishop:
8083           case WhitePromotionKnight:
8084           case BlackPromotionKnight:
8085           case WhitePromotionKing:
8086           case BlackPromotionKing:
8087           case NormalMove:
8088           case WhiteCapturesEnPassant:
8089           case BlackCapturesEnPassant:
8090           case WhiteKingSideCastle:
8091           case WhiteQueenSideCastle:
8092           case BlackKingSideCastle:
8093           case BlackQueenSideCastle:
8094           case WhiteKingSideCastleWild:
8095           case WhiteQueenSideCastleWild:
8096           case BlackKingSideCastleWild:
8097           case BlackQueenSideCastleWild:
8098           /* PUSH Fabien */
8099           case WhiteHSideCastleFR:
8100           case WhiteASideCastleFR:
8101           case BlackHSideCastleFR:
8102           case BlackASideCastleFR:
8103           /* POP Fabien */
8104             fromX = currentMoveString[0] - AAA;
8105             fromY = currentMoveString[1] - ONE;
8106             toX = currentMoveString[2] - AAA;
8107             toY = currentMoveString[3] - ONE;
8108             promoChar = currentMoveString[4];
8109             break;
8110           case WhiteDrop:
8111           case BlackDrop:
8112             fromX = moveType == WhiteDrop ?
8113               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8114             (int) CharToPiece(ToLower(currentMoveString[0]));
8115             fromY = DROP_RANK;
8116             toX = currentMoveString[2] - AAA;
8117             toY = currentMoveString[3] - ONE;
8118             promoChar = NULLCHAR;
8119             break;
8120           case AmbiguousMove:
8121             /* bug? */
8122             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8123   if (appData.debugMode) {
8124     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8125     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8126     setbuf(debugFP, NULL);
8127   }
8128             DisplayError(buf, 0);
8129             return;
8130           case ImpossibleMove:
8131             /* bug? */
8132             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8133   if (appData.debugMode) {
8134     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8135     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8136     setbuf(debugFP, NULL);
8137   }
8138             DisplayError(buf, 0);
8139             return;
8140           case (ChessMove) 0:   /* end of file */
8141             if (boardIndex < backwardMostMove) {
8142                 /* Oops, gap.  How did that happen? */
8143                 DisplayError(_("Gap in move list"), 0);
8144                 return;
8145             }
8146             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8147             if (boardIndex > forwardMostMove) {
8148                 forwardMostMove = boardIndex;
8149             }
8150             return;
8151           case ElapsedTime:
8152             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8153                 strcat(parseList[boardIndex-1], " ");
8154                 strcat(parseList[boardIndex-1], yy_text);
8155             }
8156             continue;
8157           case Comment:
8158           case PGNTag:
8159           case NAG:
8160           default:
8161             /* ignore */
8162             continue;
8163           case WhiteWins:
8164           case BlackWins:
8165           case GameIsDrawn:
8166           case GameUnfinished:
8167             if (gameMode == IcsExamining) {
8168                 if (boardIndex < backwardMostMove) {
8169                     /* Oops, gap.  How did that happen? */
8170                     return;
8171                 }
8172                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8173                 return;
8174             }
8175             gameInfo.result = moveType;
8176             p = strchr(yy_text, '{');
8177             if (p == NULL) p = strchr(yy_text, '(');
8178             if (p == NULL) {
8179                 p = yy_text;
8180                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8181             } else {
8182                 q = strchr(p, *p == '{' ? '}' : ')');
8183                 if (q != NULL) *q = NULLCHAR;
8184                 p++;
8185             }
8186             gameInfo.resultDetails = StrSave(p);
8187             continue;
8188         }
8189         if (boardIndex >= forwardMostMove &&
8190             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8191             backwardMostMove = blackPlaysFirst ? 1 : 0;
8192             return;
8193         }
8194         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8195                                  fromY, fromX, toY, toX, promoChar,
8196                                  parseList[boardIndex]);
8197         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8198         /* currentMoveString is set as a side-effect of yylex */
8199         strcpy(moveList[boardIndex], currentMoveString);
8200         strcat(moveList[boardIndex], "\n");
8201         boardIndex++;
8202         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8203         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8204           case MT_NONE:
8205           case MT_STALEMATE:
8206           default:
8207             break;
8208           case MT_CHECK:
8209             if(gameInfo.variant != VariantShogi)
8210                 strcat(parseList[boardIndex - 1], "+");
8211             break;
8212           case MT_CHECKMATE:
8213           case MT_STAINMATE:
8214             strcat(parseList[boardIndex - 1], "#");
8215             break;
8216         }
8217     }
8218 }
8219
8220
8221 /* Apply a move to the given board  */
8222 void
8223 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8224      int fromX, fromY, toX, toY;
8225      int promoChar;
8226      Board board;
8227 {
8228   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8229   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8230
8231     /* [HGM] compute & store e.p. status and castling rights for new position */
8232     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8233     { int i;
8234
8235       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8236       oldEP = (signed char)board[EP_STATUS];
8237       board[EP_STATUS] = EP_NONE;
8238
8239       if( board[toY][toX] != EmptySquare ) 
8240            board[EP_STATUS] = EP_CAPTURE;  
8241
8242       if( board[fromY][fromX] == WhitePawn ) {
8243            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8244                board[EP_STATUS] = EP_PAWN_MOVE;
8245            if( toY-fromY==2) {
8246                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8247                         gameInfo.variant != VariantBerolina || toX < fromX)
8248                       board[EP_STATUS] = toX | berolina;
8249                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8250                         gameInfo.variant != VariantBerolina || toX > fromX) 
8251                       board[EP_STATUS] = toX;
8252            }
8253       } else 
8254       if( board[fromY][fromX] == BlackPawn ) {
8255            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8256                board[EP_STATUS] = EP_PAWN_MOVE; 
8257            if( toY-fromY== -2) {
8258                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8259                         gameInfo.variant != VariantBerolina || toX < fromX)
8260                       board[EP_STATUS] = toX | berolina;
8261                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8262                         gameInfo.variant != VariantBerolina || toX > fromX) 
8263                       board[EP_STATUS] = toX;
8264            }
8265        }
8266
8267        for(i=0; i<nrCastlingRights; i++) {
8268            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8269               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8270              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8271        }
8272
8273     }
8274
8275   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8276   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8277        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8278          
8279   if (fromX == toX && fromY == toY) return;
8280
8281   if (fromY == DROP_RANK) {
8282         /* must be first */
8283         piece = board[toY][toX] = (ChessSquare) fromX;
8284   } else {
8285      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8286      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8287      if(gameInfo.variant == VariantKnightmate)
8288          king += (int) WhiteUnicorn - (int) WhiteKing;
8289
8290     /* Code added by Tord: */
8291     /* FRC castling assumed when king captures friendly rook. */
8292     if (board[fromY][fromX] == WhiteKing &&
8293              board[toY][toX] == WhiteRook) {
8294       board[fromY][fromX] = EmptySquare;
8295       board[toY][toX] = EmptySquare;
8296       if(toX > fromX) {
8297         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8298       } else {
8299         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8300       }
8301     } else if (board[fromY][fromX] == BlackKing &&
8302                board[toY][toX] == BlackRook) {
8303       board[fromY][fromX] = EmptySquare;
8304       board[toY][toX] = EmptySquare;
8305       if(toX > fromX) {
8306         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8307       } else {
8308         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8309       }
8310     /* End of code added by Tord */
8311
8312     } else if (board[fromY][fromX] == king
8313         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8314         && toY == fromY && toX > fromX+1) {
8315         board[fromY][fromX] = EmptySquare;
8316         board[toY][toX] = king;
8317         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8318         board[fromY][BOARD_RGHT-1] = EmptySquare;
8319     } else if (board[fromY][fromX] == king
8320         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8321                && toY == fromY && toX < fromX-1) {
8322         board[fromY][fromX] = EmptySquare;
8323         board[toY][toX] = king;
8324         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8325         board[fromY][BOARD_LEFT] = EmptySquare;
8326     } else if (board[fromY][fromX] == WhitePawn
8327                && toY >= BOARD_HEIGHT-promoRank
8328                && gameInfo.variant != VariantXiangqi
8329                ) {
8330         /* white pawn promotion */
8331         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8332         if (board[toY][toX] == EmptySquare) {
8333             board[toY][toX] = WhiteQueen;
8334         }
8335         if(gameInfo.variant==VariantBughouse ||
8336            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8337             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8338         board[fromY][fromX] = EmptySquare;
8339     } else if ((fromY == BOARD_HEIGHT-4)
8340                && (toX != fromX)
8341                && gameInfo.variant != VariantXiangqi
8342                && gameInfo.variant != VariantBerolina
8343                && (board[fromY][fromX] == WhitePawn)
8344                && (board[toY][toX] == EmptySquare)) {
8345         board[fromY][fromX] = EmptySquare;
8346         board[toY][toX] = WhitePawn;
8347         captured = board[toY - 1][toX];
8348         board[toY - 1][toX] = EmptySquare;
8349     } else if ((fromY == BOARD_HEIGHT-4)
8350                && (toX == fromX)
8351                && gameInfo.variant == VariantBerolina
8352                && (board[fromY][fromX] == WhitePawn)
8353                && (board[toY][toX] == EmptySquare)) {
8354         board[fromY][fromX] = EmptySquare;
8355         board[toY][toX] = WhitePawn;
8356         if(oldEP & EP_BEROLIN_A) {
8357                 captured = board[fromY][fromX-1];
8358                 board[fromY][fromX-1] = EmptySquare;
8359         }else{  captured = board[fromY][fromX+1];
8360                 board[fromY][fromX+1] = EmptySquare;
8361         }
8362     } else if (board[fromY][fromX] == king
8363         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8364                && toY == fromY && toX > fromX+1) {
8365         board[fromY][fromX] = EmptySquare;
8366         board[toY][toX] = king;
8367         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8368         board[fromY][BOARD_RGHT-1] = EmptySquare;
8369     } else if (board[fromY][fromX] == king
8370         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8371                && toY == fromY && toX < fromX-1) {
8372         board[fromY][fromX] = EmptySquare;
8373         board[toY][toX] = king;
8374         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8375         board[fromY][BOARD_LEFT] = EmptySquare;
8376     } else if (fromY == 7 && fromX == 3
8377                && board[fromY][fromX] == BlackKing
8378                && toY == 7 && toX == 5) {
8379         board[fromY][fromX] = EmptySquare;
8380         board[toY][toX] = BlackKing;
8381         board[fromY][7] = EmptySquare;
8382         board[toY][4] = BlackRook;
8383     } else if (fromY == 7 && fromX == 3
8384                && board[fromY][fromX] == BlackKing
8385                && toY == 7 && toX == 1) {
8386         board[fromY][fromX] = EmptySquare;
8387         board[toY][toX] = BlackKing;
8388         board[fromY][0] = EmptySquare;
8389         board[toY][2] = BlackRook;
8390     } else if (board[fromY][fromX] == BlackPawn
8391                && toY < promoRank
8392                && gameInfo.variant != VariantXiangqi
8393                ) {
8394         /* black pawn promotion */
8395         board[toY][toX] = CharToPiece(ToLower(promoChar));
8396         if (board[toY][toX] == EmptySquare) {
8397             board[toY][toX] = BlackQueen;
8398         }
8399         if(gameInfo.variant==VariantBughouse ||
8400            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8401             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8402         board[fromY][fromX] = EmptySquare;
8403     } else if ((fromY == 3)
8404                && (toX != fromX)
8405                && gameInfo.variant != VariantXiangqi
8406                && gameInfo.variant != VariantBerolina
8407                && (board[fromY][fromX] == BlackPawn)
8408                && (board[toY][toX] == EmptySquare)) {
8409         board[fromY][fromX] = EmptySquare;
8410         board[toY][toX] = BlackPawn;
8411         captured = board[toY + 1][toX];
8412         board[toY + 1][toX] = EmptySquare;
8413     } else if ((fromY == 3)
8414                && (toX == fromX)
8415                && gameInfo.variant == VariantBerolina
8416                && (board[fromY][fromX] == BlackPawn)
8417                && (board[toY][toX] == EmptySquare)) {
8418         board[fromY][fromX] = EmptySquare;
8419         board[toY][toX] = BlackPawn;
8420         if(oldEP & EP_BEROLIN_A) {
8421                 captured = board[fromY][fromX-1];
8422                 board[fromY][fromX-1] = EmptySquare;
8423         }else{  captured = board[fromY][fromX+1];
8424                 board[fromY][fromX+1] = EmptySquare;
8425         }
8426     } else {
8427         board[toY][toX] = board[fromY][fromX];
8428         board[fromY][fromX] = EmptySquare;
8429     }
8430
8431     /* [HGM] now we promote for Shogi, if needed */
8432     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8433         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8434   }
8435
8436     if (gameInfo.holdingsWidth != 0) {
8437
8438       /* !!A lot more code needs to be written to support holdings  */
8439       /* [HGM] OK, so I have written it. Holdings are stored in the */
8440       /* penultimate board files, so they are automaticlly stored   */
8441       /* in the game history.                                       */
8442       if (fromY == DROP_RANK) {
8443         /* Delete from holdings, by decreasing count */
8444         /* and erasing image if necessary            */
8445         p = (int) fromX;
8446         if(p < (int) BlackPawn) { /* white drop */
8447              p -= (int)WhitePawn;
8448                  p = PieceToNumber((ChessSquare)p);
8449              if(p >= gameInfo.holdingsSize) p = 0;
8450              if(--board[p][BOARD_WIDTH-2] <= 0)
8451                   board[p][BOARD_WIDTH-1] = EmptySquare;
8452              if((int)board[p][BOARD_WIDTH-2] < 0)
8453                         board[p][BOARD_WIDTH-2] = 0;
8454         } else {                  /* black drop */
8455              p -= (int)BlackPawn;
8456                  p = PieceToNumber((ChessSquare)p);
8457              if(p >= gameInfo.holdingsSize) p = 0;
8458              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8459                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8460              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8461                         board[BOARD_HEIGHT-1-p][1] = 0;
8462         }
8463       }
8464       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8465           && gameInfo.variant != VariantBughouse        ) {
8466         /* [HGM] holdings: Add to holdings, if holdings exist */
8467         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8468                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8469                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8470         }
8471         p = (int) captured;
8472         if (p >= (int) BlackPawn) {
8473           p -= (int)BlackPawn;
8474           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8475                   /* in Shogi restore piece to its original  first */
8476                   captured = (ChessSquare) (DEMOTED captured);
8477                   p = DEMOTED p;
8478           }
8479           p = PieceToNumber((ChessSquare)p);
8480           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8481           board[p][BOARD_WIDTH-2]++;
8482           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8483         } else {
8484           p -= (int)WhitePawn;
8485           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8486                   captured = (ChessSquare) (DEMOTED captured);
8487                   p = DEMOTED p;
8488           }
8489           p = PieceToNumber((ChessSquare)p);
8490           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8491           board[BOARD_HEIGHT-1-p][1]++;
8492           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8493         }
8494       }
8495     } else if (gameInfo.variant == VariantAtomic) {
8496       if (captured != EmptySquare) {
8497         int y, x;
8498         for (y = toY-1; y <= toY+1; y++) {
8499           for (x = toX-1; x <= toX+1; x++) {
8500             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8501                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8502               board[y][x] = EmptySquare;
8503             }
8504           }
8505         }
8506         board[toY][toX] = EmptySquare;
8507       }
8508     }
8509     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8510         /* [HGM] Shogi promotions */
8511         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8512     }
8513
8514     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8515                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8516         // [HGM] superchess: take promotion piece out of holdings
8517         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8518         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8519             if(!--board[k][BOARD_WIDTH-2])
8520                 board[k][BOARD_WIDTH-1] = EmptySquare;
8521         } else {
8522             if(!--board[BOARD_HEIGHT-1-k][1])
8523                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8524         }
8525     }
8526
8527 }
8528
8529 /* Updates forwardMostMove */
8530 void
8531 MakeMove(fromX, fromY, toX, toY, promoChar)
8532      int fromX, fromY, toX, toY;
8533      int promoChar;
8534 {
8535 //    forwardMostMove++; // [HGM] bare: moved downstream
8536
8537     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8538         int timeLeft; static int lastLoadFlag=0; int king, piece;
8539         piece = boards[forwardMostMove][fromY][fromX];
8540         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8541         if(gameInfo.variant == VariantKnightmate)
8542             king += (int) WhiteUnicorn - (int) WhiteKing;
8543         if(forwardMostMove == 0) {
8544             if(blackPlaysFirst) 
8545                 fprintf(serverMoves, "%s;", second.tidy);
8546             fprintf(serverMoves, "%s;", first.tidy);
8547             if(!blackPlaysFirst) 
8548                 fprintf(serverMoves, "%s;", second.tidy);
8549         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8550         lastLoadFlag = loadFlag;
8551         // print base move
8552         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8553         // print castling suffix
8554         if( toY == fromY && piece == king ) {
8555             if(toX-fromX > 1)
8556                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8557             if(fromX-toX >1)
8558                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8559         }
8560         // e.p. suffix
8561         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8562              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8563              boards[forwardMostMove][toY][toX] == EmptySquare
8564              && fromX != toX )
8565                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8566         // promotion suffix
8567         if(promoChar != NULLCHAR)
8568                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8569         if(!loadFlag) {
8570             fprintf(serverMoves, "/%d/%d",
8571                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8572             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8573             else                      timeLeft = blackTimeRemaining/1000;
8574             fprintf(serverMoves, "/%d", timeLeft);
8575         }
8576         fflush(serverMoves);
8577     }
8578
8579     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8580       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8581                         0, 1);
8582       return;
8583     }
8584     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8585     if (commentList[forwardMostMove+1] != NULL) {
8586         free(commentList[forwardMostMove+1]);
8587         commentList[forwardMostMove+1] = NULL;
8588     }
8589     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8590     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8591     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8592     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8593     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8594     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8595     gameInfo.result = GameUnfinished;
8596     if (gameInfo.resultDetails != NULL) {
8597         free(gameInfo.resultDetails);
8598         gameInfo.resultDetails = NULL;
8599     }
8600     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8601                               moveList[forwardMostMove - 1]);
8602     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8603                              PosFlags(forwardMostMove - 1),
8604                              fromY, fromX, toY, toX, promoChar,
8605                              parseList[forwardMostMove - 1]);
8606     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8607       case MT_NONE:
8608       case MT_STALEMATE:
8609       default:
8610         break;
8611       case MT_CHECK:
8612         if(gameInfo.variant != VariantShogi)
8613             strcat(parseList[forwardMostMove - 1], "+");
8614         break;
8615       case MT_CHECKMATE:
8616       case MT_STAINMATE:
8617         strcat(parseList[forwardMostMove - 1], "#");
8618         break;
8619     }
8620     if (appData.debugMode) {
8621         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8622     }
8623
8624 }
8625
8626 /* Updates currentMove if not pausing */
8627 void
8628 ShowMove(fromX, fromY, toX, toY)
8629 {
8630     int instant = (gameMode == PlayFromGameFile) ?
8631         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8632     if(appData.noGUI) return;
8633     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8634         if (!instant) {
8635             if (forwardMostMove == currentMove + 1) {
8636                 AnimateMove(boards[forwardMostMove - 1],
8637                             fromX, fromY, toX, toY);
8638             }
8639             if (appData.highlightLastMove) {
8640                 SetHighlights(fromX, fromY, toX, toY);
8641             }
8642         }
8643         currentMove = forwardMostMove;
8644     }
8645
8646     if (instant) return;
8647
8648     DisplayMove(currentMove - 1);
8649     DrawPosition(FALSE, boards[currentMove]);
8650     DisplayBothClocks();
8651     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8652 }
8653
8654 void SendEgtPath(ChessProgramState *cps)
8655 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8656         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8657
8658         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8659
8660         while(*p) {
8661             char c, *q = name+1, *r, *s;
8662
8663             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8664             while(*p && *p != ',') *q++ = *p++;
8665             *q++ = ':'; *q = 0;
8666             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8667                 strcmp(name, ",nalimov:") == 0 ) {
8668                 // take nalimov path from the menu-changeable option first, if it is defined
8669                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8670                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8671             } else
8672             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8673                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8674                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8675                 s = r = StrStr(s, ":") + 1; // beginning of path info
8676                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8677                 c = *r; *r = 0;             // temporarily null-terminate path info
8678                     *--q = 0;               // strip of trailig ':' from name
8679                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8680                 *r = c;
8681                 SendToProgram(buf,cps);     // send egtbpath command for this format
8682             }
8683             if(*p == ',') p++; // read away comma to position for next format name
8684         }
8685 }
8686
8687 void
8688 InitChessProgram(cps, setup)
8689      ChessProgramState *cps;
8690      int setup; /* [HGM] needed to setup FRC opening position */
8691 {
8692     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8693     if (appData.noChessProgram) return;
8694     hintRequested = FALSE;
8695     bookRequested = FALSE;
8696
8697     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8698     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8699     if(cps->memSize) { /* [HGM] memory */
8700         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8701         SendToProgram(buf, cps);
8702     }
8703     SendEgtPath(cps); /* [HGM] EGT */
8704     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8705         sprintf(buf, "cores %d\n", appData.smpCores);
8706         SendToProgram(buf, cps);
8707     }
8708
8709     SendToProgram(cps->initString, cps);
8710     if (gameInfo.variant != VariantNormal &&
8711         gameInfo.variant != VariantLoadable
8712         /* [HGM] also send variant if board size non-standard */
8713         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8714                                             ) {
8715       char *v = VariantName(gameInfo.variant);
8716       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8717         /* [HGM] in protocol 1 we have to assume all variants valid */
8718         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8719         DisplayFatalError(buf, 0, 1);
8720         return;
8721       }
8722
8723       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8724       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8725       if( gameInfo.variant == VariantXiangqi )
8726            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8727       if( gameInfo.variant == VariantShogi )
8728            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8729       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8730            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8731       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8732                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8733            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8734       if( gameInfo.variant == VariantCourier )
8735            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8736       if( gameInfo.variant == VariantSuper )
8737            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8738       if( gameInfo.variant == VariantGreat )
8739            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8740
8741       if(overruled) {
8742            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8743                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8744            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8745            if(StrStr(cps->variants, b) == NULL) { 
8746                // specific sized variant not known, check if general sizing allowed
8747                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8748                    if(StrStr(cps->variants, "boardsize") == NULL) {
8749                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8750                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8751                        DisplayFatalError(buf, 0, 1);
8752                        return;
8753                    }
8754                    /* [HGM] here we really should compare with the maximum supported board size */
8755                }
8756            }
8757       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8758       sprintf(buf, "variant %s\n", b);
8759       SendToProgram(buf, cps);
8760     }
8761     currentlyInitializedVariant = gameInfo.variant;
8762
8763     /* [HGM] send opening position in FRC to first engine */
8764     if(setup) {
8765           SendToProgram("force\n", cps);
8766           SendBoard(cps, 0);
8767           /* engine is now in force mode! Set flag to wake it up after first move. */
8768           setboardSpoiledMachineBlack = 1;
8769     }
8770
8771     if (cps->sendICS) {
8772       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8773       SendToProgram(buf, cps);
8774     }
8775     cps->maybeThinking = FALSE;
8776     cps->offeredDraw = 0;
8777     if (!appData.icsActive) {
8778         SendTimeControl(cps, movesPerSession, timeControl,
8779                         timeIncrement, appData.searchDepth,
8780                         searchTime);
8781     }
8782     if (appData.showThinking 
8783         // [HGM] thinking: four options require thinking output to be sent
8784         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8785                                 ) {
8786         SendToProgram("post\n", cps);
8787     }
8788     SendToProgram("hard\n", cps);
8789     if (!appData.ponderNextMove) {
8790         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8791            it without being sure what state we are in first.  "hard"
8792            is not a toggle, so that one is OK.
8793          */
8794         SendToProgram("easy\n", cps);
8795     }
8796     if (cps->usePing) {
8797       sprintf(buf, "ping %d\n", ++cps->lastPing);
8798       SendToProgram(buf, cps);
8799     }
8800     cps->initDone = TRUE;
8801 }   
8802
8803
8804 void
8805 StartChessProgram(cps)
8806      ChessProgramState *cps;
8807 {
8808     char buf[MSG_SIZ];
8809     int err;
8810
8811     if (appData.noChessProgram) return;
8812     cps->initDone = FALSE;
8813
8814     if (strcmp(cps->host, "localhost") == 0) {
8815         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8816     } else if (*appData.remoteShell == NULLCHAR) {
8817         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8818     } else {
8819         if (*appData.remoteUser == NULLCHAR) {
8820           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8821                     cps->program);
8822         } else {
8823           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8824                     cps->host, appData.remoteUser, cps->program);
8825         }
8826         err = StartChildProcess(buf, "", &cps->pr);
8827     }
8828     
8829     if (err != 0) {
8830         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8831         DisplayFatalError(buf, err, 1);
8832         cps->pr = NoProc;
8833         cps->isr = NULL;
8834         return;
8835     }
8836     
8837     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8838     if (cps->protocolVersion > 1) {
8839       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8840       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8841       cps->comboCnt = 0;  //                and values of combo boxes
8842       SendToProgram(buf, cps);
8843     } else {
8844       SendToProgram("xboard\n", cps);
8845     }
8846 }
8847
8848
8849 void
8850 TwoMachinesEventIfReady P((void))
8851 {
8852   if (first.lastPing != first.lastPong) {
8853     DisplayMessage("", _("Waiting for first chess program"));
8854     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8855     return;
8856   }
8857   if (second.lastPing != second.lastPong) {
8858     DisplayMessage("", _("Waiting for second chess program"));
8859     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8860     return;
8861   }
8862   ThawUI();
8863   TwoMachinesEvent();
8864 }
8865
8866 void
8867 NextMatchGame P((void))
8868 {
8869     int index; /* [HGM] autoinc: step load index during match */
8870     Reset(FALSE, TRUE);
8871     if (*appData.loadGameFile != NULLCHAR) {
8872         index = appData.loadGameIndex;
8873         if(index < 0) { // [HGM] autoinc
8874             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8875             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8876         } 
8877         LoadGameFromFile(appData.loadGameFile,
8878                          index,
8879                          appData.loadGameFile, FALSE);
8880     } else if (*appData.loadPositionFile != NULLCHAR) {
8881         index = appData.loadPositionIndex;
8882         if(index < 0) { // [HGM] autoinc
8883             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8884             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8885         } 
8886         LoadPositionFromFile(appData.loadPositionFile,
8887                              index,
8888                              appData.loadPositionFile);
8889     }
8890     TwoMachinesEventIfReady();
8891 }
8892
8893 void UserAdjudicationEvent( int result )
8894 {
8895     ChessMove gameResult = GameIsDrawn;
8896
8897     if( result > 0 ) {
8898         gameResult = WhiteWins;
8899     }
8900     else if( result < 0 ) {
8901         gameResult = BlackWins;
8902     }
8903
8904     if( gameMode == TwoMachinesPlay ) {
8905         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8906     }
8907 }
8908
8909
8910 // [HGM] save: calculate checksum of game to make games easily identifiable
8911 int StringCheckSum(char *s)
8912 {
8913         int i = 0;
8914         if(s==NULL) return 0;
8915         while(*s) i = i*259 + *s++;
8916         return i;
8917 }
8918
8919 int GameCheckSum()
8920 {
8921         int i, sum=0;
8922         for(i=backwardMostMove; i<forwardMostMove; i++) {
8923                 sum += pvInfoList[i].depth;
8924                 sum += StringCheckSum(parseList[i]);
8925                 sum += StringCheckSum(commentList[i]);
8926                 sum *= 261;
8927         }
8928         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8929         return sum + StringCheckSum(commentList[i]);
8930 } // end of save patch
8931
8932 void
8933 GameEnds(result, resultDetails, whosays)
8934      ChessMove result;
8935      char *resultDetails;
8936      int whosays;
8937 {
8938     GameMode nextGameMode;
8939     int isIcsGame;
8940     char buf[MSG_SIZ];
8941
8942     if(endingGame) return; /* [HGM] crash: forbid recursion */
8943     endingGame = 1;
8944     if(twoBoards) { // [HGM] dual: switch back to one board
8945         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8946         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8947     }
8948     if (appData.debugMode) {
8949       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8950               result, resultDetails ? resultDetails : "(null)", whosays);
8951     }
8952
8953     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8954
8955     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8956         /* If we are playing on ICS, the server decides when the
8957            game is over, but the engine can offer to draw, claim 
8958            a draw, or resign. 
8959          */
8960 #if ZIPPY
8961         if (appData.zippyPlay && first.initDone) {
8962             if (result == GameIsDrawn) {
8963                 /* In case draw still needs to be claimed */
8964                 SendToICS(ics_prefix);
8965                 SendToICS("draw\n");
8966             } else if (StrCaseStr(resultDetails, "resign")) {
8967                 SendToICS(ics_prefix);
8968                 SendToICS("resign\n");
8969             }
8970         }
8971 #endif
8972         endingGame = 0; /* [HGM] crash */
8973         return;
8974     }
8975
8976     /* If we're loading the game from a file, stop */
8977     if (whosays == GE_FILE) {
8978       (void) StopLoadGameTimer();
8979       gameFileFP = NULL;
8980     }
8981
8982     /* Cancel draw offers */
8983     first.offeredDraw = second.offeredDraw = 0;
8984
8985     /* If this is an ICS game, only ICS can really say it's done;
8986        if not, anyone can. */
8987     isIcsGame = (gameMode == IcsPlayingWhite || 
8988                  gameMode == IcsPlayingBlack || 
8989                  gameMode == IcsObserving    || 
8990                  gameMode == IcsExamining);
8991
8992     if (!isIcsGame || whosays == GE_ICS) {
8993         /* OK -- not an ICS game, or ICS said it was done */
8994         StopClocks();
8995         if (!isIcsGame && !appData.noChessProgram) 
8996           SetUserThinkingEnables();
8997     
8998         /* [HGM] if a machine claims the game end we verify this claim */
8999         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9000             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9001                 char claimer;
9002                 ChessMove trueResult = (ChessMove) -1;
9003
9004                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9005                                             first.twoMachinesColor[0] :
9006                                             second.twoMachinesColor[0] ;
9007
9008                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9009                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9010                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9011                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9012                 } else
9013                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9014                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9015                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9016                 } else
9017                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9018                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9019                 }
9020
9021                 // now verify win claims, but not in drop games, as we don't understand those yet
9022                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9023                                                  || gameInfo.variant == VariantGreat) &&
9024                     (result == WhiteWins && claimer == 'w' ||
9025                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9026                       if (appData.debugMode) {
9027                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9028                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9029                       }
9030                       if(result != trueResult) {
9031                               sprintf(buf, "False win claim: '%s'", resultDetails);
9032                               result = claimer == 'w' ? BlackWins : WhiteWins;
9033                               resultDetails = buf;
9034                       }
9035                 } else
9036                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9037                     && (forwardMostMove <= backwardMostMove ||
9038                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9039                         (claimer=='b')==(forwardMostMove&1))
9040                                                                                   ) {
9041                       /* [HGM] verify: draws that were not flagged are false claims */
9042                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9043                       result = claimer == 'w' ? BlackWins : WhiteWins;
9044                       resultDetails = buf;
9045                 }
9046                 /* (Claiming a loss is accepted no questions asked!) */
9047             }
9048             /* [HGM] bare: don't allow bare King to win */
9049             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9050                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9051                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9052                && result != GameIsDrawn)
9053             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9054                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9055                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9056                         if(p >= 0 && p <= (int)WhiteKing) k++;
9057                 }
9058                 if (appData.debugMode) {
9059                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9060                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9061                 }
9062                 if(k <= 1) {
9063                         result = GameIsDrawn;
9064                         sprintf(buf, "%s but bare king", resultDetails);
9065                         resultDetails = buf;
9066                 }
9067             }
9068         }
9069
9070
9071         if(serverMoves != NULL && !loadFlag) { char c = '=';
9072             if(result==WhiteWins) c = '+';
9073             if(result==BlackWins) c = '-';
9074             if(resultDetails != NULL)
9075                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9076         }
9077         if (resultDetails != NULL) {
9078             gameInfo.result = result;
9079             gameInfo.resultDetails = StrSave(resultDetails);
9080
9081             /* display last move only if game was not loaded from file */
9082             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9083                 DisplayMove(currentMove - 1);
9084     
9085             if (forwardMostMove != 0) {
9086                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9087                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9088                                                                 ) {
9089                     if (*appData.saveGameFile != NULLCHAR) {
9090                         SaveGameToFile(appData.saveGameFile, TRUE);
9091                     } else if (appData.autoSaveGames) {
9092                         AutoSaveGame();
9093                     }
9094                     if (*appData.savePositionFile != NULLCHAR) {
9095                         SavePositionToFile(appData.savePositionFile);
9096                     }
9097                 }
9098             }
9099
9100             /* Tell program how game ended in case it is learning */
9101             /* [HGM] Moved this to after saving the PGN, just in case */
9102             /* engine died and we got here through time loss. In that */
9103             /* case we will get a fatal error writing the pipe, which */
9104             /* would otherwise lose us the PGN.                       */
9105             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9106             /* output during GameEnds should never be fatal anymore   */
9107             if (gameMode == MachinePlaysWhite ||
9108                 gameMode == MachinePlaysBlack ||
9109                 gameMode == TwoMachinesPlay ||
9110                 gameMode == IcsPlayingWhite ||
9111                 gameMode == IcsPlayingBlack ||
9112                 gameMode == BeginningOfGame) {
9113                 char buf[MSG_SIZ];
9114                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9115                         resultDetails);
9116                 if (first.pr != NoProc) {
9117                     SendToProgram(buf, &first);
9118                 }
9119                 if (second.pr != NoProc &&
9120                     gameMode == TwoMachinesPlay) {
9121                     SendToProgram(buf, &second);
9122                 }
9123             }
9124         }
9125
9126         if (appData.icsActive) {
9127             if (appData.quietPlay &&
9128                 (gameMode == IcsPlayingWhite ||
9129                  gameMode == IcsPlayingBlack)) {
9130                 SendToICS(ics_prefix);
9131                 SendToICS("set shout 1\n");
9132             }
9133             nextGameMode = IcsIdle;
9134             ics_user_moved = FALSE;
9135             /* clean up premove.  It's ugly when the game has ended and the
9136              * premove highlights are still on the board.
9137              */
9138             if (gotPremove) {
9139               gotPremove = FALSE;
9140               ClearPremoveHighlights();
9141               DrawPosition(FALSE, boards[currentMove]);
9142             }
9143             if (whosays == GE_ICS) {
9144                 switch (result) {
9145                 case WhiteWins:
9146                     if (gameMode == IcsPlayingWhite)
9147                         PlayIcsWinSound();
9148                     else if(gameMode == IcsPlayingBlack)
9149                         PlayIcsLossSound();
9150                     break;
9151                 case BlackWins:
9152                     if (gameMode == IcsPlayingBlack)
9153                         PlayIcsWinSound();
9154                     else if(gameMode == IcsPlayingWhite)
9155                         PlayIcsLossSound();
9156                     break;
9157                 case GameIsDrawn:
9158                     PlayIcsDrawSound();
9159                     break;
9160                 default:
9161                     PlayIcsUnfinishedSound();
9162                 }
9163             }
9164         } else if (gameMode == EditGame ||
9165                    gameMode == PlayFromGameFile || 
9166                    gameMode == AnalyzeMode || 
9167                    gameMode == AnalyzeFile) {
9168             nextGameMode = gameMode;
9169         } else {
9170             nextGameMode = EndOfGame;
9171         }
9172         pausing = FALSE;
9173         ModeHighlight();
9174     } else {
9175         nextGameMode = gameMode;
9176     }
9177
9178     if (appData.noChessProgram) {
9179         gameMode = nextGameMode;
9180         ModeHighlight();
9181         endingGame = 0; /* [HGM] crash */
9182         return;
9183     }
9184
9185     if (first.reuse) {
9186         /* Put first chess program into idle state */
9187         if (first.pr != NoProc &&
9188             (gameMode == MachinePlaysWhite ||
9189              gameMode == MachinePlaysBlack ||
9190              gameMode == TwoMachinesPlay ||
9191              gameMode == IcsPlayingWhite ||
9192              gameMode == IcsPlayingBlack ||
9193              gameMode == BeginningOfGame)) {
9194             SendToProgram("force\n", &first);
9195             if (first.usePing) {
9196               char buf[MSG_SIZ];
9197               sprintf(buf, "ping %d\n", ++first.lastPing);
9198               SendToProgram(buf, &first);
9199             }
9200         }
9201     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9202         /* Kill off first chess program */
9203         if (first.isr != NULL)
9204           RemoveInputSource(first.isr);
9205         first.isr = NULL;
9206     
9207         if (first.pr != NoProc) {
9208             ExitAnalyzeMode();
9209             DoSleep( appData.delayBeforeQuit );
9210             SendToProgram("quit\n", &first);
9211             DoSleep( appData.delayAfterQuit );
9212             DestroyChildProcess(first.pr, first.useSigterm);
9213         }
9214         first.pr = NoProc;
9215     }
9216     if (second.reuse) {
9217         /* Put second chess program into idle state */
9218         if (second.pr != NoProc &&
9219             gameMode == TwoMachinesPlay) {
9220             SendToProgram("force\n", &second);
9221             if (second.usePing) {
9222               char buf[MSG_SIZ];
9223               sprintf(buf, "ping %d\n", ++second.lastPing);
9224               SendToProgram(buf, &second);
9225             }
9226         }
9227     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9228         /* Kill off second chess program */
9229         if (second.isr != NULL)
9230           RemoveInputSource(second.isr);
9231         second.isr = NULL;
9232     
9233         if (second.pr != NoProc) {
9234             DoSleep( appData.delayBeforeQuit );
9235             SendToProgram("quit\n", &second);
9236             DoSleep( appData.delayAfterQuit );
9237             DestroyChildProcess(second.pr, second.useSigterm);
9238         }
9239         second.pr = NoProc;
9240     }
9241
9242     if (matchMode && gameMode == TwoMachinesPlay) {
9243         switch (result) {
9244         case WhiteWins:
9245           if (first.twoMachinesColor[0] == 'w') {
9246             first.matchWins++;
9247           } else {
9248             second.matchWins++;
9249           }
9250           break;
9251         case BlackWins:
9252           if (first.twoMachinesColor[0] == 'b') {
9253             first.matchWins++;
9254           } else {
9255             second.matchWins++;
9256           }
9257           break;
9258         default:
9259           break;
9260         }
9261         if (matchGame < appData.matchGames) {
9262             char *tmp;
9263             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9264                 tmp = first.twoMachinesColor;
9265                 first.twoMachinesColor = second.twoMachinesColor;
9266                 second.twoMachinesColor = tmp;
9267             }
9268             gameMode = nextGameMode;
9269             matchGame++;
9270             if(appData.matchPause>10000 || appData.matchPause<10)
9271                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9272             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9273             endingGame = 0; /* [HGM] crash */
9274             return;
9275         } else {
9276             char buf[MSG_SIZ];
9277             gameMode = nextGameMode;
9278             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9279                     first.tidy, second.tidy,
9280                     first.matchWins, second.matchWins,
9281                     appData.matchGames - (first.matchWins + second.matchWins));
9282             DisplayFatalError(buf, 0, 0);
9283         }
9284     }
9285     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9286         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9287       ExitAnalyzeMode();
9288     gameMode = nextGameMode;
9289     ModeHighlight();
9290     endingGame = 0;  /* [HGM] crash */
9291 }
9292
9293 /* Assumes program was just initialized (initString sent).
9294    Leaves program in force mode. */
9295 void
9296 FeedMovesToProgram(cps, upto) 
9297      ChessProgramState *cps;
9298      int upto;
9299 {
9300     int i;
9301     
9302     if (appData.debugMode)
9303       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9304               startedFromSetupPosition ? "position and " : "",
9305               backwardMostMove, upto, cps->which);
9306     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9307         // [HGM] variantswitch: make engine aware of new variant
9308         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9309                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9310         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9311         SendToProgram(buf, cps);
9312         currentlyInitializedVariant = gameInfo.variant;
9313     }
9314     SendToProgram("force\n", cps);
9315     if (startedFromSetupPosition) {
9316         SendBoard(cps, backwardMostMove);
9317     if (appData.debugMode) {
9318         fprintf(debugFP, "feedMoves\n");
9319     }
9320     }
9321     for (i = backwardMostMove; i < upto; i++) {
9322         SendMoveToProgram(i, cps);
9323     }
9324 }
9325
9326
9327 void
9328 ResurrectChessProgram()
9329 {
9330      /* The chess program may have exited.
9331         If so, restart it and feed it all the moves made so far. */
9332
9333     if (appData.noChessProgram || first.pr != NoProc) return;
9334     
9335     StartChessProgram(&first);
9336     InitChessProgram(&first, FALSE);
9337     FeedMovesToProgram(&first, currentMove);
9338
9339     if (!first.sendTime) {
9340         /* can't tell gnuchess what its clock should read,
9341            so we bow to its notion. */
9342         ResetClocks();
9343         timeRemaining[0][currentMove] = whiteTimeRemaining;
9344         timeRemaining[1][currentMove] = blackTimeRemaining;
9345     }
9346
9347     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9348                 appData.icsEngineAnalyze) && first.analysisSupport) {
9349       SendToProgram("analyze\n", &first);
9350       first.analyzing = TRUE;
9351     }
9352 }
9353
9354 /*
9355  * Button procedures
9356  */
9357 void
9358 Reset(redraw, init)
9359      int redraw, init;
9360 {
9361     int i;
9362
9363     if (appData.debugMode) {
9364         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9365                 redraw, init, gameMode);
9366     }
9367     CleanupTail(); // [HGM] vari: delete any stored variations
9368     pausing = pauseExamInvalid = FALSE;
9369     startedFromSetupPosition = blackPlaysFirst = FALSE;
9370     firstMove = TRUE;
9371     whiteFlag = blackFlag = FALSE;
9372     userOfferedDraw = FALSE;
9373     hintRequested = bookRequested = FALSE;
9374     first.maybeThinking = FALSE;
9375     second.maybeThinking = FALSE;
9376     first.bookSuspend = FALSE; // [HGM] book
9377     second.bookSuspend = FALSE;
9378     thinkOutput[0] = NULLCHAR;
9379     lastHint[0] = NULLCHAR;
9380     ClearGameInfo(&gameInfo);
9381     gameInfo.variant = StringToVariant(appData.variant);
9382     ics_user_moved = ics_clock_paused = FALSE;
9383     ics_getting_history = H_FALSE;
9384     ics_gamenum = -1;
9385     white_holding[0] = black_holding[0] = NULLCHAR;
9386     ClearProgramStats();
9387     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9388     
9389     ResetFrontEnd();
9390     ClearHighlights();
9391     flipView = appData.flipView;
9392     ClearPremoveHighlights();
9393     gotPremove = FALSE;
9394     alarmSounded = FALSE;
9395
9396     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9397     if(appData.serverMovesName != NULL) {
9398         /* [HGM] prepare to make moves file for broadcasting */
9399         clock_t t = clock();
9400         if(serverMoves != NULL) fclose(serverMoves);
9401         serverMoves = fopen(appData.serverMovesName, "r");
9402         if(serverMoves != NULL) {
9403             fclose(serverMoves);
9404             /* delay 15 sec before overwriting, so all clients can see end */
9405             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9406         }
9407         serverMoves = fopen(appData.serverMovesName, "w");
9408     }
9409
9410     ExitAnalyzeMode();
9411     gameMode = BeginningOfGame;
9412     ModeHighlight();
9413     if(appData.icsActive) gameInfo.variant = VariantNormal;
9414     currentMove = forwardMostMove = backwardMostMove = 0;
9415     InitPosition(redraw);
9416     for (i = 0; i < MAX_MOVES; i++) {
9417         if (commentList[i] != NULL) {
9418             free(commentList[i]);
9419             commentList[i] = NULL;
9420         }
9421     }
9422     ResetClocks();
9423     timeRemaining[0][0] = whiteTimeRemaining;
9424     timeRemaining[1][0] = blackTimeRemaining;
9425     if (first.pr == NULL) {
9426         StartChessProgram(&first);
9427     }
9428     if (init) {
9429             InitChessProgram(&first, startedFromSetupPosition);
9430     }
9431     DisplayTitle("");
9432     DisplayMessage("", "");
9433     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9434     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9435 }
9436
9437 void
9438 AutoPlayGameLoop()
9439 {
9440     for (;;) {
9441         if (!AutoPlayOneMove())
9442           return;
9443         if (matchMode || appData.timeDelay == 0)
9444           continue;
9445         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9446           return;
9447         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9448         break;
9449     }
9450 }
9451
9452
9453 int
9454 AutoPlayOneMove()
9455 {
9456     int fromX, fromY, toX, toY;
9457
9458     if (appData.debugMode) {
9459       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9460     }
9461
9462     if (gameMode != PlayFromGameFile)
9463       return FALSE;
9464
9465     if (currentMove >= forwardMostMove) {
9466       gameMode = EditGame;
9467       ModeHighlight();
9468
9469       /* [AS] Clear current move marker at the end of a game */
9470       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9471
9472       return FALSE;
9473     }
9474     
9475     toX = moveList[currentMove][2] - AAA;
9476     toY = moveList[currentMove][3] - ONE;
9477
9478     if (moveList[currentMove][1] == '@') {
9479         if (appData.highlightLastMove) {
9480             SetHighlights(-1, -1, toX, toY);
9481         }
9482     } else {
9483         fromX = moveList[currentMove][0] - AAA;
9484         fromY = moveList[currentMove][1] - ONE;
9485
9486         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9487
9488         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9489
9490         if (appData.highlightLastMove) {
9491             SetHighlights(fromX, fromY, toX, toY);
9492         }
9493     }
9494     DisplayMove(currentMove);
9495     SendMoveToProgram(currentMove++, &first);
9496     DisplayBothClocks();
9497     DrawPosition(FALSE, boards[currentMove]);
9498     // [HGM] PV info: always display, routine tests if empty
9499     DisplayComment(currentMove - 1, commentList[currentMove]);
9500     return TRUE;
9501 }
9502
9503
9504 int
9505 LoadGameOneMove(readAhead)
9506      ChessMove readAhead;
9507 {
9508     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9509     char promoChar = NULLCHAR;
9510     ChessMove moveType;
9511     char move[MSG_SIZ];
9512     char *p, *q;
9513     
9514     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9515         gameMode != AnalyzeMode && gameMode != Training) {
9516         gameFileFP = NULL;
9517         return FALSE;
9518     }
9519     
9520     yyboardindex = forwardMostMove;
9521     if (readAhead != (ChessMove)0) {
9522       moveType = readAhead;
9523     } else {
9524       if (gameFileFP == NULL)
9525           return FALSE;
9526       moveType = (ChessMove) yylex();
9527     }
9528     
9529     done = FALSE;
9530     switch (moveType) {
9531       case Comment:
9532         if (appData.debugMode) 
9533           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9534         p = yy_text;
9535
9536         /* append the comment but don't display it */
9537         AppendComment(currentMove, p, FALSE);
9538         return TRUE;
9539
9540       case WhiteCapturesEnPassant:
9541       case BlackCapturesEnPassant:
9542       case WhitePromotionChancellor:
9543       case BlackPromotionChancellor:
9544       case WhitePromotionArchbishop:
9545       case BlackPromotionArchbishop:
9546       case WhitePromotionCentaur:
9547       case BlackPromotionCentaur:
9548       case WhitePromotionQueen:
9549       case BlackPromotionQueen:
9550       case WhitePromotionRook:
9551       case BlackPromotionRook:
9552       case WhitePromotionBishop:
9553       case BlackPromotionBishop:
9554       case WhitePromotionKnight:
9555       case BlackPromotionKnight:
9556       case WhitePromotionKing:
9557       case BlackPromotionKing:
9558       case NormalMove:
9559       case WhiteKingSideCastle:
9560       case WhiteQueenSideCastle:
9561       case BlackKingSideCastle:
9562       case BlackQueenSideCastle:
9563       case WhiteKingSideCastleWild:
9564       case WhiteQueenSideCastleWild:
9565       case BlackKingSideCastleWild:
9566       case BlackQueenSideCastleWild:
9567       /* PUSH Fabien */
9568       case WhiteHSideCastleFR:
9569       case WhiteASideCastleFR:
9570       case BlackHSideCastleFR:
9571       case BlackASideCastleFR:
9572       /* POP Fabien */
9573         if (appData.debugMode)
9574           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9575         fromX = currentMoveString[0] - AAA;
9576         fromY = currentMoveString[1] - ONE;
9577         toX = currentMoveString[2] - AAA;
9578         toY = currentMoveString[3] - ONE;
9579         promoChar = currentMoveString[4];
9580         break;
9581
9582       case WhiteDrop:
9583       case BlackDrop:
9584         if (appData.debugMode)
9585           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9586         fromX = moveType == WhiteDrop ?
9587           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9588         (int) CharToPiece(ToLower(currentMoveString[0]));
9589         fromY = DROP_RANK;
9590         toX = currentMoveString[2] - AAA;
9591         toY = currentMoveString[3] - ONE;
9592         break;
9593
9594       case WhiteWins:
9595       case BlackWins:
9596       case GameIsDrawn:
9597       case GameUnfinished:
9598         if (appData.debugMode)
9599           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9600         p = strchr(yy_text, '{');
9601         if (p == NULL) p = strchr(yy_text, '(');
9602         if (p == NULL) {
9603             p = yy_text;
9604             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9605         } else {
9606             q = strchr(p, *p == '{' ? '}' : ')');
9607             if (q != NULL) *q = NULLCHAR;
9608             p++;
9609         }
9610         GameEnds(moveType, p, GE_FILE);
9611         done = TRUE;
9612         if (cmailMsgLoaded) {
9613             ClearHighlights();
9614             flipView = WhiteOnMove(currentMove);
9615             if (moveType == GameUnfinished) flipView = !flipView;
9616             if (appData.debugMode)
9617               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9618         }
9619         break;
9620
9621       case (ChessMove) 0:       /* end of file */
9622         if (appData.debugMode)
9623           fprintf(debugFP, "Parser hit end of file\n");
9624         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9625           case MT_NONE:
9626           case MT_CHECK:
9627             break;
9628           case MT_CHECKMATE:
9629           case MT_STAINMATE:
9630             if (WhiteOnMove(currentMove)) {
9631                 GameEnds(BlackWins, "Black mates", GE_FILE);
9632             } else {
9633                 GameEnds(WhiteWins, "White mates", GE_FILE);
9634             }
9635             break;
9636           case MT_STALEMATE:
9637             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9638             break;
9639         }
9640         done = TRUE;
9641         break;
9642
9643       case MoveNumberOne:
9644         if (lastLoadGameStart == GNUChessGame) {
9645             /* GNUChessGames have numbers, but they aren't move numbers */
9646             if (appData.debugMode)
9647               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9648                       yy_text, (int) moveType);
9649             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9650         }
9651         /* else fall thru */
9652
9653       case XBoardGame:
9654       case GNUChessGame:
9655       case PGNTag:
9656         /* Reached start of next game in file */
9657         if (appData.debugMode)
9658           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9659         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9660           case MT_NONE:
9661           case MT_CHECK:
9662             break;
9663           case MT_CHECKMATE:
9664           case MT_STAINMATE:
9665             if (WhiteOnMove(currentMove)) {
9666                 GameEnds(BlackWins, "Black mates", GE_FILE);
9667             } else {
9668                 GameEnds(WhiteWins, "White mates", GE_FILE);
9669             }
9670             break;
9671           case MT_STALEMATE:
9672             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9673             break;
9674         }
9675         done = TRUE;
9676         break;
9677
9678       case PositionDiagram:     /* should not happen; ignore */
9679       case ElapsedTime:         /* ignore */
9680       case NAG:                 /* ignore */
9681         if (appData.debugMode)
9682           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9683                   yy_text, (int) moveType);
9684         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9685
9686       case IllegalMove:
9687         if (appData.testLegality) {
9688             if (appData.debugMode)
9689               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9690             sprintf(move, _("Illegal move: %d.%s%s"),
9691                     (forwardMostMove / 2) + 1,
9692                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9693             DisplayError(move, 0);
9694             done = TRUE;
9695         } else {
9696             if (appData.debugMode)
9697               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9698                       yy_text, currentMoveString);
9699             fromX = currentMoveString[0] - AAA;
9700             fromY = currentMoveString[1] - ONE;
9701             toX = currentMoveString[2] - AAA;
9702             toY = currentMoveString[3] - ONE;
9703             promoChar = currentMoveString[4];
9704         }
9705         break;
9706
9707       case AmbiguousMove:
9708         if (appData.debugMode)
9709           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9710         sprintf(move, _("Ambiguous move: %d.%s%s"),
9711                 (forwardMostMove / 2) + 1,
9712                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9713         DisplayError(move, 0);
9714         done = TRUE;
9715         break;
9716
9717       default:
9718       case ImpossibleMove:
9719         if (appData.debugMode)
9720           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9721         sprintf(move, _("Illegal move: %d.%s%s"),
9722                 (forwardMostMove / 2) + 1,
9723                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9724         DisplayError(move, 0);
9725         done = TRUE;
9726         break;
9727     }
9728
9729     if (done) {
9730         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9731             DrawPosition(FALSE, boards[currentMove]);
9732             DisplayBothClocks();
9733             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9734               DisplayComment(currentMove - 1, commentList[currentMove]);
9735         }
9736         (void) StopLoadGameTimer();
9737         gameFileFP = NULL;
9738         cmailOldMove = forwardMostMove;
9739         return FALSE;
9740     } else {
9741         /* currentMoveString is set as a side-effect of yylex */
9742         strcat(currentMoveString, "\n");
9743         strcpy(moveList[forwardMostMove], currentMoveString);
9744         
9745         thinkOutput[0] = NULLCHAR;
9746         MakeMove(fromX, fromY, toX, toY, promoChar);
9747         currentMove = forwardMostMove;
9748         return TRUE;
9749     }
9750 }
9751
9752 /* Load the nth game from the given file */
9753 int
9754 LoadGameFromFile(filename, n, title, useList)
9755      char *filename;
9756      int n;
9757      char *title;
9758      /*Boolean*/ int useList;
9759 {
9760     FILE *f;
9761     char buf[MSG_SIZ];
9762
9763     if (strcmp(filename, "-") == 0) {
9764         f = stdin;
9765         title = "stdin";
9766     } else {
9767         f = fopen(filename, "rb");
9768         if (f == NULL) {
9769           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9770             DisplayError(buf, errno);
9771             return FALSE;
9772         }
9773     }
9774     if (fseek(f, 0, 0) == -1) {
9775         /* f is not seekable; probably a pipe */
9776         useList = FALSE;
9777     }
9778     if (useList && n == 0) {
9779         int error = GameListBuild(f);
9780         if (error) {
9781             DisplayError(_("Cannot build game list"), error);
9782         } else if (!ListEmpty(&gameList) &&
9783                    ((ListGame *) gameList.tailPred)->number > 1) {
9784             GameListPopUp(f, title);
9785             return TRUE;
9786         }
9787         GameListDestroy();
9788         n = 1;
9789     }
9790     if (n == 0) n = 1;
9791     return LoadGame(f, n, title, FALSE);
9792 }
9793
9794
9795 void
9796 MakeRegisteredMove()
9797 {
9798     int fromX, fromY, toX, toY;
9799     char promoChar;
9800     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9801         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9802           case CMAIL_MOVE:
9803           case CMAIL_DRAW:
9804             if (appData.debugMode)
9805               fprintf(debugFP, "Restoring %s for game %d\n",
9806                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9807     
9808             thinkOutput[0] = NULLCHAR;
9809             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9810             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9811             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9812             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9813             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9814             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9815             MakeMove(fromX, fromY, toX, toY, promoChar);
9816             ShowMove(fromX, fromY, toX, toY);
9817               
9818             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9819               case MT_NONE:
9820               case MT_CHECK:
9821                 break;
9822                 
9823               case MT_CHECKMATE:
9824               case MT_STAINMATE:
9825                 if (WhiteOnMove(currentMove)) {
9826                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9827                 } else {
9828                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9829                 }
9830                 break;
9831                 
9832               case MT_STALEMATE:
9833                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9834                 break;
9835             }
9836
9837             break;
9838             
9839           case CMAIL_RESIGN:
9840             if (WhiteOnMove(currentMove)) {
9841                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9842             } else {
9843                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9844             }
9845             break;
9846             
9847           case CMAIL_ACCEPT:
9848             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9849             break;
9850               
9851           default:
9852             break;
9853         }
9854     }
9855
9856     return;
9857 }
9858
9859 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9860 int
9861 CmailLoadGame(f, gameNumber, title, useList)
9862      FILE *f;
9863      int gameNumber;
9864      char *title;
9865      int useList;
9866 {
9867     int retVal;
9868
9869     if (gameNumber > nCmailGames) {
9870         DisplayError(_("No more games in this message"), 0);
9871         return FALSE;
9872     }
9873     if (f == lastLoadGameFP) {
9874         int offset = gameNumber - lastLoadGameNumber;
9875         if (offset == 0) {
9876             cmailMsg[0] = NULLCHAR;
9877             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9878                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9879                 nCmailMovesRegistered--;
9880             }
9881             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9882             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9883                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9884             }
9885         } else {
9886             if (! RegisterMove()) return FALSE;
9887         }
9888     }
9889
9890     retVal = LoadGame(f, gameNumber, title, useList);
9891
9892     /* Make move registered during previous look at this game, if any */
9893     MakeRegisteredMove();
9894
9895     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9896         commentList[currentMove]
9897           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9898         DisplayComment(currentMove - 1, commentList[currentMove]);
9899     }
9900
9901     return retVal;
9902 }
9903
9904 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9905 int
9906 ReloadGame(offset)
9907      int offset;
9908 {
9909     int gameNumber = lastLoadGameNumber + offset;
9910     if (lastLoadGameFP == NULL) {
9911         DisplayError(_("No game has been loaded yet"), 0);
9912         return FALSE;
9913     }
9914     if (gameNumber <= 0) {
9915         DisplayError(_("Can't back up any further"), 0);
9916         return FALSE;
9917     }
9918     if (cmailMsgLoaded) {
9919         return CmailLoadGame(lastLoadGameFP, gameNumber,
9920                              lastLoadGameTitle, lastLoadGameUseList);
9921     } else {
9922         return LoadGame(lastLoadGameFP, gameNumber,
9923                         lastLoadGameTitle, lastLoadGameUseList);
9924     }
9925 }
9926
9927
9928
9929 /* Load the nth game from open file f */
9930 int
9931 LoadGame(f, gameNumber, title, useList)
9932      FILE *f;
9933      int gameNumber;
9934      char *title;
9935      int useList;
9936 {
9937     ChessMove cm;
9938     char buf[MSG_SIZ];
9939     int gn = gameNumber;
9940     ListGame *lg = NULL;
9941     int numPGNTags = 0;
9942     int err;
9943     GameMode oldGameMode;
9944     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9945
9946     if (appData.debugMode) 
9947         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9948
9949     if (gameMode == Training )
9950         SetTrainingModeOff();
9951
9952     oldGameMode = gameMode;
9953     if (gameMode != BeginningOfGame) {
9954       Reset(FALSE, TRUE);
9955     }
9956
9957     gameFileFP = f;
9958     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9959         fclose(lastLoadGameFP);
9960     }
9961
9962     if (useList) {
9963         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9964         
9965         if (lg) {
9966             fseek(f, lg->offset, 0);
9967             GameListHighlight(gameNumber);
9968             gn = 1;
9969         }
9970         else {
9971             DisplayError(_("Game number out of range"), 0);
9972             return FALSE;
9973         }
9974     } else {
9975         GameListDestroy();
9976         if (fseek(f, 0, 0) == -1) {
9977             if (f == lastLoadGameFP ?
9978                 gameNumber == lastLoadGameNumber + 1 :
9979                 gameNumber == 1) {
9980                 gn = 1;
9981             } else {
9982                 DisplayError(_("Can't seek on game file"), 0);
9983                 return FALSE;
9984             }
9985         }
9986     }
9987     lastLoadGameFP = f;
9988     lastLoadGameNumber = gameNumber;
9989     strcpy(lastLoadGameTitle, title);
9990     lastLoadGameUseList = useList;
9991
9992     yynewfile(f);
9993
9994     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9995       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9996                 lg->gameInfo.black);
9997             DisplayTitle(buf);
9998     } else if (*title != NULLCHAR) {
9999         if (gameNumber > 1) {
10000             sprintf(buf, "%s %d", title, gameNumber);
10001             DisplayTitle(buf);
10002         } else {
10003             DisplayTitle(title);
10004         }
10005     }
10006
10007     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10008         gameMode = PlayFromGameFile;
10009         ModeHighlight();
10010     }
10011
10012     currentMove = forwardMostMove = backwardMostMove = 0;
10013     CopyBoard(boards[0], initialPosition);
10014     StopClocks();
10015
10016     /*
10017      * Skip the first gn-1 games in the file.
10018      * Also skip over anything that precedes an identifiable 
10019      * start of game marker, to avoid being confused by 
10020      * garbage at the start of the file.  Currently 
10021      * recognized start of game markers are the move number "1",
10022      * the pattern "gnuchess .* game", the pattern
10023      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10024      * A game that starts with one of the latter two patterns
10025      * will also have a move number 1, possibly
10026      * following a position diagram.
10027      * 5-4-02: Let's try being more lenient and allowing a game to
10028      * start with an unnumbered move.  Does that break anything?
10029      */
10030     cm = lastLoadGameStart = (ChessMove) 0;
10031     while (gn > 0) {
10032         yyboardindex = forwardMostMove;
10033         cm = (ChessMove) yylex();
10034         switch (cm) {
10035           case (ChessMove) 0:
10036             if (cmailMsgLoaded) {
10037                 nCmailGames = CMAIL_MAX_GAMES - gn;
10038             } else {
10039                 Reset(TRUE, TRUE);
10040                 DisplayError(_("Game not found in file"), 0);
10041             }
10042             return FALSE;
10043
10044           case GNUChessGame:
10045           case XBoardGame:
10046             gn--;
10047             lastLoadGameStart = cm;
10048             break;
10049             
10050           case MoveNumberOne:
10051             switch (lastLoadGameStart) {
10052               case GNUChessGame:
10053               case XBoardGame:
10054               case PGNTag:
10055                 break;
10056               case MoveNumberOne:
10057               case (ChessMove) 0:
10058                 gn--;           /* count this game */
10059                 lastLoadGameStart = cm;
10060                 break;
10061               default:
10062                 /* impossible */
10063                 break;
10064             }
10065             break;
10066
10067           case PGNTag:
10068             switch (lastLoadGameStart) {
10069               case GNUChessGame:
10070               case PGNTag:
10071               case MoveNumberOne:
10072               case (ChessMove) 0:
10073                 gn--;           /* count this game */
10074                 lastLoadGameStart = cm;
10075                 break;
10076               case XBoardGame:
10077                 lastLoadGameStart = cm; /* game counted already */
10078                 break;
10079               default:
10080                 /* impossible */
10081                 break;
10082             }
10083             if (gn > 0) {
10084                 do {
10085                     yyboardindex = forwardMostMove;
10086                     cm = (ChessMove) yylex();
10087                 } while (cm == PGNTag || cm == Comment);
10088             }
10089             break;
10090
10091           case WhiteWins:
10092           case BlackWins:
10093           case GameIsDrawn:
10094             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10095                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10096                     != CMAIL_OLD_RESULT) {
10097                     nCmailResults ++ ;
10098                     cmailResult[  CMAIL_MAX_GAMES
10099                                 - gn - 1] = CMAIL_OLD_RESULT;
10100                 }
10101             }
10102             break;
10103
10104           case NormalMove:
10105             /* Only a NormalMove can be at the start of a game
10106              * without a position diagram. */
10107             if (lastLoadGameStart == (ChessMove) 0) {
10108               gn--;
10109               lastLoadGameStart = MoveNumberOne;
10110             }
10111             break;
10112
10113           default:
10114             break;
10115         }
10116     }
10117     
10118     if (appData.debugMode)
10119       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10120
10121     if (cm == XBoardGame) {
10122         /* Skip any header junk before position diagram and/or move 1 */
10123         for (;;) {
10124             yyboardindex = forwardMostMove;
10125             cm = (ChessMove) yylex();
10126
10127             if (cm == (ChessMove) 0 ||
10128                 cm == GNUChessGame || cm == XBoardGame) {
10129                 /* Empty game; pretend end-of-file and handle later */
10130                 cm = (ChessMove) 0;
10131                 break;
10132             }
10133
10134             if (cm == MoveNumberOne || cm == PositionDiagram ||
10135                 cm == PGNTag || cm == Comment)
10136               break;
10137         }
10138     } else if (cm == GNUChessGame) {
10139         if (gameInfo.event != NULL) {
10140             free(gameInfo.event);
10141         }
10142         gameInfo.event = StrSave(yy_text);
10143     }   
10144
10145     startedFromSetupPosition = FALSE;
10146     while (cm == PGNTag) {
10147         if (appData.debugMode) 
10148           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10149         err = ParsePGNTag(yy_text, &gameInfo);
10150         if (!err) numPGNTags++;
10151
10152         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10153         if(gameInfo.variant != oldVariant) {
10154             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10155             InitPosition(TRUE);
10156             oldVariant = gameInfo.variant;
10157             if (appData.debugMode) 
10158               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10159         }
10160
10161
10162         if (gameInfo.fen != NULL) {
10163           Board initial_position;
10164           startedFromSetupPosition = TRUE;
10165           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10166             Reset(TRUE, TRUE);
10167             DisplayError(_("Bad FEN position in file"), 0);
10168             return FALSE;
10169           }
10170           CopyBoard(boards[0], initial_position);
10171           if (blackPlaysFirst) {
10172             currentMove = forwardMostMove = backwardMostMove = 1;
10173             CopyBoard(boards[1], initial_position);
10174             strcpy(moveList[0], "");
10175             strcpy(parseList[0], "");
10176             timeRemaining[0][1] = whiteTimeRemaining;
10177             timeRemaining[1][1] = blackTimeRemaining;
10178             if (commentList[0] != NULL) {
10179               commentList[1] = commentList[0];
10180               commentList[0] = NULL;
10181             }
10182           } else {
10183             currentMove = forwardMostMove = backwardMostMove = 0;
10184           }
10185           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10186           {   int i;
10187               initialRulePlies = FENrulePlies;
10188               for( i=0; i< nrCastlingRights; i++ )
10189                   initialRights[i] = initial_position[CASTLING][i];
10190           }
10191           yyboardindex = forwardMostMove;
10192           free(gameInfo.fen);
10193           gameInfo.fen = NULL;
10194         }
10195
10196         yyboardindex = forwardMostMove;
10197         cm = (ChessMove) yylex();
10198
10199         /* Handle comments interspersed among the tags */
10200         while (cm == Comment) {
10201             char *p;
10202             if (appData.debugMode) 
10203               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10204             p = yy_text;
10205             AppendComment(currentMove, p, FALSE);
10206             yyboardindex = forwardMostMove;
10207             cm = (ChessMove) yylex();
10208         }
10209     }
10210
10211     /* don't rely on existence of Event tag since if game was
10212      * pasted from clipboard the Event tag may not exist
10213      */
10214     if (numPGNTags > 0){
10215         char *tags;
10216         if (gameInfo.variant == VariantNormal) {
10217           VariantClass v = StringToVariant(gameInfo.event);
10218           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10219           if(v < VariantShogi) gameInfo.variant = v;
10220         }
10221         if (!matchMode) {
10222           if( appData.autoDisplayTags ) {
10223             tags = PGNTags(&gameInfo);
10224             TagsPopUp(tags, CmailMsg());
10225             free(tags);
10226           }
10227         }
10228     } else {
10229         /* Make something up, but don't display it now */
10230         SetGameInfo();
10231         TagsPopDown();
10232     }
10233
10234     if (cm == PositionDiagram) {
10235         int i, j;
10236         char *p;
10237         Board initial_position;
10238
10239         if (appData.debugMode)
10240           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10241
10242         if (!startedFromSetupPosition) {
10243             p = yy_text;
10244             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10245               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10246                 switch (*p) {
10247                   case '[':
10248                   case '-':
10249                   case ' ':
10250                   case '\t':
10251                   case '\n':
10252                   case '\r':
10253                     break;
10254                   default:
10255                     initial_position[i][j++] = CharToPiece(*p);
10256                     break;
10257                 }
10258             while (*p == ' ' || *p == '\t' ||
10259                    *p == '\n' || *p == '\r') p++;
10260         
10261             if (strncmp(p, "black", strlen("black"))==0)
10262               blackPlaysFirst = TRUE;
10263             else
10264               blackPlaysFirst = FALSE;
10265             startedFromSetupPosition = TRUE;
10266         
10267             CopyBoard(boards[0], initial_position);
10268             if (blackPlaysFirst) {
10269                 currentMove = forwardMostMove = backwardMostMove = 1;
10270                 CopyBoard(boards[1], initial_position);
10271                 strcpy(moveList[0], "");
10272                 strcpy(parseList[0], "");
10273                 timeRemaining[0][1] = whiteTimeRemaining;
10274                 timeRemaining[1][1] = blackTimeRemaining;
10275                 if (commentList[0] != NULL) {
10276                     commentList[1] = commentList[0];
10277                     commentList[0] = NULL;
10278                 }
10279             } else {
10280                 currentMove = forwardMostMove = backwardMostMove = 0;
10281             }
10282         }
10283         yyboardindex = forwardMostMove;
10284         cm = (ChessMove) yylex();
10285     }
10286
10287     if (first.pr == NoProc) {
10288         StartChessProgram(&first);
10289     }
10290     InitChessProgram(&first, FALSE);
10291     SendToProgram("force\n", &first);
10292     if (startedFromSetupPosition) {
10293         SendBoard(&first, forwardMostMove);
10294     if (appData.debugMode) {
10295         fprintf(debugFP, "Load Game\n");
10296     }
10297         DisplayBothClocks();
10298     }      
10299
10300     /* [HGM] server: flag to write setup moves in broadcast file as one */
10301     loadFlag = appData.suppressLoadMoves;
10302
10303     while (cm == Comment) {
10304         char *p;
10305         if (appData.debugMode) 
10306           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10307         p = yy_text;
10308         AppendComment(currentMove, p, FALSE);
10309         yyboardindex = forwardMostMove;
10310         cm = (ChessMove) yylex();
10311     }
10312
10313     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10314         cm == WhiteWins || cm == BlackWins ||
10315         cm == GameIsDrawn || cm == GameUnfinished) {
10316         DisplayMessage("", _("No moves in game"));
10317         if (cmailMsgLoaded) {
10318             if (appData.debugMode)
10319               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10320             ClearHighlights();
10321             flipView = FALSE;
10322         }
10323         DrawPosition(FALSE, boards[currentMove]);
10324         DisplayBothClocks();
10325         gameMode = EditGame;
10326         ModeHighlight();
10327         gameFileFP = NULL;
10328         cmailOldMove = 0;
10329         return TRUE;
10330     }
10331
10332     // [HGM] PV info: routine tests if comment empty
10333     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10334         DisplayComment(currentMove - 1, commentList[currentMove]);
10335     }
10336     if (!matchMode && appData.timeDelay != 0) 
10337       DrawPosition(FALSE, boards[currentMove]);
10338
10339     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10340       programStats.ok_to_send = 1;
10341     }
10342
10343     /* if the first token after the PGN tags is a move
10344      * and not move number 1, retrieve it from the parser 
10345      */
10346     if (cm != MoveNumberOne)
10347         LoadGameOneMove(cm);
10348
10349     /* load the remaining moves from the file */
10350     while (LoadGameOneMove((ChessMove)0)) {
10351       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10352       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10353     }
10354
10355     /* rewind to the start of the game */
10356     currentMove = backwardMostMove;
10357
10358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10359
10360     if (oldGameMode == AnalyzeFile ||
10361         oldGameMode == AnalyzeMode) {
10362       AnalyzeFileEvent();
10363     }
10364
10365     if (matchMode || appData.timeDelay == 0) {
10366       ToEndEvent();
10367       gameMode = EditGame;
10368       ModeHighlight();
10369     } else if (appData.timeDelay > 0) {
10370       AutoPlayGameLoop();
10371     }
10372
10373     if (appData.debugMode) 
10374         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10375
10376     loadFlag = 0; /* [HGM] true game starts */
10377     return TRUE;
10378 }
10379
10380 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10381 int
10382 ReloadPosition(offset)
10383      int offset;
10384 {
10385     int positionNumber = lastLoadPositionNumber + offset;
10386     if (lastLoadPositionFP == NULL) {
10387         DisplayError(_("No position has been loaded yet"), 0);
10388         return FALSE;
10389     }
10390     if (positionNumber <= 0) {
10391         DisplayError(_("Can't back up any further"), 0);
10392         return FALSE;
10393     }
10394     return LoadPosition(lastLoadPositionFP, positionNumber,
10395                         lastLoadPositionTitle);
10396 }
10397
10398 /* Load the nth position from the given file */
10399 int
10400 LoadPositionFromFile(filename, n, title)
10401      char *filename;
10402      int n;
10403      char *title;
10404 {
10405     FILE *f;
10406     char buf[MSG_SIZ];
10407
10408     if (strcmp(filename, "-") == 0) {
10409         return LoadPosition(stdin, n, "stdin");
10410     } else {
10411         f = fopen(filename, "rb");
10412         if (f == NULL) {
10413             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10414             DisplayError(buf, errno);
10415             return FALSE;
10416         } else {
10417             return LoadPosition(f, n, title);
10418         }
10419     }
10420 }
10421
10422 /* Load the nth position from the given open file, and close it */
10423 int
10424 LoadPosition(f, positionNumber, title)
10425      FILE *f;
10426      int positionNumber;
10427      char *title;
10428 {
10429     char *p, line[MSG_SIZ];
10430     Board initial_position;
10431     int i, j, fenMode, pn;
10432     
10433     if (gameMode == Training )
10434         SetTrainingModeOff();
10435
10436     if (gameMode != BeginningOfGame) {
10437         Reset(FALSE, TRUE);
10438     }
10439     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10440         fclose(lastLoadPositionFP);
10441     }
10442     if (positionNumber == 0) positionNumber = 1;
10443     lastLoadPositionFP = f;
10444     lastLoadPositionNumber = positionNumber;
10445     strcpy(lastLoadPositionTitle, title);
10446     if (first.pr == NoProc) {
10447       StartChessProgram(&first);
10448       InitChessProgram(&first, FALSE);
10449     }    
10450     pn = positionNumber;
10451     if (positionNumber < 0) {
10452         /* Negative position number means to seek to that byte offset */
10453         if (fseek(f, -positionNumber, 0) == -1) {
10454             DisplayError(_("Can't seek on position file"), 0);
10455             return FALSE;
10456         };
10457         pn = 1;
10458     } else {
10459         if (fseek(f, 0, 0) == -1) {
10460             if (f == lastLoadPositionFP ?
10461                 positionNumber == lastLoadPositionNumber + 1 :
10462                 positionNumber == 1) {
10463                 pn = 1;
10464             } else {
10465                 DisplayError(_("Can't seek on position file"), 0);
10466                 return FALSE;
10467             }
10468         }
10469     }
10470     /* See if this file is FEN or old-style xboard */
10471     if (fgets(line, MSG_SIZ, f) == NULL) {
10472         DisplayError(_("Position not found in file"), 0);
10473         return FALSE;
10474     }
10475     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10476     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10477
10478     if (pn >= 2) {
10479         if (fenMode || line[0] == '#') pn--;
10480         while (pn > 0) {
10481             /* skip positions before number pn */
10482             if (fgets(line, MSG_SIZ, f) == NULL) {
10483                 Reset(TRUE, TRUE);
10484                 DisplayError(_("Position not found in file"), 0);
10485                 return FALSE;
10486             }
10487             if (fenMode || line[0] == '#') pn--;
10488         }
10489     }
10490
10491     if (fenMode) {
10492         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10493             DisplayError(_("Bad FEN position in file"), 0);
10494             return FALSE;
10495         }
10496     } else {
10497         (void) fgets(line, MSG_SIZ, f);
10498         (void) fgets(line, MSG_SIZ, f);
10499     
10500         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10501             (void) fgets(line, MSG_SIZ, f);
10502             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10503                 if (*p == ' ')
10504                   continue;
10505                 initial_position[i][j++] = CharToPiece(*p);
10506             }
10507         }
10508     
10509         blackPlaysFirst = FALSE;
10510         if (!feof(f)) {
10511             (void) fgets(line, MSG_SIZ, f);
10512             if (strncmp(line, "black", strlen("black"))==0)
10513               blackPlaysFirst = TRUE;
10514         }
10515     }
10516     startedFromSetupPosition = TRUE;
10517     
10518     SendToProgram("force\n", &first);
10519     CopyBoard(boards[0], initial_position);
10520     if (blackPlaysFirst) {
10521         currentMove = forwardMostMove = backwardMostMove = 1;
10522         strcpy(moveList[0], "");
10523         strcpy(parseList[0], "");
10524         CopyBoard(boards[1], initial_position);
10525         DisplayMessage("", _("Black to play"));
10526     } else {
10527         currentMove = forwardMostMove = backwardMostMove = 0;
10528         DisplayMessage("", _("White to play"));
10529     }
10530     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10531     SendBoard(&first, forwardMostMove);
10532     if (appData.debugMode) {
10533 int i, j;
10534   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10535   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10536         fprintf(debugFP, "Load Position\n");
10537     }
10538
10539     if (positionNumber > 1) {
10540         sprintf(line, "%s %d", title, positionNumber);
10541         DisplayTitle(line);
10542     } else {
10543         DisplayTitle(title);
10544     }
10545     gameMode = EditGame;
10546     ModeHighlight();
10547     ResetClocks();
10548     timeRemaining[0][1] = whiteTimeRemaining;
10549     timeRemaining[1][1] = blackTimeRemaining;
10550     DrawPosition(FALSE, boards[currentMove]);
10551    
10552     return TRUE;
10553 }
10554
10555
10556 void
10557 CopyPlayerNameIntoFileName(dest, src)
10558      char **dest, *src;
10559 {
10560     while (*src != NULLCHAR && *src != ',') {
10561         if (*src == ' ') {
10562             *(*dest)++ = '_';
10563             src++;
10564         } else {
10565             *(*dest)++ = *src++;
10566         }
10567     }
10568 }
10569
10570 char *DefaultFileName(ext)
10571      char *ext;
10572 {
10573     static char def[MSG_SIZ];
10574     char *p;
10575
10576     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10577         p = def;
10578         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10579         *p++ = '-';
10580         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10581         *p++ = '.';
10582         strcpy(p, ext);
10583     } else {
10584         def[0] = NULLCHAR;
10585     }
10586     return def;
10587 }
10588
10589 /* Save the current game to the given file */
10590 int
10591 SaveGameToFile(filename, append)
10592      char *filename;
10593      int append;
10594 {
10595     FILE *f;
10596     char buf[MSG_SIZ];
10597
10598     if (strcmp(filename, "-") == 0) {
10599         return SaveGame(stdout, 0, NULL);
10600     } else {
10601         f = fopen(filename, append ? "a" : "w");
10602         if (f == NULL) {
10603             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10604             DisplayError(buf, errno);
10605             return FALSE;
10606         } else {
10607             return SaveGame(f, 0, NULL);
10608         }
10609     }
10610 }
10611
10612 char *
10613 SavePart(str)
10614      char *str;
10615 {
10616     static char buf[MSG_SIZ];
10617     char *p;
10618     
10619     p = strchr(str, ' ');
10620     if (p == NULL) return str;
10621     strncpy(buf, str, p - str);
10622     buf[p - str] = NULLCHAR;
10623     return buf;
10624 }
10625
10626 #define PGN_MAX_LINE 75
10627
10628 #define PGN_SIDE_WHITE  0
10629 #define PGN_SIDE_BLACK  1
10630
10631 /* [AS] */
10632 static int FindFirstMoveOutOfBook( int side )
10633 {
10634     int result = -1;
10635
10636     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10637         int index = backwardMostMove;
10638         int has_book_hit = 0;
10639
10640         if( (index % 2) != side ) {
10641             index++;
10642         }
10643
10644         while( index < forwardMostMove ) {
10645             /* Check to see if engine is in book */
10646             int depth = pvInfoList[index].depth;
10647             int score = pvInfoList[index].score;
10648             int in_book = 0;
10649
10650             if( depth <= 2 ) {
10651                 in_book = 1;
10652             }
10653             else if( score == 0 && depth == 63 ) {
10654                 in_book = 1; /* Zappa */
10655             }
10656             else if( score == 2 && depth == 99 ) {
10657                 in_book = 1; /* Abrok */
10658             }
10659
10660             has_book_hit += in_book;
10661
10662             if( ! in_book ) {
10663                 result = index;
10664
10665                 break;
10666             }
10667
10668             index += 2;
10669         }
10670     }
10671
10672     return result;
10673 }
10674
10675 /* [AS] */
10676 void GetOutOfBookInfo( char * buf )
10677 {
10678     int oob[2];
10679     int i;
10680     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10681
10682     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10683     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10684
10685     *buf = '\0';
10686
10687     if( oob[0] >= 0 || oob[1] >= 0 ) {
10688         for( i=0; i<2; i++ ) {
10689             int idx = oob[i];
10690
10691             if( idx >= 0 ) {
10692                 if( i > 0 && oob[0] >= 0 ) {
10693                     strcat( buf, "   " );
10694                 }
10695
10696                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10697                 sprintf( buf+strlen(buf), "%s%.2f", 
10698                     pvInfoList[idx].score >= 0 ? "+" : "",
10699                     pvInfoList[idx].score / 100.0 );
10700             }
10701         }
10702     }
10703 }
10704
10705 /* Save game in PGN style and close the file */
10706 int
10707 SaveGamePGN(f)
10708      FILE *f;
10709 {
10710     int i, offset, linelen, newblock;
10711     time_t tm;
10712 //    char *movetext;
10713     char numtext[32];
10714     int movelen, numlen, blank;
10715     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10716
10717     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10718     
10719     tm = time((time_t *) NULL);
10720     
10721     PrintPGNTags(f, &gameInfo);
10722     
10723     if (backwardMostMove > 0 || startedFromSetupPosition) {
10724         char *fen = PositionToFEN(backwardMostMove, NULL);
10725         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10726         fprintf(f, "\n{--------------\n");
10727         PrintPosition(f, backwardMostMove);
10728         fprintf(f, "--------------}\n");
10729         free(fen);
10730     }
10731     else {
10732         /* [AS] Out of book annotation */
10733         if( appData.saveOutOfBookInfo ) {
10734             char buf[64];
10735
10736             GetOutOfBookInfo( buf );
10737
10738             if( buf[0] != '\0' ) {
10739                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10740             }
10741         }
10742
10743         fprintf(f, "\n");
10744     }
10745
10746     i = backwardMostMove;
10747     linelen = 0;
10748     newblock = TRUE;
10749
10750     while (i < forwardMostMove) {
10751         /* Print comments preceding this move */
10752         if (commentList[i] != NULL) {
10753             if (linelen > 0) fprintf(f, "\n");
10754             fprintf(f, "%s", commentList[i]);
10755             linelen = 0;
10756             newblock = TRUE;
10757         }
10758
10759         /* Format move number */
10760         if ((i % 2) == 0) {
10761             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10762         } else {
10763             if (newblock) {
10764                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10765             } else {
10766                 numtext[0] = NULLCHAR;
10767             }
10768         }
10769         numlen = strlen(numtext);
10770         newblock = FALSE;
10771
10772         /* Print move number */
10773         blank = linelen > 0 && numlen > 0;
10774         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10775             fprintf(f, "\n");
10776             linelen = 0;
10777             blank = 0;
10778         }
10779         if (blank) {
10780             fprintf(f, " ");
10781             linelen++;
10782         }
10783         fprintf(f, "%s", numtext);
10784         linelen += numlen;
10785
10786         /* Get move */
10787         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10788         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10789
10790         /* Print move */
10791         blank = linelen > 0 && movelen > 0;
10792         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10793             fprintf(f, "\n");
10794             linelen = 0;
10795             blank = 0;
10796         }
10797         if (blank) {
10798             fprintf(f, " ");
10799             linelen++;
10800         }
10801         fprintf(f, "%s", move_buffer);
10802         linelen += movelen;
10803
10804         /* [AS] Add PV info if present */
10805         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10806             /* [HGM] add time */
10807             char buf[MSG_SIZ]; int seconds;
10808
10809             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10810
10811             if( seconds <= 0) buf[0] = 0; else
10812             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10813                 seconds = (seconds + 4)/10; // round to full seconds
10814                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10815                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10816             }
10817
10818             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10819                 pvInfoList[i].score >= 0 ? "+" : "",
10820                 pvInfoList[i].score / 100.0,
10821                 pvInfoList[i].depth,
10822                 buf );
10823
10824             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10825
10826             /* Print score/depth */
10827             blank = linelen > 0 && movelen > 0;
10828             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10829                 fprintf(f, "\n");
10830                 linelen = 0;
10831                 blank = 0;
10832             }
10833             if (blank) {
10834                 fprintf(f, " ");
10835                 linelen++;
10836             }
10837             fprintf(f, "%s", move_buffer);
10838             linelen += movelen;
10839         }
10840
10841         i++;
10842     }
10843     
10844     /* Start a new line */
10845     if (linelen > 0) fprintf(f, "\n");
10846
10847     /* Print comments after last move */
10848     if (commentList[i] != NULL) {
10849         fprintf(f, "%s\n", commentList[i]);
10850     }
10851
10852     /* Print result */
10853     if (gameInfo.resultDetails != NULL &&
10854         gameInfo.resultDetails[0] != NULLCHAR) {
10855         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10856                 PGNResult(gameInfo.result));
10857     } else {
10858         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10859     }
10860
10861     fclose(f);
10862     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10863     return TRUE;
10864 }
10865
10866 /* Save game in old style and close the file */
10867 int
10868 SaveGameOldStyle(f)
10869      FILE *f;
10870 {
10871     int i, offset;
10872     time_t tm;
10873     
10874     tm = time((time_t *) NULL);
10875     
10876     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10877     PrintOpponents(f);
10878     
10879     if (backwardMostMove > 0 || startedFromSetupPosition) {
10880         fprintf(f, "\n[--------------\n");
10881         PrintPosition(f, backwardMostMove);
10882         fprintf(f, "--------------]\n");
10883     } else {
10884         fprintf(f, "\n");
10885     }
10886
10887     i = backwardMostMove;
10888     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10889
10890     while (i < forwardMostMove) {
10891         if (commentList[i] != NULL) {
10892             fprintf(f, "[%s]\n", commentList[i]);
10893         }
10894
10895         if ((i % 2) == 1) {
10896             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10897             i++;
10898         } else {
10899             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10900             i++;
10901             if (commentList[i] != NULL) {
10902                 fprintf(f, "\n");
10903                 continue;
10904             }
10905             if (i >= forwardMostMove) {
10906                 fprintf(f, "\n");
10907                 break;
10908             }
10909             fprintf(f, "%s\n", parseList[i]);
10910             i++;
10911         }
10912     }
10913     
10914     if (commentList[i] != NULL) {
10915         fprintf(f, "[%s]\n", commentList[i]);
10916     }
10917
10918     /* This isn't really the old style, but it's close enough */
10919     if (gameInfo.resultDetails != NULL &&
10920         gameInfo.resultDetails[0] != NULLCHAR) {
10921         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10922                 gameInfo.resultDetails);
10923     } else {
10924         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10925     }
10926
10927     fclose(f);
10928     return TRUE;
10929 }
10930
10931 /* Save the current game to open file f and close the file */
10932 int
10933 SaveGame(f, dummy, dummy2)
10934      FILE *f;
10935      int dummy;
10936      char *dummy2;
10937 {
10938     if (gameMode == EditPosition) EditPositionDone(TRUE);
10939     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10940     if (appData.oldSaveStyle)
10941       return SaveGameOldStyle(f);
10942     else
10943       return SaveGamePGN(f);
10944 }
10945
10946 /* Save the current position to the given file */
10947 int
10948 SavePositionToFile(filename)
10949      char *filename;
10950 {
10951     FILE *f;
10952     char buf[MSG_SIZ];
10953
10954     if (strcmp(filename, "-") == 0) {
10955         return SavePosition(stdout, 0, NULL);
10956     } else {
10957         f = fopen(filename, "a");
10958         if (f == NULL) {
10959             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10960             DisplayError(buf, errno);
10961             return FALSE;
10962         } else {
10963             SavePosition(f, 0, NULL);
10964             return TRUE;
10965         }
10966     }
10967 }
10968
10969 /* Save the current position to the given open file and close the file */
10970 int
10971 SavePosition(f, dummy, dummy2)
10972      FILE *f;
10973      int dummy;
10974      char *dummy2;
10975 {
10976     time_t tm;
10977     char *fen;
10978     
10979     if (gameMode == EditPosition) EditPositionDone(TRUE);
10980     if (appData.oldSaveStyle) {
10981         tm = time((time_t *) NULL);
10982     
10983         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10984         PrintOpponents(f);
10985         fprintf(f, "[--------------\n");
10986         PrintPosition(f, currentMove);
10987         fprintf(f, "--------------]\n");
10988     } else {
10989         fen = PositionToFEN(currentMove, NULL);
10990         fprintf(f, "%s\n", fen);
10991         free(fen);
10992     }
10993     fclose(f);
10994     return TRUE;
10995 }
10996
10997 void
10998 ReloadCmailMsgEvent(unregister)
10999      int unregister;
11000 {
11001 #if !WIN32
11002     static char *inFilename = NULL;
11003     static char *outFilename;
11004     int i;
11005     struct stat inbuf, outbuf;
11006     int status;
11007     
11008     /* Any registered moves are unregistered if unregister is set, */
11009     /* i.e. invoked by the signal handler */
11010     if (unregister) {
11011         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11012             cmailMoveRegistered[i] = FALSE;
11013             if (cmailCommentList[i] != NULL) {
11014                 free(cmailCommentList[i]);
11015                 cmailCommentList[i] = NULL;
11016             }
11017         }
11018         nCmailMovesRegistered = 0;
11019     }
11020
11021     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11022         cmailResult[i] = CMAIL_NOT_RESULT;
11023     }
11024     nCmailResults = 0;
11025
11026     if (inFilename == NULL) {
11027         /* Because the filenames are static they only get malloced once  */
11028         /* and they never get freed                                      */
11029         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11030         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11031
11032         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11033         sprintf(outFilename, "%s.out", appData.cmailGameName);
11034     }
11035     
11036     status = stat(outFilename, &outbuf);
11037     if (status < 0) {
11038         cmailMailedMove = FALSE;
11039     } else {
11040         status = stat(inFilename, &inbuf);
11041         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11042     }
11043     
11044     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11045        counts the games, notes how each one terminated, etc.
11046        
11047        It would be nice to remove this kludge and instead gather all
11048        the information while building the game list.  (And to keep it
11049        in the game list nodes instead of having a bunch of fixed-size
11050        parallel arrays.)  Note this will require getting each game's
11051        termination from the PGN tags, as the game list builder does
11052        not process the game moves.  --mann
11053        */
11054     cmailMsgLoaded = TRUE;
11055     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11056     
11057     /* Load first game in the file or popup game menu */
11058     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11059
11060 #endif /* !WIN32 */
11061     return;
11062 }
11063
11064 int
11065 RegisterMove()
11066 {
11067     FILE *f;
11068     char string[MSG_SIZ];
11069
11070     if (   cmailMailedMove
11071         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11072         return TRUE;            /* Allow free viewing  */
11073     }
11074
11075     /* Unregister move to ensure that we don't leave RegisterMove        */
11076     /* with the move registered when the conditions for registering no   */
11077     /* longer hold                                                       */
11078     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11079         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11080         nCmailMovesRegistered --;
11081
11082         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11083           {
11084               free(cmailCommentList[lastLoadGameNumber - 1]);
11085               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11086           }
11087     }
11088
11089     if (cmailOldMove == -1) {
11090         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11091         return FALSE;
11092     }
11093
11094     if (currentMove > cmailOldMove + 1) {
11095         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11096         return FALSE;
11097     }
11098
11099     if (currentMove < cmailOldMove) {
11100         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11101         return FALSE;
11102     }
11103
11104     if (forwardMostMove > currentMove) {
11105         /* Silently truncate extra moves */
11106         TruncateGame();
11107     }
11108
11109     if (   (currentMove == cmailOldMove + 1)
11110         || (   (currentMove == cmailOldMove)
11111             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11112                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11113         if (gameInfo.result != GameUnfinished) {
11114             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11115         }
11116
11117         if (commentList[currentMove] != NULL) {
11118             cmailCommentList[lastLoadGameNumber - 1]
11119               = StrSave(commentList[currentMove]);
11120         }
11121         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11122
11123         if (appData.debugMode)
11124           fprintf(debugFP, "Saving %s for game %d\n",
11125                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11126
11127         sprintf(string,
11128                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11129         
11130         f = fopen(string, "w");
11131         if (appData.oldSaveStyle) {
11132             SaveGameOldStyle(f); /* also closes the file */
11133             
11134             sprintf(string, "%s.pos.out", appData.cmailGameName);
11135             f = fopen(string, "w");
11136             SavePosition(f, 0, NULL); /* also closes the file */
11137         } else {
11138             fprintf(f, "{--------------\n");
11139             PrintPosition(f, currentMove);
11140             fprintf(f, "--------------}\n\n");
11141             
11142             SaveGame(f, 0, NULL); /* also closes the file*/
11143         }
11144         
11145         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11146         nCmailMovesRegistered ++;
11147     } else if (nCmailGames == 1) {
11148         DisplayError(_("You have not made a move yet"), 0);
11149         return FALSE;
11150     }
11151
11152     return TRUE;
11153 }
11154
11155 void
11156 MailMoveEvent()
11157 {
11158 #if !WIN32
11159     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11160     FILE *commandOutput;
11161     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11162     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11163     int nBuffers;
11164     int i;
11165     int archived;
11166     char *arcDir;
11167
11168     if (! cmailMsgLoaded) {
11169         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11170         return;
11171     }
11172
11173     if (nCmailGames == nCmailResults) {
11174         DisplayError(_("No unfinished games"), 0);
11175         return;
11176     }
11177
11178 #if CMAIL_PROHIBIT_REMAIL
11179     if (cmailMailedMove) {
11180         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);
11181         DisplayError(msg, 0);
11182         return;
11183     }
11184 #endif
11185
11186     if (! (cmailMailedMove || RegisterMove())) return;
11187     
11188     if (   cmailMailedMove
11189         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11190         sprintf(string, partCommandString,
11191                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11192         commandOutput = popen(string, "r");
11193
11194         if (commandOutput == NULL) {
11195             DisplayError(_("Failed to invoke cmail"), 0);
11196         } else {
11197             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11198                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11199             }
11200             if (nBuffers > 1) {
11201                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11202                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11203                 nBytes = MSG_SIZ - 1;
11204             } else {
11205                 (void) memcpy(msg, buffer, nBytes);
11206             }
11207             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11208
11209             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11210                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11211
11212                 archived = TRUE;
11213                 for (i = 0; i < nCmailGames; i ++) {
11214                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11215                         archived = FALSE;
11216                     }
11217                 }
11218                 if (   archived
11219                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11220                         != NULL)) {
11221                     sprintf(buffer, "%s/%s.%s.archive",
11222                             arcDir,
11223                             appData.cmailGameName,
11224                             gameInfo.date);
11225                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11226                     cmailMsgLoaded = FALSE;
11227                 }
11228             }
11229
11230             DisplayInformation(msg);
11231             pclose(commandOutput);
11232         }
11233     } else {
11234         if ((*cmailMsg) != '\0') {
11235             DisplayInformation(cmailMsg);
11236         }
11237     }
11238
11239     return;
11240 #endif /* !WIN32 */
11241 }
11242
11243 char *
11244 CmailMsg()
11245 {
11246 #if WIN32
11247     return NULL;
11248 #else
11249     int  prependComma = 0;
11250     char number[5];
11251     char string[MSG_SIZ];       /* Space for game-list */
11252     int  i;
11253     
11254     if (!cmailMsgLoaded) return "";
11255
11256     if (cmailMailedMove) {
11257         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11258     } else {
11259         /* Create a list of games left */
11260         sprintf(string, "[");
11261         for (i = 0; i < nCmailGames; i ++) {
11262             if (! (   cmailMoveRegistered[i]
11263                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11264                 if (prependComma) {
11265                     sprintf(number, ",%d", i + 1);
11266                 } else {
11267                     sprintf(number, "%d", i + 1);
11268                     prependComma = 1;
11269                 }
11270                 
11271                 strcat(string, number);
11272             }
11273         }
11274         strcat(string, "]");
11275
11276         if (nCmailMovesRegistered + nCmailResults == 0) {
11277             switch (nCmailGames) {
11278               case 1:
11279                 sprintf(cmailMsg,
11280                         _("Still need to make move for game\n"));
11281                 break;
11282                 
11283               case 2:
11284                 sprintf(cmailMsg,
11285                         _("Still need to make moves for both games\n"));
11286                 break;
11287                 
11288               default:
11289                 sprintf(cmailMsg,
11290                         _("Still need to make moves for all %d games\n"),
11291                         nCmailGames);
11292                 break;
11293             }
11294         } else {
11295             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11296               case 1:
11297                 sprintf(cmailMsg,
11298                         _("Still need to make a move for game %s\n"),
11299                         string);
11300                 break;
11301                 
11302               case 0:
11303                 if (nCmailResults == nCmailGames) {
11304                     sprintf(cmailMsg, _("No unfinished games\n"));
11305                 } else {
11306                     sprintf(cmailMsg, _("Ready to send mail\n"));
11307                 }
11308                 break;
11309                 
11310               default:
11311                 sprintf(cmailMsg,
11312                         _("Still need to make moves for games %s\n"),
11313                         string);
11314             }
11315         }
11316     }
11317     return cmailMsg;
11318 #endif /* WIN32 */
11319 }
11320
11321 void
11322 ResetGameEvent()
11323 {
11324     if (gameMode == Training)
11325       SetTrainingModeOff();
11326
11327     Reset(TRUE, TRUE);
11328     cmailMsgLoaded = FALSE;
11329     if (appData.icsActive) {
11330       SendToICS(ics_prefix);
11331       SendToICS("refresh\n");
11332     }
11333 }
11334
11335 void
11336 ExitEvent(status)
11337      int status;
11338 {
11339     exiting++;
11340     if (exiting > 2) {
11341       /* Give up on clean exit */
11342       exit(status);
11343     }
11344     if (exiting > 1) {
11345       /* Keep trying for clean exit */
11346       return;
11347     }
11348
11349     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11350
11351     if (telnetISR != NULL) {
11352       RemoveInputSource(telnetISR);
11353     }
11354     if (icsPR != NoProc) {
11355       DestroyChildProcess(icsPR, TRUE);
11356     }
11357
11358     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11359     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11360
11361     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11362     /* make sure this other one finishes before killing it!                  */
11363     if(endingGame) { int count = 0;
11364         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11365         while(endingGame && count++ < 10) DoSleep(1);
11366         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11367     }
11368
11369     /* Kill off chess programs */
11370     if (first.pr != NoProc) {
11371         ExitAnalyzeMode();
11372         
11373         DoSleep( appData.delayBeforeQuit );
11374         SendToProgram("quit\n", &first);
11375         DoSleep( appData.delayAfterQuit );
11376         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11377     }
11378     if (second.pr != NoProc) {
11379         DoSleep( appData.delayBeforeQuit );
11380         SendToProgram("quit\n", &second);
11381         DoSleep( appData.delayAfterQuit );
11382         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11383     }
11384     if (first.isr != NULL) {
11385         RemoveInputSource(first.isr);
11386     }
11387     if (second.isr != NULL) {
11388         RemoveInputSource(second.isr);
11389     }
11390
11391     ShutDownFrontEnd();
11392     exit(status);
11393 }
11394
11395 void
11396 PauseEvent()
11397 {
11398     if (appData.debugMode)
11399         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11400     if (pausing) {
11401         pausing = FALSE;
11402         ModeHighlight();
11403         if (gameMode == MachinePlaysWhite ||
11404             gameMode == MachinePlaysBlack) {
11405             StartClocks();
11406         } else {
11407             DisplayBothClocks();
11408         }
11409         if (gameMode == PlayFromGameFile) {
11410             if (appData.timeDelay >= 0) 
11411                 AutoPlayGameLoop();
11412         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11413             Reset(FALSE, TRUE);
11414             SendToICS(ics_prefix);
11415             SendToICS("refresh\n");
11416         } else if (currentMove < forwardMostMove) {
11417             ForwardInner(forwardMostMove);
11418         }
11419         pauseExamInvalid = FALSE;
11420     } else {
11421         switch (gameMode) {
11422           default:
11423             return;
11424           case IcsExamining:
11425             pauseExamForwardMostMove = forwardMostMove;
11426             pauseExamInvalid = FALSE;
11427             /* fall through */
11428           case IcsObserving:
11429           case IcsPlayingWhite:
11430           case IcsPlayingBlack:
11431             pausing = TRUE;
11432             ModeHighlight();
11433             return;
11434           case PlayFromGameFile:
11435             (void) StopLoadGameTimer();
11436             pausing = TRUE;
11437             ModeHighlight();
11438             break;
11439           case BeginningOfGame:
11440             if (appData.icsActive) return;
11441             /* else fall through */
11442           case MachinePlaysWhite:
11443           case MachinePlaysBlack:
11444           case TwoMachinesPlay:
11445             if (forwardMostMove == 0)
11446               return;           /* don't pause if no one has moved */
11447             if ((gameMode == MachinePlaysWhite &&
11448                  !WhiteOnMove(forwardMostMove)) ||
11449                 (gameMode == MachinePlaysBlack &&
11450                  WhiteOnMove(forwardMostMove))) {
11451                 StopClocks();
11452             }
11453             pausing = TRUE;
11454             ModeHighlight();
11455             break;
11456         }
11457     }
11458 }
11459
11460 void
11461 EditCommentEvent()
11462 {
11463     char title[MSG_SIZ];
11464
11465     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11466         strcpy(title, _("Edit comment"));
11467     } else {
11468         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11469                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11470                 parseList[currentMove - 1]);
11471     }
11472
11473     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11474 }
11475
11476
11477 void
11478 EditTagsEvent()
11479 {
11480     char *tags = PGNTags(&gameInfo);
11481     EditTagsPopUp(tags);
11482     free(tags);
11483 }
11484
11485 void
11486 AnalyzeModeEvent()
11487 {
11488     if (appData.noChessProgram || gameMode == AnalyzeMode)
11489       return;
11490
11491     if (gameMode != AnalyzeFile) {
11492         if (!appData.icsEngineAnalyze) {
11493                EditGameEvent();
11494                if (gameMode != EditGame) return;
11495         }
11496         ResurrectChessProgram();
11497         SendToProgram("analyze\n", &first);
11498         first.analyzing = TRUE;
11499         /*first.maybeThinking = TRUE;*/
11500         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11501         EngineOutputPopUp();
11502     }
11503     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11504     pausing = FALSE;
11505     ModeHighlight();
11506     SetGameInfo();
11507
11508     StartAnalysisClock();
11509     GetTimeMark(&lastNodeCountTime);
11510     lastNodeCount = 0;
11511 }
11512
11513 void
11514 AnalyzeFileEvent()
11515 {
11516     if (appData.noChessProgram || gameMode == AnalyzeFile)
11517       return;
11518
11519     if (gameMode != AnalyzeMode) {
11520         EditGameEvent();
11521         if (gameMode != EditGame) return;
11522         ResurrectChessProgram();
11523         SendToProgram("analyze\n", &first);
11524         first.analyzing = TRUE;
11525         /*first.maybeThinking = TRUE;*/
11526         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11527         EngineOutputPopUp();
11528     }
11529     gameMode = AnalyzeFile;
11530     pausing = FALSE;
11531     ModeHighlight();
11532     SetGameInfo();
11533
11534     StartAnalysisClock();
11535     GetTimeMark(&lastNodeCountTime);
11536     lastNodeCount = 0;
11537 }
11538
11539 void
11540 MachineWhiteEvent()
11541 {
11542     char buf[MSG_SIZ];
11543     char *bookHit = NULL;
11544
11545     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11546       return;
11547
11548
11549     if (gameMode == PlayFromGameFile || 
11550         gameMode == TwoMachinesPlay  || 
11551         gameMode == Training         || 
11552         gameMode == AnalyzeMode      || 
11553         gameMode == EndOfGame)
11554         EditGameEvent();
11555
11556     if (gameMode == EditPosition) 
11557         EditPositionDone(TRUE);
11558
11559     if (!WhiteOnMove(currentMove)) {
11560         DisplayError(_("It is not White's turn"), 0);
11561         return;
11562     }
11563   
11564     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11565       ExitAnalyzeMode();
11566
11567     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11568         gameMode == AnalyzeFile)
11569         TruncateGame();
11570
11571     ResurrectChessProgram();    /* in case it isn't running */
11572     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11573         gameMode = MachinePlaysWhite;
11574         ResetClocks();
11575     } else
11576     gameMode = MachinePlaysWhite;
11577     pausing = FALSE;
11578     ModeHighlight();
11579     SetGameInfo();
11580     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11581     DisplayTitle(buf);
11582     if (first.sendName) {
11583       sprintf(buf, "name %s\n", gameInfo.black);
11584       SendToProgram(buf, &first);
11585     }
11586     if (first.sendTime) {
11587       if (first.useColors) {
11588         SendToProgram("black\n", &first); /*gnu kludge*/
11589       }
11590       SendTimeRemaining(&first, TRUE);
11591     }
11592     if (first.useColors) {
11593       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11594     }
11595     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11596     SetMachineThinkingEnables();
11597     first.maybeThinking = TRUE;
11598     StartClocks();
11599     firstMove = FALSE;
11600
11601     if (appData.autoFlipView && !flipView) {
11602       flipView = !flipView;
11603       DrawPosition(FALSE, NULL);
11604       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11605     }
11606
11607     if(bookHit) { // [HGM] book: simulate book reply
11608         static char bookMove[MSG_SIZ]; // a bit generous?
11609
11610         programStats.nodes = programStats.depth = programStats.time = 
11611         programStats.score = programStats.got_only_move = 0;
11612         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11613
11614         strcpy(bookMove, "move ");
11615         strcat(bookMove, bookHit);
11616         HandleMachineMove(bookMove, &first);
11617     }
11618 }
11619
11620 void
11621 MachineBlackEvent()
11622 {
11623     char buf[MSG_SIZ];
11624    char *bookHit = NULL;
11625
11626     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11627         return;
11628
11629
11630     if (gameMode == PlayFromGameFile || 
11631         gameMode == TwoMachinesPlay  || 
11632         gameMode == Training         || 
11633         gameMode == AnalyzeMode      || 
11634         gameMode == EndOfGame)
11635         EditGameEvent();
11636
11637     if (gameMode == EditPosition) 
11638         EditPositionDone(TRUE);
11639
11640     if (WhiteOnMove(currentMove)) {
11641         DisplayError(_("It is not Black's turn"), 0);
11642         return;
11643     }
11644     
11645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11646       ExitAnalyzeMode();
11647
11648     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11649         gameMode == AnalyzeFile)
11650         TruncateGame();
11651
11652     ResurrectChessProgram();    /* in case it isn't running */
11653     gameMode = MachinePlaysBlack;
11654     pausing = FALSE;
11655     ModeHighlight();
11656     SetGameInfo();
11657     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11658     DisplayTitle(buf);
11659     if (first.sendName) {
11660       sprintf(buf, "name %s\n", gameInfo.white);
11661       SendToProgram(buf, &first);
11662     }
11663     if (first.sendTime) {
11664       if (first.useColors) {
11665         SendToProgram("white\n", &first); /*gnu kludge*/
11666       }
11667       SendTimeRemaining(&first, FALSE);
11668     }
11669     if (first.useColors) {
11670       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11671     }
11672     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11673     SetMachineThinkingEnables();
11674     first.maybeThinking = TRUE;
11675     StartClocks();
11676
11677     if (appData.autoFlipView && flipView) {
11678       flipView = !flipView;
11679       DrawPosition(FALSE, NULL);
11680       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11681     }
11682     if(bookHit) { // [HGM] book: simulate book reply
11683         static char bookMove[MSG_SIZ]; // a bit generous?
11684
11685         programStats.nodes = programStats.depth = programStats.time = 
11686         programStats.score = programStats.got_only_move = 0;
11687         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11688
11689         strcpy(bookMove, "move ");
11690         strcat(bookMove, bookHit);
11691         HandleMachineMove(bookMove, &first);
11692     }
11693 }
11694
11695
11696 void
11697 DisplayTwoMachinesTitle()
11698 {
11699     char buf[MSG_SIZ];
11700     if (appData.matchGames > 0) {
11701         if (first.twoMachinesColor[0] == 'w') {
11702             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11703                     gameInfo.white, gameInfo.black,
11704                     first.matchWins, second.matchWins,
11705                     matchGame - 1 - (first.matchWins + second.matchWins));
11706         } else {
11707             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11708                     gameInfo.white, gameInfo.black,
11709                     second.matchWins, first.matchWins,
11710                     matchGame - 1 - (first.matchWins + second.matchWins));
11711         }
11712     } else {
11713         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11714     }
11715     DisplayTitle(buf);
11716 }
11717
11718 void
11719 TwoMachinesEvent P((void))
11720 {
11721     int i;
11722     char buf[MSG_SIZ];
11723     ChessProgramState *onmove;
11724     char *bookHit = NULL;
11725     
11726     if (appData.noChessProgram) return;
11727
11728     switch (gameMode) {
11729       case TwoMachinesPlay:
11730         return;
11731       case MachinePlaysWhite:
11732       case MachinePlaysBlack:
11733         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11734             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11735             return;
11736         }
11737         /* fall through */
11738       case BeginningOfGame:
11739       case PlayFromGameFile:
11740       case EndOfGame:
11741         EditGameEvent();
11742         if (gameMode != EditGame) return;
11743         break;
11744       case EditPosition:
11745         EditPositionDone(TRUE);
11746         break;
11747       case AnalyzeMode:
11748       case AnalyzeFile:
11749         ExitAnalyzeMode();
11750         break;
11751       case EditGame:
11752       default:
11753         break;
11754     }
11755
11756 //    forwardMostMove = currentMove;
11757     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11758     ResurrectChessProgram();    /* in case first program isn't running */
11759
11760     if (second.pr == NULL) {
11761         StartChessProgram(&second);
11762         if (second.protocolVersion == 1) {
11763           TwoMachinesEventIfReady();
11764         } else {
11765           /* kludge: allow timeout for initial "feature" command */
11766           FreezeUI();
11767           DisplayMessage("", _("Starting second chess program"));
11768           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11769         }
11770         return;
11771     }
11772     DisplayMessage("", "");
11773     InitChessProgram(&second, FALSE);
11774     SendToProgram("force\n", &second);
11775     if (startedFromSetupPosition) {
11776         SendBoard(&second, backwardMostMove);
11777     if (appData.debugMode) {
11778         fprintf(debugFP, "Two Machines\n");
11779     }
11780     }
11781     for (i = backwardMostMove; i < forwardMostMove; i++) {
11782         SendMoveToProgram(i, &second);
11783     }
11784
11785     gameMode = TwoMachinesPlay;
11786     pausing = FALSE;
11787     ModeHighlight();
11788     SetGameInfo();
11789     DisplayTwoMachinesTitle();
11790     firstMove = TRUE;
11791     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11792         onmove = &first;
11793     } else {
11794         onmove = &second;
11795     }
11796
11797     SendToProgram(first.computerString, &first);
11798     if (first.sendName) {
11799       sprintf(buf, "name %s\n", second.tidy);
11800       SendToProgram(buf, &first);
11801     }
11802     SendToProgram(second.computerString, &second);
11803     if (second.sendName) {
11804       sprintf(buf, "name %s\n", first.tidy);
11805       SendToProgram(buf, &second);
11806     }
11807
11808     ResetClocks();
11809     if (!first.sendTime || !second.sendTime) {
11810         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11811         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11812     }
11813     if (onmove->sendTime) {
11814       if (onmove->useColors) {
11815         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11816       }
11817       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11818     }
11819     if (onmove->useColors) {
11820       SendToProgram(onmove->twoMachinesColor, onmove);
11821     }
11822     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11823 //    SendToProgram("go\n", onmove);
11824     onmove->maybeThinking = TRUE;
11825     SetMachineThinkingEnables();
11826
11827     StartClocks();
11828
11829     if(bookHit) { // [HGM] book: simulate book reply
11830         static char bookMove[MSG_SIZ]; // a bit generous?
11831
11832         programStats.nodes = programStats.depth = programStats.time = 
11833         programStats.score = programStats.got_only_move = 0;
11834         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11835
11836         strcpy(bookMove, "move ");
11837         strcat(bookMove, bookHit);
11838         savedMessage = bookMove; // args for deferred call
11839         savedState = onmove;
11840         ScheduleDelayedEvent(DeferredBookMove, 1);
11841     }
11842 }
11843
11844 void
11845 TrainingEvent()
11846 {
11847     if (gameMode == Training) {
11848       SetTrainingModeOff();
11849       gameMode = PlayFromGameFile;
11850       DisplayMessage("", _("Training mode off"));
11851     } else {
11852       gameMode = Training;
11853       animateTraining = appData.animate;
11854
11855       /* make sure we are not already at the end of the game */
11856       if (currentMove < forwardMostMove) {
11857         SetTrainingModeOn();
11858         DisplayMessage("", _("Training mode on"));
11859       } else {
11860         gameMode = PlayFromGameFile;
11861         DisplayError(_("Already at end of game"), 0);
11862       }
11863     }
11864     ModeHighlight();
11865 }
11866
11867 void
11868 IcsClientEvent()
11869 {
11870     if (!appData.icsActive) return;
11871     switch (gameMode) {
11872       case IcsPlayingWhite:
11873       case IcsPlayingBlack:
11874       case IcsObserving:
11875       case IcsIdle:
11876       case BeginningOfGame:
11877       case IcsExamining:
11878         return;
11879
11880       case EditGame:
11881         break;
11882
11883       case EditPosition:
11884         EditPositionDone(TRUE);
11885         break;
11886
11887       case AnalyzeMode:
11888       case AnalyzeFile:
11889         ExitAnalyzeMode();
11890         break;
11891         
11892       default:
11893         EditGameEvent();
11894         break;
11895     }
11896
11897     gameMode = IcsIdle;
11898     ModeHighlight();
11899     return;
11900 }
11901
11902
11903 void
11904 EditGameEvent()
11905 {
11906     int i;
11907
11908     switch (gameMode) {
11909       case Training:
11910         SetTrainingModeOff();
11911         break;
11912       case MachinePlaysWhite:
11913       case MachinePlaysBlack:
11914       case BeginningOfGame:
11915         SendToProgram("force\n", &first);
11916         SetUserThinkingEnables();
11917         break;
11918       case PlayFromGameFile:
11919         (void) StopLoadGameTimer();
11920         if (gameFileFP != NULL) {
11921             gameFileFP = NULL;
11922         }
11923         break;
11924       case EditPosition:
11925         EditPositionDone(TRUE);
11926         break;
11927       case AnalyzeMode:
11928       case AnalyzeFile:
11929         ExitAnalyzeMode();
11930         SendToProgram("force\n", &first);
11931         break;
11932       case TwoMachinesPlay:
11933         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11934         ResurrectChessProgram();
11935         SetUserThinkingEnables();
11936         break;
11937       case EndOfGame:
11938         ResurrectChessProgram();
11939         break;
11940       case IcsPlayingBlack:
11941       case IcsPlayingWhite:
11942         DisplayError(_("Warning: You are still playing a game"), 0);
11943         break;
11944       case IcsObserving:
11945         DisplayError(_("Warning: You are still observing a game"), 0);
11946         break;
11947       case IcsExamining:
11948         DisplayError(_("Warning: You are still examining a game"), 0);
11949         break;
11950       case IcsIdle:
11951         break;
11952       case EditGame:
11953       default:
11954         return;
11955     }
11956     
11957     pausing = FALSE;
11958     StopClocks();
11959     first.offeredDraw = second.offeredDraw = 0;
11960
11961     if (gameMode == PlayFromGameFile) {
11962         whiteTimeRemaining = timeRemaining[0][currentMove];
11963         blackTimeRemaining = timeRemaining[1][currentMove];
11964         DisplayTitle("");
11965     }
11966
11967     if (gameMode == MachinePlaysWhite ||
11968         gameMode == MachinePlaysBlack ||
11969         gameMode == TwoMachinesPlay ||
11970         gameMode == EndOfGame) {
11971         i = forwardMostMove;
11972         while (i > currentMove) {
11973             SendToProgram("undo\n", &first);
11974             i--;
11975         }
11976         whiteTimeRemaining = timeRemaining[0][currentMove];
11977         blackTimeRemaining = timeRemaining[1][currentMove];
11978         DisplayBothClocks();
11979         if (whiteFlag || blackFlag) {
11980             whiteFlag = blackFlag = 0;
11981         }
11982         DisplayTitle("");
11983     }           
11984     
11985     gameMode = EditGame;
11986     ModeHighlight();
11987     SetGameInfo();
11988 }
11989
11990
11991 void
11992 EditPositionEvent()
11993 {
11994     if (gameMode == EditPosition) {
11995         EditGameEvent();
11996         return;
11997     }
11998     
11999     EditGameEvent();
12000     if (gameMode != EditGame) return;
12001     
12002     gameMode = EditPosition;
12003     ModeHighlight();
12004     SetGameInfo();
12005     if (currentMove > 0)
12006       CopyBoard(boards[0], boards[currentMove]);
12007     
12008     blackPlaysFirst = !WhiteOnMove(currentMove);
12009     ResetClocks();
12010     currentMove = forwardMostMove = backwardMostMove = 0;
12011     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12012     DisplayMove(-1);
12013 }
12014
12015 void
12016 ExitAnalyzeMode()
12017 {
12018     /* [DM] icsEngineAnalyze - possible call from other functions */
12019     if (appData.icsEngineAnalyze) {
12020         appData.icsEngineAnalyze = FALSE;
12021
12022         DisplayMessage("",_("Close ICS engine analyze..."));
12023     }
12024     if (first.analysisSupport && first.analyzing) {
12025       SendToProgram("exit\n", &first);
12026       first.analyzing = FALSE;
12027     }
12028     thinkOutput[0] = NULLCHAR;
12029 }
12030
12031 void
12032 EditPositionDone(Boolean fakeRights)
12033 {
12034     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12035
12036     startedFromSetupPosition = TRUE;
12037     InitChessProgram(&first, FALSE);
12038     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12039       boards[0][EP_STATUS] = EP_NONE;
12040       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12041     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12042         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12043         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12044       } else boards[0][CASTLING][2] = NoRights;
12045     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12046         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12047         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12048       } else boards[0][CASTLING][5] = NoRights;
12049     }
12050     SendToProgram("force\n", &first);
12051     if (blackPlaysFirst) {
12052         strcpy(moveList[0], "");
12053         strcpy(parseList[0], "");
12054         currentMove = forwardMostMove = backwardMostMove = 1;
12055         CopyBoard(boards[1], boards[0]);
12056     } else {
12057         currentMove = forwardMostMove = backwardMostMove = 0;
12058     }
12059     SendBoard(&first, forwardMostMove);
12060     if (appData.debugMode) {
12061         fprintf(debugFP, "EditPosDone\n");
12062     }
12063     DisplayTitle("");
12064     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12065     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12066     gameMode = EditGame;
12067     ModeHighlight();
12068     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12069     ClearHighlights(); /* [AS] */
12070 }
12071
12072 /* Pause for `ms' milliseconds */
12073 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12074 void
12075 TimeDelay(ms)
12076      long ms;
12077 {
12078     TimeMark m1, m2;
12079
12080     GetTimeMark(&m1);
12081     do {
12082         GetTimeMark(&m2);
12083     } while (SubtractTimeMarks(&m2, &m1) < ms);
12084 }
12085
12086 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12087 void
12088 SendMultiLineToICS(buf)
12089      char *buf;
12090 {
12091     char temp[MSG_SIZ+1], *p;
12092     int len;
12093
12094     len = strlen(buf);
12095     if (len > MSG_SIZ)
12096       len = MSG_SIZ;
12097   
12098     strncpy(temp, buf, len);
12099     temp[len] = 0;
12100
12101     p = temp;
12102     while (*p) {
12103         if (*p == '\n' || *p == '\r')
12104           *p = ' ';
12105         ++p;
12106     }
12107
12108     strcat(temp, "\n");
12109     SendToICS(temp);
12110     SendToPlayer(temp, strlen(temp));
12111 }
12112
12113 void
12114 SetWhiteToPlayEvent()
12115 {
12116     if (gameMode == EditPosition) {
12117         blackPlaysFirst = FALSE;
12118         DisplayBothClocks();    /* works because currentMove is 0 */
12119     } else if (gameMode == IcsExamining) {
12120         SendToICS(ics_prefix);
12121         SendToICS("tomove white\n");
12122     }
12123 }
12124
12125 void
12126 SetBlackToPlayEvent()
12127 {
12128     if (gameMode == EditPosition) {
12129         blackPlaysFirst = TRUE;
12130         currentMove = 1;        /* kludge */
12131         DisplayBothClocks();
12132         currentMove = 0;
12133     } else if (gameMode == IcsExamining) {
12134         SendToICS(ics_prefix);
12135         SendToICS("tomove black\n");
12136     }
12137 }
12138
12139 void
12140 EditPositionMenuEvent(selection, x, y)
12141      ChessSquare selection;
12142      int x, y;
12143 {
12144     char buf[MSG_SIZ];
12145     ChessSquare piece = boards[0][y][x];
12146
12147     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12148
12149     switch (selection) {
12150       case ClearBoard:
12151         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12152             SendToICS(ics_prefix);
12153             SendToICS("bsetup clear\n");
12154         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12155             SendToICS(ics_prefix);
12156             SendToICS("clearboard\n");
12157         } else {
12158             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12159                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12160                 for (y = 0; y < BOARD_HEIGHT; y++) {
12161                     if (gameMode == IcsExamining) {
12162                         if (boards[currentMove][y][x] != EmptySquare) {
12163                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12164                                     AAA + x, ONE + y);
12165                             SendToICS(buf);
12166                         }
12167                     } else {
12168                         boards[0][y][x] = p;
12169                     }
12170                 }
12171             }
12172         }
12173         if (gameMode == EditPosition) {
12174             DrawPosition(FALSE, boards[0]);
12175         }
12176         break;
12177
12178       case WhitePlay:
12179         SetWhiteToPlayEvent();
12180         break;
12181
12182       case BlackPlay:
12183         SetBlackToPlayEvent();
12184         break;
12185
12186       case EmptySquare:
12187         if (gameMode == IcsExamining) {
12188             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12189             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12190             SendToICS(buf);
12191         } else {
12192             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12193                 if(x == BOARD_LEFT-2) {
12194                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12195                     boards[0][y][1] = 0;
12196                 } else
12197                 if(x == BOARD_RGHT+1) {
12198                     if(y >= gameInfo.holdingsSize) break;
12199                     boards[0][y][BOARD_WIDTH-2] = 0;
12200                 } else break;
12201             }
12202             boards[0][y][x] = EmptySquare;
12203             DrawPosition(FALSE, boards[0]);
12204         }
12205         break;
12206
12207       case PromotePiece:
12208         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12209            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12210             selection = (ChessSquare) (PROMOTED piece);
12211         } else if(piece == EmptySquare) selection = WhiteSilver;
12212         else selection = (ChessSquare)((int)piece - 1);
12213         goto defaultlabel;
12214
12215       case DemotePiece:
12216         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12217            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12218             selection = (ChessSquare) (DEMOTED piece);
12219         } else if(piece == EmptySquare) selection = BlackSilver;
12220         else selection = (ChessSquare)((int)piece + 1);       
12221         goto defaultlabel;
12222
12223       case WhiteQueen:
12224       case BlackQueen:
12225         if(gameInfo.variant == VariantShatranj ||
12226            gameInfo.variant == VariantXiangqi  ||
12227            gameInfo.variant == VariantCourier  ||
12228            gameInfo.variant == VariantMakruk     )
12229             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12230         goto defaultlabel;
12231
12232       case WhiteKing:
12233       case BlackKing:
12234         if(gameInfo.variant == VariantXiangqi)
12235             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12236         if(gameInfo.variant == VariantKnightmate)
12237             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12238       default:
12239         defaultlabel:
12240         if (gameMode == IcsExamining) {
12241             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12242             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12243                     PieceToChar(selection), AAA + x, ONE + y);
12244             SendToICS(buf);
12245         } else {
12246             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12247                 int n;
12248                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12249                     n = PieceToNumber(selection - BlackPawn);
12250                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12251                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12252                     boards[0][BOARD_HEIGHT-1-n][1]++;
12253                 } else
12254                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12255                     n = PieceToNumber(selection);
12256                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12257                     boards[0][n][BOARD_WIDTH-1] = selection;
12258                     boards[0][n][BOARD_WIDTH-2]++;
12259                 }
12260             } else
12261             boards[0][y][x] = selection;
12262             DrawPosition(TRUE, boards[0]);
12263         }
12264         break;
12265     }
12266 }
12267
12268
12269 void
12270 DropMenuEvent(selection, x, y)
12271      ChessSquare selection;
12272      int x, y;
12273 {
12274     ChessMove moveType;
12275
12276     switch (gameMode) {
12277       case IcsPlayingWhite:
12278       case MachinePlaysBlack:
12279         if (!WhiteOnMove(currentMove)) {
12280             DisplayMoveError(_("It is Black's turn"));
12281             return;
12282         }
12283         moveType = WhiteDrop;
12284         break;
12285       case IcsPlayingBlack:
12286       case MachinePlaysWhite:
12287         if (WhiteOnMove(currentMove)) {
12288             DisplayMoveError(_("It is White's turn"));
12289             return;
12290         }
12291         moveType = BlackDrop;
12292         break;
12293       case EditGame:
12294         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12295         break;
12296       default:
12297         return;
12298     }
12299
12300     if (moveType == BlackDrop && selection < BlackPawn) {
12301       selection = (ChessSquare) ((int) selection
12302                                  + (int) BlackPawn - (int) WhitePawn);
12303     }
12304     if (boards[currentMove][y][x] != EmptySquare) {
12305         DisplayMoveError(_("That square is occupied"));
12306         return;
12307     }
12308
12309     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12310 }
12311
12312 void
12313 AcceptEvent()
12314 {
12315     /* Accept a pending offer of any kind from opponent */
12316     
12317     if (appData.icsActive) {
12318         SendToICS(ics_prefix);
12319         SendToICS("accept\n");
12320     } else if (cmailMsgLoaded) {
12321         if (currentMove == cmailOldMove &&
12322             commentList[cmailOldMove] != NULL &&
12323             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12324                    "Black offers a draw" : "White offers a draw")) {
12325             TruncateGame();
12326             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12327             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12328         } else {
12329             DisplayError(_("There is no pending offer on this move"), 0);
12330             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12331         }
12332     } else {
12333         /* Not used for offers from chess program */
12334     }
12335 }
12336
12337 void
12338 DeclineEvent()
12339 {
12340     /* Decline a pending offer of any kind from opponent */
12341     
12342     if (appData.icsActive) {
12343         SendToICS(ics_prefix);
12344         SendToICS("decline\n");
12345     } else if (cmailMsgLoaded) {
12346         if (currentMove == cmailOldMove &&
12347             commentList[cmailOldMove] != NULL &&
12348             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12349                    "Black offers a draw" : "White offers a draw")) {
12350 #ifdef NOTDEF
12351             AppendComment(cmailOldMove, "Draw declined", TRUE);
12352             DisplayComment(cmailOldMove - 1, "Draw declined");
12353 #endif /*NOTDEF*/
12354         } else {
12355             DisplayError(_("There is no pending offer on this move"), 0);
12356         }
12357     } else {
12358         /* Not used for offers from chess program */
12359     }
12360 }
12361
12362 void
12363 RematchEvent()
12364 {
12365     /* Issue ICS rematch command */
12366     if (appData.icsActive) {
12367         SendToICS(ics_prefix);
12368         SendToICS("rematch\n");
12369     }
12370 }
12371
12372 void
12373 CallFlagEvent()
12374 {
12375     /* Call your opponent's flag (claim a win on time) */
12376     if (appData.icsActive) {
12377         SendToICS(ics_prefix);
12378         SendToICS("flag\n");
12379     } else {
12380         switch (gameMode) {
12381           default:
12382             return;
12383           case MachinePlaysWhite:
12384             if (whiteFlag) {
12385                 if (blackFlag)
12386                   GameEnds(GameIsDrawn, "Both players ran out of time",
12387                            GE_PLAYER);
12388                 else
12389                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12390             } else {
12391                 DisplayError(_("Your opponent is not out of time"), 0);
12392             }
12393             break;
12394           case MachinePlaysBlack:
12395             if (blackFlag) {
12396                 if (whiteFlag)
12397                   GameEnds(GameIsDrawn, "Both players ran out of time",
12398                            GE_PLAYER);
12399                 else
12400                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12401             } else {
12402                 DisplayError(_("Your opponent is not out of time"), 0);
12403             }
12404             break;
12405         }
12406     }
12407 }
12408
12409 void
12410 DrawEvent()
12411 {
12412     /* Offer draw or accept pending draw offer from opponent */
12413     
12414     if (appData.icsActive) {
12415         /* Note: tournament rules require draw offers to be
12416            made after you make your move but before you punch
12417            your clock.  Currently ICS doesn't let you do that;
12418            instead, you immediately punch your clock after making
12419            a move, but you can offer a draw at any time. */
12420         
12421         SendToICS(ics_prefix);
12422         SendToICS("draw\n");
12423         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12424     } else if (cmailMsgLoaded) {
12425         if (currentMove == cmailOldMove &&
12426             commentList[cmailOldMove] != NULL &&
12427             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12428                    "Black offers a draw" : "White offers a draw")) {
12429             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12430             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12431         } else if (currentMove == cmailOldMove + 1) {
12432             char *offer = WhiteOnMove(cmailOldMove) ?
12433               "White offers a draw" : "Black offers a draw";
12434             AppendComment(currentMove, offer, TRUE);
12435             DisplayComment(currentMove - 1, offer);
12436             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12437         } else {
12438             DisplayError(_("You must make your move before offering a draw"), 0);
12439             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12440         }
12441     } else if (first.offeredDraw) {
12442         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12443     } else {
12444         if (first.sendDrawOffers) {
12445             SendToProgram("draw\n", &first);
12446             userOfferedDraw = TRUE;
12447         }
12448     }
12449 }
12450
12451 void
12452 AdjournEvent()
12453 {
12454     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12455     
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("adjourn\n");
12459     } else {
12460         /* Currently GNU Chess doesn't offer or accept Adjourns */
12461     }
12462 }
12463
12464
12465 void
12466 AbortEvent()
12467 {
12468     /* Offer Abort or accept pending Abort offer from opponent */
12469     
12470     if (appData.icsActive) {
12471         SendToICS(ics_prefix);
12472         SendToICS("abort\n");
12473     } else {
12474         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12475     }
12476 }
12477
12478 void
12479 ResignEvent()
12480 {
12481     /* Resign.  You can do this even if it's not your turn. */
12482     
12483     if (appData.icsActive) {
12484         SendToICS(ics_prefix);
12485         SendToICS("resign\n");
12486     } else {
12487         switch (gameMode) {
12488           case MachinePlaysWhite:
12489             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12490             break;
12491           case MachinePlaysBlack:
12492             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12493             break;
12494           case EditGame:
12495             if (cmailMsgLoaded) {
12496                 TruncateGame();
12497                 if (WhiteOnMove(cmailOldMove)) {
12498                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12499                 } else {
12500                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12501                 }
12502                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12503             }
12504             break;
12505           default:
12506             break;
12507         }
12508     }
12509 }
12510
12511
12512 void
12513 StopObservingEvent()
12514 {
12515     /* Stop observing current games */
12516     SendToICS(ics_prefix);
12517     SendToICS("unobserve\n");
12518 }
12519
12520 void
12521 StopExaminingEvent()
12522 {
12523     /* Stop observing current game */
12524     SendToICS(ics_prefix);
12525     SendToICS("unexamine\n");
12526 }
12527
12528 void
12529 ForwardInner(target)
12530      int target;
12531 {
12532     int limit;
12533
12534     if (appData.debugMode)
12535         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12536                 target, currentMove, forwardMostMove);
12537
12538     if (gameMode == EditPosition)
12539       return;
12540
12541     if (gameMode == PlayFromGameFile && !pausing)
12542       PauseEvent();
12543     
12544     if (gameMode == IcsExamining && pausing)
12545       limit = pauseExamForwardMostMove;
12546     else
12547       limit = forwardMostMove;
12548     
12549     if (target > limit) target = limit;
12550
12551     if (target > 0 && moveList[target - 1][0]) {
12552         int fromX, fromY, toX, toY;
12553         toX = moveList[target - 1][2] - AAA;
12554         toY = moveList[target - 1][3] - ONE;
12555         if (moveList[target - 1][1] == '@') {
12556             if (appData.highlightLastMove) {
12557                 SetHighlights(-1, -1, toX, toY);
12558             }
12559         } else {
12560             fromX = moveList[target - 1][0] - AAA;
12561             fromY = moveList[target - 1][1] - ONE;
12562             if (target == currentMove + 1) {
12563                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12564             }
12565             if (appData.highlightLastMove) {
12566                 SetHighlights(fromX, fromY, toX, toY);
12567             }
12568         }
12569     }
12570     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12571         gameMode == Training || gameMode == PlayFromGameFile || 
12572         gameMode == AnalyzeFile) {
12573         while (currentMove < target) {
12574             SendMoveToProgram(currentMove++, &first);
12575         }
12576     } else {
12577         currentMove = target;
12578     }
12579     
12580     if (gameMode == EditGame || gameMode == EndOfGame) {
12581         whiteTimeRemaining = timeRemaining[0][currentMove];
12582         blackTimeRemaining = timeRemaining[1][currentMove];
12583     }
12584     DisplayBothClocks();
12585     DisplayMove(currentMove - 1);
12586     DrawPosition(FALSE, boards[currentMove]);
12587     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12588     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12589         DisplayComment(currentMove - 1, commentList[currentMove]);
12590     }
12591 }
12592
12593
12594 void
12595 ForwardEvent()
12596 {
12597     if (gameMode == IcsExamining && !pausing) {
12598         SendToICS(ics_prefix);
12599         SendToICS("forward\n");
12600     } else {
12601         ForwardInner(currentMove + 1);
12602     }
12603 }
12604
12605 void
12606 ToEndEvent()
12607 {
12608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12609         /* to optimze, we temporarily turn off analysis mode while we feed
12610          * the remaining moves to the engine. Otherwise we get analysis output
12611          * after each move.
12612          */ 
12613         if (first.analysisSupport) {
12614           SendToProgram("exit\nforce\n", &first);
12615           first.analyzing = FALSE;
12616         }
12617     }
12618         
12619     if (gameMode == IcsExamining && !pausing) {
12620         SendToICS(ics_prefix);
12621         SendToICS("forward 999999\n");
12622     } else {
12623         ForwardInner(forwardMostMove);
12624     }
12625
12626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12627         /* we have fed all the moves, so reactivate analysis mode */
12628         SendToProgram("analyze\n", &first);
12629         first.analyzing = TRUE;
12630         /*first.maybeThinking = TRUE;*/
12631         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12632     }
12633 }
12634
12635 void
12636 BackwardInner(target)
12637      int target;
12638 {
12639     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12640
12641     if (appData.debugMode)
12642         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12643                 target, currentMove, forwardMostMove);
12644
12645     if (gameMode == EditPosition) return;
12646     if (currentMove <= backwardMostMove) {
12647         ClearHighlights();
12648         DrawPosition(full_redraw, boards[currentMove]);
12649         return;
12650     }
12651     if (gameMode == PlayFromGameFile && !pausing)
12652       PauseEvent();
12653     
12654     if (moveList[target][0]) {
12655         int fromX, fromY, toX, toY;
12656         toX = moveList[target][2] - AAA;
12657         toY = moveList[target][3] - ONE;
12658         if (moveList[target][1] == '@') {
12659             if (appData.highlightLastMove) {
12660                 SetHighlights(-1, -1, toX, toY);
12661             }
12662         } else {
12663             fromX = moveList[target][0] - AAA;
12664             fromY = moveList[target][1] - ONE;
12665             if (target == currentMove - 1) {
12666                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12667             }
12668             if (appData.highlightLastMove) {
12669                 SetHighlights(fromX, fromY, toX, toY);
12670             }
12671         }
12672     }
12673     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12674         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12675         while (currentMove > target) {
12676             SendToProgram("undo\n", &first);
12677             currentMove--;
12678         }
12679     } else {
12680         currentMove = target;
12681     }
12682     
12683     if (gameMode == EditGame || gameMode == EndOfGame) {
12684         whiteTimeRemaining = timeRemaining[0][currentMove];
12685         blackTimeRemaining = timeRemaining[1][currentMove];
12686     }
12687     DisplayBothClocks();
12688     DisplayMove(currentMove - 1);
12689     DrawPosition(full_redraw, boards[currentMove]);
12690     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12691     // [HGM] PV info: routine tests if comment empty
12692     DisplayComment(currentMove - 1, commentList[currentMove]);
12693 }
12694
12695 void
12696 BackwardEvent()
12697 {
12698     if (gameMode == IcsExamining && !pausing) {
12699         SendToICS(ics_prefix);
12700         SendToICS("backward\n");
12701     } else {
12702         BackwardInner(currentMove - 1);
12703     }
12704 }
12705
12706 void
12707 ToStartEvent()
12708 {
12709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12710         /* to optimize, we temporarily turn off analysis mode while we undo
12711          * all the moves. Otherwise we get analysis output after each undo.
12712          */ 
12713         if (first.analysisSupport) {
12714           SendToProgram("exit\nforce\n", &first);
12715           first.analyzing = FALSE;
12716         }
12717     }
12718
12719     if (gameMode == IcsExamining && !pausing) {
12720         SendToICS(ics_prefix);
12721         SendToICS("backward 999999\n");
12722     } else {
12723         BackwardInner(backwardMostMove);
12724     }
12725
12726     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12727         /* we have fed all the moves, so reactivate analysis mode */
12728         SendToProgram("analyze\n", &first);
12729         first.analyzing = TRUE;
12730         /*first.maybeThinking = TRUE;*/
12731         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12732     }
12733 }
12734
12735 void
12736 ToNrEvent(int to)
12737 {
12738   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12739   if (to >= forwardMostMove) to = forwardMostMove;
12740   if (to <= backwardMostMove) to = backwardMostMove;
12741   if (to < currentMove) {
12742     BackwardInner(to);
12743   } else {
12744     ForwardInner(to);
12745   }
12746 }
12747
12748 void
12749 RevertEvent(Boolean annotate)
12750 {
12751     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12752         return;
12753     }
12754     if (gameMode != IcsExamining) {
12755         DisplayError(_("You are not examining a game"), 0);
12756         return;
12757     }
12758     if (pausing) {
12759         DisplayError(_("You can't revert while pausing"), 0);
12760         return;
12761     }
12762     SendToICS(ics_prefix);
12763     SendToICS("revert\n");
12764 }
12765
12766 void
12767 RetractMoveEvent()
12768 {
12769     switch (gameMode) {
12770       case MachinePlaysWhite:
12771       case MachinePlaysBlack:
12772         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12773             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12774             return;
12775         }
12776         if (forwardMostMove < 2) return;
12777         currentMove = forwardMostMove = forwardMostMove - 2;
12778         whiteTimeRemaining = timeRemaining[0][currentMove];
12779         blackTimeRemaining = timeRemaining[1][currentMove];
12780         DisplayBothClocks();
12781         DisplayMove(currentMove - 1);
12782         ClearHighlights();/*!! could figure this out*/
12783         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12784         SendToProgram("remove\n", &first);
12785         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12786         break;
12787
12788       case BeginningOfGame:
12789       default:
12790         break;
12791
12792       case IcsPlayingWhite:
12793       case IcsPlayingBlack:
12794         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12795             SendToICS(ics_prefix);
12796             SendToICS("takeback 2\n");
12797         } else {
12798             SendToICS(ics_prefix);
12799             SendToICS("takeback 1\n");
12800         }
12801         break;
12802     }
12803 }
12804
12805 void
12806 MoveNowEvent()
12807 {
12808     ChessProgramState *cps;
12809
12810     switch (gameMode) {
12811       case MachinePlaysWhite:
12812         if (!WhiteOnMove(forwardMostMove)) {
12813             DisplayError(_("It is your turn"), 0);
12814             return;
12815         }
12816         cps = &first;
12817         break;
12818       case MachinePlaysBlack:
12819         if (WhiteOnMove(forwardMostMove)) {
12820             DisplayError(_("It is your turn"), 0);
12821             return;
12822         }
12823         cps = &first;
12824         break;
12825       case TwoMachinesPlay:
12826         if (WhiteOnMove(forwardMostMove) ==
12827             (first.twoMachinesColor[0] == 'w')) {
12828             cps = &first;
12829         } else {
12830             cps = &second;
12831         }
12832         break;
12833       case BeginningOfGame:
12834       default:
12835         return;
12836     }
12837     SendToProgram("?\n", cps);
12838 }
12839
12840 void
12841 TruncateGameEvent()
12842 {
12843     EditGameEvent();
12844     if (gameMode != EditGame) return;
12845     TruncateGame();
12846 }
12847
12848 void
12849 TruncateGame()
12850 {
12851     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12852     if (forwardMostMove > currentMove) {
12853         if (gameInfo.resultDetails != NULL) {
12854             free(gameInfo.resultDetails);
12855             gameInfo.resultDetails = NULL;
12856             gameInfo.result = GameUnfinished;
12857         }
12858         forwardMostMove = currentMove;
12859         HistorySet(parseList, backwardMostMove, forwardMostMove,
12860                    currentMove-1);
12861     }
12862 }
12863
12864 void
12865 HintEvent()
12866 {
12867     if (appData.noChessProgram) return;
12868     switch (gameMode) {
12869       case MachinePlaysWhite:
12870         if (WhiteOnMove(forwardMostMove)) {
12871             DisplayError(_("Wait until your turn"), 0);
12872             return;
12873         }
12874         break;
12875       case BeginningOfGame:
12876       case MachinePlaysBlack:
12877         if (!WhiteOnMove(forwardMostMove)) {
12878             DisplayError(_("Wait until your turn"), 0);
12879             return;
12880         }
12881         break;
12882       default:
12883         DisplayError(_("No hint available"), 0);
12884         return;
12885     }
12886     SendToProgram("hint\n", &first);
12887     hintRequested = TRUE;
12888 }
12889
12890 void
12891 BookEvent()
12892 {
12893     if (appData.noChessProgram) return;
12894     switch (gameMode) {
12895       case MachinePlaysWhite:
12896         if (WhiteOnMove(forwardMostMove)) {
12897             DisplayError(_("Wait until your turn"), 0);
12898             return;
12899         }
12900         break;
12901       case BeginningOfGame:
12902       case MachinePlaysBlack:
12903         if (!WhiteOnMove(forwardMostMove)) {
12904             DisplayError(_("Wait until your turn"), 0);
12905             return;
12906         }
12907         break;
12908       case EditPosition:
12909         EditPositionDone(TRUE);
12910         break;
12911       case TwoMachinesPlay:
12912         return;
12913       default:
12914         break;
12915     }
12916     SendToProgram("bk\n", &first);
12917     bookOutput[0] = NULLCHAR;
12918     bookRequested = TRUE;
12919 }
12920
12921 void
12922 AboutGameEvent()
12923 {
12924     char *tags = PGNTags(&gameInfo);
12925     TagsPopUp(tags, CmailMsg());
12926     free(tags);
12927 }
12928
12929 /* end button procedures */
12930
12931 void
12932 PrintPosition(fp, move)
12933      FILE *fp;
12934      int move;
12935 {
12936     int i, j;
12937     
12938     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12939         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12940             char c = PieceToChar(boards[move][i][j]);
12941             fputc(c == 'x' ? '.' : c, fp);
12942             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12943         }
12944     }
12945     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12946       fprintf(fp, "white to play\n");
12947     else
12948       fprintf(fp, "black to play\n");
12949 }
12950
12951 void
12952 PrintOpponents(fp)
12953      FILE *fp;
12954 {
12955     if (gameInfo.white != NULL) {
12956         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12957     } else {
12958         fprintf(fp, "\n");
12959     }
12960 }
12961
12962 /* Find last component of program's own name, using some heuristics */
12963 void
12964 TidyProgramName(prog, host, buf)
12965      char *prog, *host, buf[MSG_SIZ];
12966 {
12967     char *p, *q;
12968     int local = (strcmp(host, "localhost") == 0);
12969     while (!local && (p = strchr(prog, ';')) != NULL) {
12970         p++;
12971         while (*p == ' ') p++;
12972         prog = p;
12973     }
12974     if (*prog == '"' || *prog == '\'') {
12975         q = strchr(prog + 1, *prog);
12976     } else {
12977         q = strchr(prog, ' ');
12978     }
12979     if (q == NULL) q = prog + strlen(prog);
12980     p = q;
12981     while (p >= prog && *p != '/' && *p != '\\') p--;
12982     p++;
12983     if(p == prog && *p == '"') p++;
12984     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12985     memcpy(buf, p, q - p);
12986     buf[q - p] = NULLCHAR;
12987     if (!local) {
12988         strcat(buf, "@");
12989         strcat(buf, host);
12990     }
12991 }
12992
12993 char *
12994 TimeControlTagValue()
12995 {
12996     char buf[MSG_SIZ];
12997     if (!appData.clockMode) {
12998         strcpy(buf, "-");
12999     } else if (movesPerSession > 0) {
13000         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13001     } else if (timeIncrement == 0) {
13002         sprintf(buf, "%ld", timeControl/1000);
13003     } else {
13004         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13005     }
13006     return StrSave(buf);
13007 }
13008
13009 void
13010 SetGameInfo()
13011 {
13012     /* This routine is used only for certain modes */
13013     VariantClass v = gameInfo.variant;
13014     ChessMove r = GameUnfinished;
13015     char *p = NULL;
13016
13017     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13018         r = gameInfo.result; 
13019         p = gameInfo.resultDetails; 
13020         gameInfo.resultDetails = NULL;
13021     }
13022     ClearGameInfo(&gameInfo);
13023     gameInfo.variant = v;
13024
13025     switch (gameMode) {
13026       case MachinePlaysWhite:
13027         gameInfo.event = StrSave( appData.pgnEventHeader );
13028         gameInfo.site = StrSave(HostName());
13029         gameInfo.date = PGNDate();
13030         gameInfo.round = StrSave("-");
13031         gameInfo.white = StrSave(first.tidy);
13032         gameInfo.black = StrSave(UserName());
13033         gameInfo.timeControl = TimeControlTagValue();
13034         break;
13035
13036       case MachinePlaysBlack:
13037         gameInfo.event = StrSave( appData.pgnEventHeader );
13038         gameInfo.site = StrSave(HostName());
13039         gameInfo.date = PGNDate();
13040         gameInfo.round = StrSave("-");
13041         gameInfo.white = StrSave(UserName());
13042         gameInfo.black = StrSave(first.tidy);
13043         gameInfo.timeControl = TimeControlTagValue();
13044         break;
13045
13046       case TwoMachinesPlay:
13047         gameInfo.event = StrSave( appData.pgnEventHeader );
13048         gameInfo.site = StrSave(HostName());
13049         gameInfo.date = PGNDate();
13050         if (matchGame > 0) {
13051             char buf[MSG_SIZ];
13052             sprintf(buf, "%d", matchGame);
13053             gameInfo.round = StrSave(buf);
13054         } else {
13055             gameInfo.round = StrSave("-");
13056         }
13057         if (first.twoMachinesColor[0] == 'w') {
13058             gameInfo.white = StrSave(first.tidy);
13059             gameInfo.black = StrSave(second.tidy);
13060         } else {
13061             gameInfo.white = StrSave(second.tidy);
13062             gameInfo.black = StrSave(first.tidy);
13063         }
13064         gameInfo.timeControl = TimeControlTagValue();
13065         break;
13066
13067       case EditGame:
13068         gameInfo.event = StrSave("Edited game");
13069         gameInfo.site = StrSave(HostName());
13070         gameInfo.date = PGNDate();
13071         gameInfo.round = StrSave("-");
13072         gameInfo.white = StrSave("-");
13073         gameInfo.black = StrSave("-");
13074         gameInfo.result = r;
13075         gameInfo.resultDetails = p;
13076         break;
13077
13078       case EditPosition:
13079         gameInfo.event = StrSave("Edited position");
13080         gameInfo.site = StrSave(HostName());
13081         gameInfo.date = PGNDate();
13082         gameInfo.round = StrSave("-");
13083         gameInfo.white = StrSave("-");
13084         gameInfo.black = StrSave("-");
13085         break;
13086
13087       case IcsPlayingWhite:
13088       case IcsPlayingBlack:
13089       case IcsObserving:
13090       case IcsExamining:
13091         break;
13092
13093       case PlayFromGameFile:
13094         gameInfo.event = StrSave("Game from non-PGN file");
13095         gameInfo.site = StrSave(HostName());
13096         gameInfo.date = PGNDate();
13097         gameInfo.round = StrSave("-");
13098         gameInfo.white = StrSave("?");
13099         gameInfo.black = StrSave("?");
13100         break;
13101
13102       default:
13103         break;
13104     }
13105 }
13106
13107 void
13108 ReplaceComment(index, text)
13109      int index;
13110      char *text;
13111 {
13112     int len;
13113
13114     while (*text == '\n') text++;
13115     len = strlen(text);
13116     while (len > 0 && text[len - 1] == '\n') len--;
13117
13118     if (commentList[index] != NULL)
13119       free(commentList[index]);
13120
13121     if (len == 0) {
13122         commentList[index] = NULL;
13123         return;
13124     }
13125   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13126       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13127       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13128     commentList[index] = (char *) malloc(len + 2);
13129     strncpy(commentList[index], text, len);
13130     commentList[index][len] = '\n';
13131     commentList[index][len + 1] = NULLCHAR;
13132   } else { 
13133     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13134     char *p;
13135     commentList[index] = (char *) malloc(len + 6);
13136     strcpy(commentList[index], "{\n");
13137     strncpy(commentList[index]+2, text, len);
13138     commentList[index][len+2] = NULLCHAR;
13139     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13140     strcat(commentList[index], "\n}\n");
13141   }
13142 }
13143
13144 void
13145 CrushCRs(text)
13146      char *text;
13147 {
13148   char *p = text;
13149   char *q = text;
13150   char ch;
13151
13152   do {
13153     ch = *p++;
13154     if (ch == '\r') continue;
13155     *q++ = ch;
13156   } while (ch != '\0');
13157 }
13158
13159 void
13160 AppendComment(index, text, addBraces)
13161      int index;
13162      char *text;
13163      Boolean addBraces; // [HGM] braces: tells if we should add {}
13164 {
13165     int oldlen, len;
13166     char *old;
13167
13168 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13169     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13170
13171     CrushCRs(text);
13172     while (*text == '\n') text++;
13173     len = strlen(text);
13174     while (len > 0 && text[len - 1] == '\n') len--;
13175
13176     if (len == 0) return;
13177
13178     if (commentList[index] != NULL) {
13179         old = commentList[index];
13180         oldlen = strlen(old);
13181         while(commentList[index][oldlen-1] ==  '\n')
13182           commentList[index][--oldlen] = NULLCHAR;
13183         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13184         strcpy(commentList[index], old);
13185         free(old);
13186         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13187         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13188           if(addBraces) addBraces = FALSE; else { text++; len--; }
13189           while (*text == '\n') { text++; len--; }
13190           commentList[index][--oldlen] = NULLCHAR;
13191       }
13192         if(addBraces) strcat(commentList[index], "\n{\n");
13193         else          strcat(commentList[index], "\n");
13194         strcat(commentList[index], text);
13195         if(addBraces) strcat(commentList[index], "\n}\n");
13196         else          strcat(commentList[index], "\n");
13197     } else {
13198         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13199         if(addBraces)
13200              strcpy(commentList[index], "{\n");
13201         else commentList[index][0] = NULLCHAR;
13202         strcat(commentList[index], text);
13203         strcat(commentList[index], "\n");
13204         if(addBraces) strcat(commentList[index], "}\n");
13205     }
13206 }
13207
13208 static char * FindStr( char * text, char * sub_text )
13209 {
13210     char * result = strstr( text, sub_text );
13211
13212     if( result != NULL ) {
13213         result += strlen( sub_text );
13214     }
13215
13216     return result;
13217 }
13218
13219 /* [AS] Try to extract PV info from PGN comment */
13220 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13221 char *GetInfoFromComment( int index, char * text )
13222 {
13223     char * sep = text;
13224
13225     if( text != NULL && index > 0 ) {
13226         int score = 0;
13227         int depth = 0;
13228         int time = -1, sec = 0, deci;
13229         char * s_eval = FindStr( text, "[%eval " );
13230         char * s_emt = FindStr( text, "[%emt " );
13231
13232         if( s_eval != NULL || s_emt != NULL ) {
13233             /* New style */
13234             char delim;
13235
13236             if( s_eval != NULL ) {
13237                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13238                     return text;
13239                 }
13240
13241                 if( delim != ']' ) {
13242                     return text;
13243                 }
13244             }
13245
13246             if( s_emt != NULL ) {
13247             }
13248                 return text;
13249         }
13250         else {
13251             /* We expect something like: [+|-]nnn.nn/dd */
13252             int score_lo = 0;
13253
13254             if(*text != '{') return text; // [HGM] braces: must be normal comment
13255
13256             sep = strchr( text, '/' );
13257             if( sep == NULL || sep < (text+4) ) {
13258                 return text;
13259             }
13260
13261             time = -1; sec = -1; deci = -1;
13262             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13263                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13264                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13265                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13266                 return text;
13267             }
13268
13269             if( score_lo < 0 || score_lo >= 100 ) {
13270                 return text;
13271             }
13272
13273             if(sec >= 0) time = 600*time + 10*sec; else
13274             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13275
13276             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13277
13278             /* [HGM] PV time: now locate end of PV info */
13279             while( *++sep >= '0' && *sep <= '9'); // strip depth
13280             if(time >= 0)
13281             while( *++sep >= '0' && *sep <= '9'); // strip time
13282             if(sec >= 0)
13283             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13284             if(deci >= 0)
13285             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13286             while(*sep == ' ') sep++;
13287         }
13288
13289         if( depth <= 0 ) {
13290             return text;
13291         }
13292
13293         if( time < 0 ) {
13294             time = -1;
13295         }
13296
13297         pvInfoList[index-1].depth = depth;
13298         pvInfoList[index-1].score = score;
13299         pvInfoList[index-1].time  = 10*time; // centi-sec
13300         if(*sep == '}') *sep = 0; else *--sep = '{';
13301     }
13302     return sep;
13303 }
13304
13305 void
13306 SendToProgram(message, cps)
13307      char *message;
13308      ChessProgramState *cps;
13309 {
13310     int count, outCount, error;
13311     char buf[MSG_SIZ];
13312
13313     if (cps->pr == NULL) return;
13314     Attention(cps);
13315     
13316     if (appData.debugMode) {
13317         TimeMark now;
13318         GetTimeMark(&now);
13319         fprintf(debugFP, "%ld >%-6s: %s", 
13320                 SubtractTimeMarks(&now, &programStartTime),
13321                 cps->which, message);
13322     }
13323     
13324     count = strlen(message);
13325     outCount = OutputToProcess(cps->pr, message, count, &error);
13326     if (outCount < count && !exiting 
13327                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13328         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13329         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13330             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13331                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13332                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13333             } else {
13334                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13335             }
13336             gameInfo.resultDetails = StrSave(buf);
13337         }
13338         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13339     }
13340 }
13341
13342 void
13343 ReceiveFromProgram(isr, closure, message, count, error)
13344      InputSourceRef isr;
13345      VOIDSTAR closure;
13346      char *message;
13347      int count;
13348      int error;
13349 {
13350     char *end_str;
13351     char buf[MSG_SIZ];
13352     ChessProgramState *cps = (ChessProgramState *)closure;
13353
13354     if (isr != cps->isr) return; /* Killed intentionally */
13355     if (count <= 0) {
13356         if (count == 0) {
13357             sprintf(buf,
13358                     _("Error: %s chess program (%s) exited unexpectedly"),
13359                     cps->which, cps->program);
13360         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13361                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13362                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13363                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13364                 } else {
13365                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13366                 }
13367                 gameInfo.resultDetails = StrSave(buf);
13368             }
13369             RemoveInputSource(cps->isr);
13370             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13371         } else {
13372             sprintf(buf,
13373                     _("Error reading from %s chess program (%s)"),
13374                     cps->which, cps->program);
13375             RemoveInputSource(cps->isr);
13376
13377             /* [AS] Program is misbehaving badly... kill it */
13378             if( count == -2 ) {
13379                 DestroyChildProcess( cps->pr, 9 );
13380                 cps->pr = NoProc;
13381             }
13382
13383             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13384         }
13385         return;
13386     }
13387     
13388     if ((end_str = strchr(message, '\r')) != NULL)
13389       *end_str = NULLCHAR;
13390     if ((end_str = strchr(message, '\n')) != NULL)
13391       *end_str = NULLCHAR;
13392     
13393     if (appData.debugMode) {
13394         TimeMark now; int print = 1;
13395         char *quote = ""; char c; int i;
13396
13397         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13398                 char start = message[0];
13399                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13400                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13401                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13402                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13403                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13404                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13405                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13406                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13407                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13408                     print = (appData.engineComments >= 2);
13409                 }
13410                 message[0] = start; // restore original message
13411         }
13412         if(print) {
13413                 GetTimeMark(&now);
13414                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13415                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13416                         quote,
13417                         message);
13418         }
13419     }
13420
13421     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13422     if (appData.icsEngineAnalyze) {
13423         if (strstr(message, "whisper") != NULL ||
13424              strstr(message, "kibitz") != NULL || 
13425             strstr(message, "tellics") != NULL) return;
13426     }
13427
13428     HandleMachineMove(message, cps);
13429 }
13430
13431
13432 void
13433 SendTimeControl(cps, mps, tc, inc, sd, st)
13434      ChessProgramState *cps;
13435      int mps, inc, sd, st;
13436      long tc;
13437 {
13438     char buf[MSG_SIZ];
13439     int seconds;
13440
13441     if( timeControl_2 > 0 ) {
13442         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13443             tc = timeControl_2;
13444         }
13445     }
13446     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13447     inc /= cps->timeOdds;
13448     st  /= cps->timeOdds;
13449
13450     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13451
13452     if (st > 0) {
13453       /* Set exact time per move, normally using st command */
13454       if (cps->stKludge) {
13455         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13456         seconds = st % 60;
13457         if (seconds == 0) {
13458           sprintf(buf, "level 1 %d\n", st/60);
13459         } else {
13460           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13461         }
13462       } else {
13463         sprintf(buf, "st %d\n", st);
13464       }
13465     } else {
13466       /* Set conventional or incremental time control, using level command */
13467       if (seconds == 0) {
13468         /* Note old gnuchess bug -- minutes:seconds used to not work.
13469            Fixed in later versions, but still avoid :seconds
13470            when seconds is 0. */
13471         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13472       } else {
13473         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13474                 seconds, inc/1000);
13475       }
13476     }
13477     SendToProgram(buf, cps);
13478
13479     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13480     /* Orthogonally, limit search to given depth */
13481     if (sd > 0) {
13482       if (cps->sdKludge) {
13483         sprintf(buf, "depth\n%d\n", sd);
13484       } else {
13485         sprintf(buf, "sd %d\n", sd);
13486       }
13487       SendToProgram(buf, cps);
13488     }
13489
13490     if(cps->nps > 0) { /* [HGM] nps */
13491         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13492         else {
13493                 sprintf(buf, "nps %d\n", cps->nps);
13494               SendToProgram(buf, cps);
13495         }
13496     }
13497 }
13498
13499 ChessProgramState *WhitePlayer()
13500 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13501 {
13502     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13503        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13504         return &second;
13505     return &first;
13506 }
13507
13508 void
13509 SendTimeRemaining(cps, machineWhite)
13510      ChessProgramState *cps;
13511      int /*boolean*/ machineWhite;
13512 {
13513     char message[MSG_SIZ];
13514     long time, otime;
13515
13516     /* Note: this routine must be called when the clocks are stopped
13517        or when they have *just* been set or switched; otherwise
13518        it will be off by the time since the current tick started.
13519     */
13520     if (machineWhite) {
13521         time = whiteTimeRemaining / 10;
13522         otime = blackTimeRemaining / 10;
13523     } else {
13524         time = blackTimeRemaining / 10;
13525         otime = whiteTimeRemaining / 10;
13526     }
13527     /* [HGM] translate opponent's time by time-odds factor */
13528     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13529     if (appData.debugMode) {
13530         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13531     }
13532
13533     if (time <= 0) time = 1;
13534     if (otime <= 0) otime = 1;
13535     
13536     sprintf(message, "time %ld\n", time);
13537     SendToProgram(message, cps);
13538
13539     sprintf(message, "otim %ld\n", otime);
13540     SendToProgram(message, cps);
13541 }
13542
13543 int
13544 BoolFeature(p, name, loc, cps)
13545      char **p;
13546      char *name;
13547      int *loc;
13548      ChessProgramState *cps;
13549 {
13550   char buf[MSG_SIZ];
13551   int len = strlen(name);
13552   int val;
13553   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13554     (*p) += len + 1;
13555     sscanf(*p, "%d", &val);
13556     *loc = (val != 0);
13557     while (**p && **p != ' ') (*p)++;
13558     sprintf(buf, "accepted %s\n", name);
13559     SendToProgram(buf, cps);
13560     return TRUE;
13561   }
13562   return FALSE;
13563 }
13564
13565 int
13566 IntFeature(p, name, loc, cps)
13567      char **p;
13568      char *name;
13569      int *loc;
13570      ChessProgramState *cps;
13571 {
13572   char buf[MSG_SIZ];
13573   int len = strlen(name);
13574   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13575     (*p) += len + 1;
13576     sscanf(*p, "%d", loc);
13577     while (**p && **p != ' ') (*p)++;
13578     sprintf(buf, "accepted %s\n", name);
13579     SendToProgram(buf, cps);
13580     return TRUE;
13581   }
13582   return FALSE;
13583 }
13584
13585 int
13586 StringFeature(p, name, loc, cps)
13587      char **p;
13588      char *name;
13589      char loc[];
13590      ChessProgramState *cps;
13591 {
13592   char buf[MSG_SIZ];
13593   int len = strlen(name);
13594   if (strncmp((*p), name, len) == 0
13595       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13596     (*p) += len + 2;
13597     sscanf(*p, "%[^\"]", loc);
13598     while (**p && **p != '\"') (*p)++;
13599     if (**p == '\"') (*p)++;
13600     sprintf(buf, "accepted %s\n", name);
13601     SendToProgram(buf, cps);
13602     return TRUE;
13603   }
13604   return FALSE;
13605 }
13606
13607 int 
13608 ParseOption(Option *opt, ChessProgramState *cps)
13609 // [HGM] options: process the string that defines an engine option, and determine
13610 // name, type, default value, and allowed value range
13611 {
13612         char *p, *q, buf[MSG_SIZ];
13613         int n, min = (-1)<<31, max = 1<<31, def;
13614
13615         if(p = strstr(opt->name, " -spin ")) {
13616             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13617             if(max < min) max = min; // enforce consistency
13618             if(def < min) def = min;
13619             if(def > max) def = max;
13620             opt->value = def;
13621             opt->min = min;
13622             opt->max = max;
13623             opt->type = Spin;
13624         } else if((p = strstr(opt->name, " -slider "))) {
13625             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13626             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13627             if(max < min) max = min; // enforce consistency
13628             if(def < min) def = min;
13629             if(def > max) def = max;
13630             opt->value = def;
13631             opt->min = min;
13632             opt->max = max;
13633             opt->type = Spin; // Slider;
13634         } else if((p = strstr(opt->name, " -string "))) {
13635             opt->textValue = p+9;
13636             opt->type = TextBox;
13637         } else if((p = strstr(opt->name, " -file "))) {
13638             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13639             opt->textValue = p+7;
13640             opt->type = TextBox; // FileName;
13641         } else if((p = strstr(opt->name, " -path "))) {
13642             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13643             opt->textValue = p+7;
13644             opt->type = TextBox; // PathName;
13645         } else if(p = strstr(opt->name, " -check ")) {
13646             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13647             opt->value = (def != 0);
13648             opt->type = CheckBox;
13649         } else if(p = strstr(opt->name, " -combo ")) {
13650             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13651             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13652             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13653             opt->value = n = 0;
13654             while(q = StrStr(q, " /// ")) {
13655                 n++; *q = 0;    // count choices, and null-terminate each of them
13656                 q += 5;
13657                 if(*q == '*') { // remember default, which is marked with * prefix
13658                     q++;
13659                     opt->value = n;
13660                 }
13661                 cps->comboList[cps->comboCnt++] = q;
13662             }
13663             cps->comboList[cps->comboCnt++] = NULL;
13664             opt->max = n + 1;
13665             opt->type = ComboBox;
13666         } else if(p = strstr(opt->name, " -button")) {
13667             opt->type = Button;
13668         } else if(p = strstr(opt->name, " -save")) {
13669             opt->type = SaveButton;
13670         } else return FALSE;
13671         *p = 0; // terminate option name
13672         // now look if the command-line options define a setting for this engine option.
13673         if(cps->optionSettings && cps->optionSettings[0])
13674             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13675         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13676                 sprintf(buf, "option %s", p);
13677                 if(p = strstr(buf, ",")) *p = 0;
13678                 strcat(buf, "\n");
13679                 SendToProgram(buf, cps);
13680         }
13681         return TRUE;
13682 }
13683
13684 void
13685 FeatureDone(cps, val)
13686      ChessProgramState* cps;
13687      int val;
13688 {
13689   DelayedEventCallback cb = GetDelayedEvent();
13690   if ((cb == InitBackEnd3 && cps == &first) ||
13691       (cb == TwoMachinesEventIfReady && cps == &second)) {
13692     CancelDelayedEvent();
13693     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13694   }
13695   cps->initDone = val;
13696 }
13697
13698 /* Parse feature command from engine */
13699 void
13700 ParseFeatures(args, cps)
13701      char* args;
13702      ChessProgramState *cps;  
13703 {
13704   char *p = args;
13705   char *q;
13706   int val;
13707   char buf[MSG_SIZ];
13708
13709   for (;;) {
13710     while (*p == ' ') p++;
13711     if (*p == NULLCHAR) return;
13712
13713     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13714     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13715     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13716     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13717     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13718     if (BoolFeature(&p, "reuse", &val, cps)) {
13719       /* Engine can disable reuse, but can't enable it if user said no */
13720       if (!val) cps->reuse = FALSE;
13721       continue;
13722     }
13723     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13724     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13725       if (gameMode == TwoMachinesPlay) {
13726         DisplayTwoMachinesTitle();
13727       } else {
13728         DisplayTitle("");
13729       }
13730       continue;
13731     }
13732     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13733     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13734     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13735     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13736     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13737     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13738     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13739     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13740     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13741     if (IntFeature(&p, "done", &val, cps)) {
13742       FeatureDone(cps, val);
13743       continue;
13744     }
13745     /* Added by Tord: */
13746     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13747     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13748     /* End of additions by Tord */
13749
13750     /* [HGM] added features: */
13751     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13752     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13753     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13754     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13755     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13756     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13757     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13758         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13759             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13760             SendToProgram(buf, cps);
13761             continue;
13762         }
13763         if(cps->nrOptions >= MAX_OPTIONS) {
13764             cps->nrOptions--;
13765             sprintf(buf, "%s engine has too many options\n", cps->which);
13766             DisplayError(buf, 0);
13767         }
13768         continue;
13769     }
13770     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13771     /* End of additions by HGM */
13772
13773     /* unknown feature: complain and skip */
13774     q = p;
13775     while (*q && *q != '=') q++;
13776     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13777     SendToProgram(buf, cps);
13778     p = q;
13779     if (*p == '=') {
13780       p++;
13781       if (*p == '\"') {
13782         p++;
13783         while (*p && *p != '\"') p++;
13784         if (*p == '\"') p++;
13785       } else {
13786         while (*p && *p != ' ') p++;
13787       }
13788     }
13789   }
13790
13791 }
13792
13793 void
13794 PeriodicUpdatesEvent(newState)
13795      int newState;
13796 {
13797     if (newState == appData.periodicUpdates)
13798       return;
13799
13800     appData.periodicUpdates=newState;
13801
13802     /* Display type changes, so update it now */
13803 //    DisplayAnalysis();
13804
13805     /* Get the ball rolling again... */
13806     if (newState) {
13807         AnalysisPeriodicEvent(1);
13808         StartAnalysisClock();
13809     }
13810 }
13811
13812 void
13813 PonderNextMoveEvent(newState)
13814      int newState;
13815 {
13816     if (newState == appData.ponderNextMove) return;
13817     if (gameMode == EditPosition) EditPositionDone(TRUE);
13818     if (newState) {
13819         SendToProgram("hard\n", &first);
13820         if (gameMode == TwoMachinesPlay) {
13821             SendToProgram("hard\n", &second);
13822         }
13823     } else {
13824         SendToProgram("easy\n", &first);
13825         thinkOutput[0] = NULLCHAR;
13826         if (gameMode == TwoMachinesPlay) {
13827             SendToProgram("easy\n", &second);
13828         }
13829     }
13830     appData.ponderNextMove = newState;
13831 }
13832
13833 void
13834 NewSettingEvent(option, command, value)
13835      char *command;
13836      int option, value;
13837 {
13838     char buf[MSG_SIZ];
13839
13840     if (gameMode == EditPosition) EditPositionDone(TRUE);
13841     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13842     SendToProgram(buf, &first);
13843     if (gameMode == TwoMachinesPlay) {
13844         SendToProgram(buf, &second);
13845     }
13846 }
13847
13848 void
13849 ShowThinkingEvent()
13850 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13851 {
13852     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13853     int newState = appData.showThinking
13854         // [HGM] thinking: other features now need thinking output as well
13855         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13856     
13857     if (oldState == newState) return;
13858     oldState = newState;
13859     if (gameMode == EditPosition) EditPositionDone(TRUE);
13860     if (oldState) {
13861         SendToProgram("post\n", &first);
13862         if (gameMode == TwoMachinesPlay) {
13863             SendToProgram("post\n", &second);
13864         }
13865     } else {
13866         SendToProgram("nopost\n", &first);
13867         thinkOutput[0] = NULLCHAR;
13868         if (gameMode == TwoMachinesPlay) {
13869             SendToProgram("nopost\n", &second);
13870         }
13871     }
13872 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13873 }
13874
13875 void
13876 AskQuestionEvent(title, question, replyPrefix, which)
13877      char *title; char *question; char *replyPrefix; char *which;
13878 {
13879   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13880   if (pr == NoProc) return;
13881   AskQuestion(title, question, replyPrefix, pr);
13882 }
13883
13884 void
13885 DisplayMove(moveNumber)
13886      int moveNumber;
13887 {
13888     char message[MSG_SIZ];
13889     char res[MSG_SIZ];
13890     char cpThinkOutput[MSG_SIZ];
13891
13892     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13893     
13894     if (moveNumber == forwardMostMove - 1 || 
13895         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13896
13897         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13898
13899         if (strchr(cpThinkOutput, '\n')) {
13900             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13901         }
13902     } else {
13903         *cpThinkOutput = NULLCHAR;
13904     }
13905
13906     /* [AS] Hide thinking from human user */
13907     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13908         *cpThinkOutput = NULLCHAR;
13909         if( thinkOutput[0] != NULLCHAR ) {
13910             int i;
13911
13912             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13913                 cpThinkOutput[i] = '.';
13914             }
13915             cpThinkOutput[i] = NULLCHAR;
13916             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13917         }
13918     }
13919
13920     if (moveNumber == forwardMostMove - 1 &&
13921         gameInfo.resultDetails != NULL) {
13922         if (gameInfo.resultDetails[0] == NULLCHAR) {
13923             sprintf(res, " %s", PGNResult(gameInfo.result));
13924         } else {
13925             sprintf(res, " {%s} %s",
13926                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13927         }
13928     } else {
13929         res[0] = NULLCHAR;
13930     }
13931
13932     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13933         DisplayMessage(res, cpThinkOutput);
13934     } else {
13935         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13936                 WhiteOnMove(moveNumber) ? " " : ".. ",
13937                 parseList[moveNumber], res);
13938         DisplayMessage(message, cpThinkOutput);
13939     }
13940 }
13941
13942 void
13943 DisplayComment(moveNumber, text)
13944      int moveNumber;
13945      char *text;
13946 {
13947     char title[MSG_SIZ];
13948     char buf[8000]; // comment can be long!
13949     int score, depth;
13950     
13951     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13952       strcpy(title, "Comment");
13953     } else {
13954       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13955               WhiteOnMove(moveNumber) ? " " : ".. ",
13956               parseList[moveNumber]);
13957     }
13958     // [HGM] PV info: display PV info together with (or as) comment
13959     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13960       if(text == NULL) text = "";                                           
13961       score = pvInfoList[moveNumber].score;
13962       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13963               depth, (pvInfoList[moveNumber].time+50)/100, text);
13964       text = buf;
13965     }
13966     if (text != NULL && (appData.autoDisplayComment || commentUp))
13967         CommentPopUp(title, text);
13968 }
13969
13970 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13971  * might be busy thinking or pondering.  It can be omitted if your
13972  * gnuchess is configured to stop thinking immediately on any user
13973  * input.  However, that gnuchess feature depends on the FIONREAD
13974  * ioctl, which does not work properly on some flavors of Unix.
13975  */
13976 void
13977 Attention(cps)
13978      ChessProgramState *cps;
13979 {
13980 #if ATTENTION
13981     if (!cps->useSigint) return;
13982     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13983     switch (gameMode) {
13984       case MachinePlaysWhite:
13985       case MachinePlaysBlack:
13986       case TwoMachinesPlay:
13987       case IcsPlayingWhite:
13988       case IcsPlayingBlack:
13989       case AnalyzeMode:
13990       case AnalyzeFile:
13991         /* Skip if we know it isn't thinking */
13992         if (!cps->maybeThinking) return;
13993         if (appData.debugMode)
13994           fprintf(debugFP, "Interrupting %s\n", cps->which);
13995         InterruptChildProcess(cps->pr);
13996         cps->maybeThinking = FALSE;
13997         break;
13998       default:
13999         break;
14000     }
14001 #endif /*ATTENTION*/
14002 }
14003
14004 int
14005 CheckFlags()
14006 {
14007     if (whiteTimeRemaining <= 0) {
14008         if (!whiteFlag) {
14009             whiteFlag = TRUE;
14010             if (appData.icsActive) {
14011                 if (appData.autoCallFlag &&
14012                     gameMode == IcsPlayingBlack && !blackFlag) {
14013                   SendToICS(ics_prefix);
14014                   SendToICS("flag\n");
14015                 }
14016             } else {
14017                 if (blackFlag) {
14018                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14019                 } else {
14020                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14021                     if (appData.autoCallFlag) {
14022                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14023                         return TRUE;
14024                     }
14025                 }
14026             }
14027         }
14028     }
14029     if (blackTimeRemaining <= 0) {
14030         if (!blackFlag) {
14031             blackFlag = TRUE;
14032             if (appData.icsActive) {
14033                 if (appData.autoCallFlag &&
14034                     gameMode == IcsPlayingWhite && !whiteFlag) {
14035                   SendToICS(ics_prefix);
14036                   SendToICS("flag\n");
14037                 }
14038             } else {
14039                 if (whiteFlag) {
14040                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14041                 } else {
14042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14043                     if (appData.autoCallFlag) {
14044                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14045                         return TRUE;
14046                     }
14047                 }
14048             }
14049         }
14050     }
14051     return FALSE;
14052 }
14053
14054 void
14055 CheckTimeControl()
14056 {
14057     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14058         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14059
14060     /*
14061      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14062      */
14063     if ( !WhiteOnMove(forwardMostMove) )
14064         /* White made time control */
14065         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14066         /* [HGM] time odds: correct new time quota for time odds! */
14067                                             / WhitePlayer()->timeOdds;
14068       else
14069         /* Black made time control */
14070         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14071                                             / WhitePlayer()->other->timeOdds;
14072 }
14073
14074 void
14075 DisplayBothClocks()
14076 {
14077     int wom = gameMode == EditPosition ?
14078       !blackPlaysFirst : WhiteOnMove(currentMove);
14079     DisplayWhiteClock(whiteTimeRemaining, wom);
14080     DisplayBlackClock(blackTimeRemaining, !wom);
14081 }
14082
14083
14084 /* Timekeeping seems to be a portability nightmare.  I think everyone
14085    has ftime(), but I'm really not sure, so I'm including some ifdefs
14086    to use other calls if you don't.  Clocks will be less accurate if
14087    you have neither ftime nor gettimeofday.
14088 */
14089
14090 /* VS 2008 requires the #include outside of the function */
14091 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14092 #include <sys/timeb.h>
14093 #endif
14094
14095 /* Get the current time as a TimeMark */
14096 void
14097 GetTimeMark(tm)
14098      TimeMark *tm;
14099 {
14100 #if HAVE_GETTIMEOFDAY
14101
14102     struct timeval timeVal;
14103     struct timezone timeZone;
14104
14105     gettimeofday(&timeVal, &timeZone);
14106     tm->sec = (long) timeVal.tv_sec; 
14107     tm->ms = (int) (timeVal.tv_usec / 1000L);
14108
14109 #else /*!HAVE_GETTIMEOFDAY*/
14110 #if HAVE_FTIME
14111
14112 // include <sys/timeb.h> / moved to just above start of function
14113     struct timeb timeB;
14114
14115     ftime(&timeB);
14116     tm->sec = (long) timeB.time;
14117     tm->ms = (int) timeB.millitm;
14118
14119 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14120     tm->sec = (long) time(NULL);
14121     tm->ms = 0;
14122 #endif
14123 #endif
14124 }
14125
14126 /* Return the difference in milliseconds between two
14127    time marks.  We assume the difference will fit in a long!
14128 */
14129 long
14130 SubtractTimeMarks(tm2, tm1)
14131      TimeMark *tm2, *tm1;
14132 {
14133     return 1000L*(tm2->sec - tm1->sec) +
14134            (long) (tm2->ms - tm1->ms);
14135 }
14136
14137
14138 /*
14139  * Code to manage the game clocks.
14140  *
14141  * In tournament play, black starts the clock and then white makes a move.
14142  * We give the human user a slight advantage if he is playing white---the
14143  * clocks don't run until he makes his first move, so it takes zero time.
14144  * Also, we don't account for network lag, so we could get out of sync
14145  * with GNU Chess's clock -- but then, referees are always right.  
14146  */
14147
14148 static TimeMark tickStartTM;
14149 static long intendedTickLength;
14150
14151 long
14152 NextTickLength(timeRemaining)
14153      long timeRemaining;
14154 {
14155     long nominalTickLength, nextTickLength;
14156
14157     if (timeRemaining > 0L && timeRemaining <= 10000L)
14158       nominalTickLength = 100L;
14159     else
14160       nominalTickLength = 1000L;
14161     nextTickLength = timeRemaining % nominalTickLength;
14162     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14163
14164     return nextTickLength;
14165 }
14166
14167 /* Adjust clock one minute up or down */
14168 void
14169 AdjustClock(Boolean which, int dir)
14170 {
14171     if(which) blackTimeRemaining += 60000*dir;
14172     else      whiteTimeRemaining += 60000*dir;
14173     DisplayBothClocks();
14174 }
14175
14176 /* Stop clocks and reset to a fresh time control */
14177 void
14178 ResetClocks() 
14179 {
14180     (void) StopClockTimer();
14181     if (appData.icsActive) {
14182         whiteTimeRemaining = blackTimeRemaining = 0;
14183     } else if (searchTime) {
14184         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14185         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14186     } else { /* [HGM] correct new time quote for time odds */
14187         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14188         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14189     }
14190     if (whiteFlag || blackFlag) {
14191         DisplayTitle("");
14192         whiteFlag = blackFlag = FALSE;
14193     }
14194     DisplayBothClocks();
14195 }
14196
14197 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14198
14199 /* Decrement running clock by amount of time that has passed */
14200 void
14201 DecrementClocks()
14202 {
14203     long timeRemaining;
14204     long lastTickLength, fudge;
14205     TimeMark now;
14206
14207     if (!appData.clockMode) return;
14208     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14209         
14210     GetTimeMark(&now);
14211
14212     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14213
14214     /* Fudge if we woke up a little too soon */
14215     fudge = intendedTickLength - lastTickLength;
14216     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14217
14218     if (WhiteOnMove(forwardMostMove)) {
14219         if(whiteNPS >= 0) lastTickLength = 0;
14220         timeRemaining = whiteTimeRemaining -= lastTickLength;
14221         DisplayWhiteClock(whiteTimeRemaining - fudge,
14222                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14223     } else {
14224         if(blackNPS >= 0) lastTickLength = 0;
14225         timeRemaining = blackTimeRemaining -= lastTickLength;
14226         DisplayBlackClock(blackTimeRemaining - fudge,
14227                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14228     }
14229
14230     if (CheckFlags()) return;
14231         
14232     tickStartTM = now;
14233     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14234     StartClockTimer(intendedTickLength);
14235
14236     /* if the time remaining has fallen below the alarm threshold, sound the
14237      * alarm. if the alarm has sounded and (due to a takeback or time control
14238      * with increment) the time remaining has increased to a level above the
14239      * threshold, reset the alarm so it can sound again. 
14240      */
14241     
14242     if (appData.icsActive && appData.icsAlarm) {
14243
14244         /* make sure we are dealing with the user's clock */
14245         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14246                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14247            )) return;
14248
14249         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14250             alarmSounded = FALSE;
14251         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14252             PlayAlarmSound();
14253             alarmSounded = TRUE;
14254         }
14255     }
14256 }
14257
14258
14259 /* A player has just moved, so stop the previously running
14260    clock and (if in clock mode) start the other one.
14261    We redisplay both clocks in case we're in ICS mode, because
14262    ICS gives us an update to both clocks after every move.
14263    Note that this routine is called *after* forwardMostMove
14264    is updated, so the last fractional tick must be subtracted
14265    from the color that is *not* on move now.
14266 */
14267 void
14268 SwitchClocks(int newMoveNr)
14269 {
14270     long lastTickLength;
14271     TimeMark now;
14272     int flagged = FALSE;
14273
14274     GetTimeMark(&now);
14275
14276     if (StopClockTimer() && appData.clockMode) {
14277         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14278         if (!WhiteOnMove(forwardMostMove)) {
14279             if(blackNPS >= 0) lastTickLength = 0;
14280             blackTimeRemaining -= lastTickLength;
14281            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14282 //         if(pvInfoList[forwardMostMove-1].time == -1)
14283                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14284                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14285         } else {
14286            if(whiteNPS >= 0) lastTickLength = 0;
14287            whiteTimeRemaining -= lastTickLength;
14288            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14289 //         if(pvInfoList[forwardMostMove-1].time == -1)
14290                  pvInfoList[forwardMostMove-1].time = 
14291                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14292         }
14293         flagged = CheckFlags();
14294     }
14295     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14296     CheckTimeControl();
14297
14298     if (flagged || !appData.clockMode) return;
14299
14300     switch (gameMode) {
14301       case MachinePlaysBlack:
14302       case MachinePlaysWhite:
14303       case BeginningOfGame:
14304         if (pausing) return;
14305         break;
14306
14307       case EditGame:
14308       case PlayFromGameFile:
14309       case IcsExamining:
14310         return;
14311
14312       default:
14313         break;
14314     }
14315
14316     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14317         if(WhiteOnMove(forwardMostMove))
14318              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14319         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14320     }
14321
14322     tickStartTM = now;
14323     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14324       whiteTimeRemaining : blackTimeRemaining);
14325     StartClockTimer(intendedTickLength);
14326 }
14327         
14328
14329 /* Stop both clocks */
14330 void
14331 StopClocks()
14332 {       
14333     long lastTickLength;
14334     TimeMark now;
14335
14336     if (!StopClockTimer()) return;
14337     if (!appData.clockMode) return;
14338
14339     GetTimeMark(&now);
14340
14341     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14342     if (WhiteOnMove(forwardMostMove)) {
14343         if(whiteNPS >= 0) lastTickLength = 0;
14344         whiteTimeRemaining -= lastTickLength;
14345         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14346     } else {
14347         if(blackNPS >= 0) lastTickLength = 0;
14348         blackTimeRemaining -= lastTickLength;
14349         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14350     }
14351     CheckFlags();
14352 }
14353         
14354 /* Start clock of player on move.  Time may have been reset, so
14355    if clock is already running, stop and restart it. */
14356 void
14357 StartClocks()
14358 {
14359     (void) StopClockTimer(); /* in case it was running already */
14360     DisplayBothClocks();
14361     if (CheckFlags()) return;
14362
14363     if (!appData.clockMode) return;
14364     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14365
14366     GetTimeMark(&tickStartTM);
14367     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14368       whiteTimeRemaining : blackTimeRemaining);
14369
14370    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14371     whiteNPS = blackNPS = -1; 
14372     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14373        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14374         whiteNPS = first.nps;
14375     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14376        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14377         blackNPS = first.nps;
14378     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14379         whiteNPS = second.nps;
14380     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14381         blackNPS = second.nps;
14382     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14383
14384     StartClockTimer(intendedTickLength);
14385 }
14386
14387 char *
14388 TimeString(ms)
14389      long ms;
14390 {
14391     long second, minute, hour, day;
14392     char *sign = "";
14393     static char buf[32];
14394     
14395     if (ms > 0 && ms <= 9900) {
14396       /* convert milliseconds to tenths, rounding up */
14397       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14398
14399       sprintf(buf, " %03.1f ", tenths/10.0);
14400       return buf;
14401     }
14402
14403     /* convert milliseconds to seconds, rounding up */
14404     /* use floating point to avoid strangeness of integer division
14405        with negative dividends on many machines */
14406     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14407
14408     if (second < 0) {
14409         sign = "-";
14410         second = -second;
14411     }
14412     
14413     day = second / (60 * 60 * 24);
14414     second = second % (60 * 60 * 24);
14415     hour = second / (60 * 60);
14416     second = second % (60 * 60);
14417     minute = second / 60;
14418     second = second % 60;
14419     
14420     if (day > 0)
14421       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14422               sign, day, hour, minute, second);
14423     else if (hour > 0)
14424       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14425     else
14426       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14427     
14428     return buf;
14429 }
14430
14431
14432 /*
14433  * This is necessary because some C libraries aren't ANSI C compliant yet.
14434  */
14435 char *
14436 StrStr(string, match)
14437      char *string, *match;
14438 {
14439     int i, length;
14440     
14441     length = strlen(match);
14442     
14443     for (i = strlen(string) - length; i >= 0; i--, string++)
14444       if (!strncmp(match, string, length))
14445         return string;
14446     
14447     return NULL;
14448 }
14449
14450 char *
14451 StrCaseStr(string, match)
14452      char *string, *match;
14453 {
14454     int i, j, length;
14455     
14456     length = strlen(match);
14457     
14458     for (i = strlen(string) - length; i >= 0; i--, string++) {
14459         for (j = 0; j < length; j++) {
14460             if (ToLower(match[j]) != ToLower(string[j]))
14461               break;
14462         }
14463         if (j == length) return string;
14464     }
14465
14466     return NULL;
14467 }
14468
14469 #ifndef _amigados
14470 int
14471 StrCaseCmp(s1, s2)
14472      char *s1, *s2;
14473 {
14474     char c1, c2;
14475     
14476     for (;;) {
14477         c1 = ToLower(*s1++);
14478         c2 = ToLower(*s2++);
14479         if (c1 > c2) return 1;
14480         if (c1 < c2) return -1;
14481         if (c1 == NULLCHAR) return 0;
14482     }
14483 }
14484
14485
14486 int
14487 ToLower(c)
14488      int c;
14489 {
14490     return isupper(c) ? tolower(c) : c;
14491 }
14492
14493
14494 int
14495 ToUpper(c)
14496      int c;
14497 {
14498     return islower(c) ? toupper(c) : c;
14499 }
14500 #endif /* !_amigados    */
14501
14502 char *
14503 StrSave(s)
14504      char *s;
14505 {
14506     char *ret;
14507
14508     if ((ret = (char *) malloc(strlen(s) + 1))) {
14509         strcpy(ret, s);
14510     }
14511     return ret;
14512 }
14513
14514 char *
14515 StrSavePtr(s, savePtr)
14516      char *s, **savePtr;
14517 {
14518     if (*savePtr) {
14519         free(*savePtr);
14520     }
14521     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14522         strcpy(*savePtr, s);
14523     }
14524     return(*savePtr);
14525 }
14526
14527 char *
14528 PGNDate()
14529 {
14530     time_t clock;
14531     struct tm *tm;
14532     char buf[MSG_SIZ];
14533
14534     clock = time((time_t *)NULL);
14535     tm = localtime(&clock);
14536     sprintf(buf, "%04d.%02d.%02d",
14537             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14538     return StrSave(buf);
14539 }
14540
14541
14542 char *
14543 PositionToFEN(move, overrideCastling)
14544      int move;
14545      char *overrideCastling;
14546 {
14547     int i, j, fromX, fromY, toX, toY;
14548     int whiteToPlay;
14549     char buf[128];
14550     char *p, *q;
14551     int emptycount;
14552     ChessSquare piece;
14553
14554     whiteToPlay = (gameMode == EditPosition) ?
14555       !blackPlaysFirst : (move % 2 == 0);
14556     p = buf;
14557
14558     /* Piece placement data */
14559     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14560         emptycount = 0;
14561         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14562             if (boards[move][i][j] == EmptySquare) {
14563                 emptycount++;
14564             } else { ChessSquare piece = boards[move][i][j];
14565                 if (emptycount > 0) {
14566                     if(emptycount<10) /* [HGM] can be >= 10 */
14567                         *p++ = '0' + emptycount;
14568                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14569                     emptycount = 0;
14570                 }
14571                 if(PieceToChar(piece) == '+') {
14572                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14573                     *p++ = '+';
14574                     piece = (ChessSquare)(DEMOTED piece);
14575                 } 
14576                 *p++ = PieceToChar(piece);
14577                 if(p[-1] == '~') {
14578                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14579                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14580                     *p++ = '~';
14581                 }
14582             }
14583         }
14584         if (emptycount > 0) {
14585             if(emptycount<10) /* [HGM] can be >= 10 */
14586                 *p++ = '0' + emptycount;
14587             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14588             emptycount = 0;
14589         }
14590         *p++ = '/';
14591     }
14592     *(p - 1) = ' ';
14593
14594     /* [HGM] print Crazyhouse or Shogi holdings */
14595     if( gameInfo.holdingsWidth ) {
14596         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14597         q = p;
14598         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14599             piece = boards[move][i][BOARD_WIDTH-1];
14600             if( piece != EmptySquare )
14601               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14602                   *p++ = PieceToChar(piece);
14603         }
14604         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14605             piece = boards[move][BOARD_HEIGHT-i-1][0];
14606             if( piece != EmptySquare )
14607               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14608                   *p++ = PieceToChar(piece);
14609         }
14610
14611         if( q == p ) *p++ = '-';
14612         *p++ = ']';
14613         *p++ = ' ';
14614     }
14615
14616     /* Active color */
14617     *p++ = whiteToPlay ? 'w' : 'b';
14618     *p++ = ' ';
14619
14620   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14621     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14622   } else {
14623   if(nrCastlingRights) {
14624      q = p;
14625      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14626        /* [HGM] write directly from rights */
14627            if(boards[move][CASTLING][2] != NoRights &&
14628               boards[move][CASTLING][0] != NoRights   )
14629                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14630            if(boards[move][CASTLING][2] != NoRights &&
14631               boards[move][CASTLING][1] != NoRights   )
14632                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14633            if(boards[move][CASTLING][5] != NoRights &&
14634               boards[move][CASTLING][3] != NoRights   )
14635                 *p++ = boards[move][CASTLING][3] + AAA;
14636            if(boards[move][CASTLING][5] != NoRights &&
14637               boards[move][CASTLING][4] != NoRights   )
14638                 *p++ = boards[move][CASTLING][4] + AAA;
14639      } else {
14640
14641         /* [HGM] write true castling rights */
14642         if( nrCastlingRights == 6 ) {
14643             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14644                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14645             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14646                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14647             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14648                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14649             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14650                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14651         }
14652      }
14653      if (q == p) *p++ = '-'; /* No castling rights */
14654      *p++ = ' ';
14655   }
14656
14657   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14658      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14659     /* En passant target square */
14660     if (move > backwardMostMove) {
14661         fromX = moveList[move - 1][0] - AAA;
14662         fromY = moveList[move - 1][1] - ONE;
14663         toX = moveList[move - 1][2] - AAA;
14664         toY = moveList[move - 1][3] - ONE;
14665         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14666             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14667             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14668             fromX == toX) {
14669             /* 2-square pawn move just happened */
14670             *p++ = toX + AAA;
14671             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14672         } else {
14673             *p++ = '-';
14674         }
14675     } else if(move == backwardMostMove) {
14676         // [HGM] perhaps we should always do it like this, and forget the above?
14677         if((signed char)boards[move][EP_STATUS] >= 0) {
14678             *p++ = boards[move][EP_STATUS] + AAA;
14679             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14680         } else {
14681             *p++ = '-';
14682         }
14683     } else {
14684         *p++ = '-';
14685     }
14686     *p++ = ' ';
14687   }
14688   }
14689
14690     /* [HGM] find reversible plies */
14691     {   int i = 0, j=move;
14692
14693         if (appData.debugMode) { int k;
14694             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14695             for(k=backwardMostMove; k<=forwardMostMove; k++)
14696                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14697
14698         }
14699
14700         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14701         if( j == backwardMostMove ) i += initialRulePlies;
14702         sprintf(p, "%d ", i);
14703         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14704     }
14705     /* Fullmove number */
14706     sprintf(p, "%d", (move / 2) + 1);
14707     
14708     return StrSave(buf);
14709 }
14710
14711 Boolean
14712 ParseFEN(board, blackPlaysFirst, fen)
14713     Board board;
14714      int *blackPlaysFirst;
14715      char *fen;
14716 {
14717     int i, j;
14718     char *p;
14719     int emptycount;
14720     ChessSquare piece;
14721
14722     p = fen;
14723
14724     /* [HGM] by default clear Crazyhouse holdings, if present */
14725     if(gameInfo.holdingsWidth) {
14726        for(i=0; i<BOARD_HEIGHT; i++) {
14727            board[i][0]             = EmptySquare; /* black holdings */
14728            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14729            board[i][1]             = (ChessSquare) 0; /* black counts */
14730            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14731        }
14732     }
14733
14734     /* Piece placement data */
14735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14736         j = 0;
14737         for (;;) {
14738             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14739                 if (*p == '/') p++;
14740                 emptycount = gameInfo.boardWidth - j;
14741                 while (emptycount--)
14742                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14743                 break;
14744 #if(BOARD_FILES >= 10)
14745             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14746                 p++; emptycount=10;
14747                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14748                 while (emptycount--)
14749                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14750 #endif
14751             } else if (isdigit(*p)) {
14752                 emptycount = *p++ - '0';
14753                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14754                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14755                 while (emptycount--)
14756                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14757             } else if (*p == '+' || isalpha(*p)) {
14758                 if (j >= gameInfo.boardWidth) return FALSE;
14759                 if(*p=='+') {
14760                     piece = CharToPiece(*++p);
14761                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14762                     piece = (ChessSquare) (PROMOTED piece ); p++;
14763                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14764                 } else piece = CharToPiece(*p++);
14765
14766                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14767                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14768                     piece = (ChessSquare) (PROMOTED piece);
14769                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14770                     p++;
14771                 }
14772                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14773             } else {
14774                 return FALSE;
14775             }
14776         }
14777     }
14778     while (*p == '/' || *p == ' ') p++;
14779
14780     /* [HGM] look for Crazyhouse holdings here */
14781     while(*p==' ') p++;
14782     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14783         if(*p == '[') p++;
14784         if(*p == '-' ) *p++; /* empty holdings */ else {
14785             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14786             /* if we would allow FEN reading to set board size, we would   */
14787             /* have to add holdings and shift the board read so far here   */
14788             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14789                 *p++;
14790                 if((int) piece >= (int) BlackPawn ) {
14791                     i = (int)piece - (int)BlackPawn;
14792                     i = PieceToNumber((ChessSquare)i);
14793                     if( i >= gameInfo.holdingsSize ) return FALSE;
14794                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14795                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14796                 } else {
14797                     i = (int)piece - (int)WhitePawn;
14798                     i = PieceToNumber((ChessSquare)i);
14799                     if( i >= gameInfo.holdingsSize ) return FALSE;
14800                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14801                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14802                 }
14803             }
14804         }
14805         if(*p == ']') *p++;
14806     }
14807
14808     while(*p == ' ') p++;
14809
14810     /* Active color */
14811     switch (*p++) {
14812       case 'w':
14813         *blackPlaysFirst = FALSE;
14814         break;
14815       case 'b': 
14816         *blackPlaysFirst = TRUE;
14817         break;
14818       default:
14819         return FALSE;
14820     }
14821
14822     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14823     /* return the extra info in global variiables             */
14824
14825     /* set defaults in case FEN is incomplete */
14826     board[EP_STATUS] = EP_UNKNOWN;
14827     for(i=0; i<nrCastlingRights; i++ ) {
14828         board[CASTLING][i] =
14829             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14830     }   /* assume possible unless obviously impossible */
14831     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14832     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14833     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14834                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14835     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14836     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14837     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14838                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14839     FENrulePlies = 0;
14840
14841     while(*p==' ') p++;
14842     if(nrCastlingRights) {
14843       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14844           /* castling indicator present, so default becomes no castlings */
14845           for(i=0; i<nrCastlingRights; i++ ) {
14846                  board[CASTLING][i] = NoRights;
14847           }
14848       }
14849       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14850              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14851              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14852              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14853         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14854
14855         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14856             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14857             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14858         }
14859         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14860             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14861         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14862                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14863         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14864                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14865         switch(c) {
14866           case'K':
14867               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14868               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14869               board[CASTLING][2] = whiteKingFile;
14870               break;
14871           case'Q':
14872               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14873               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14874               board[CASTLING][2] = whiteKingFile;
14875               break;
14876           case'k':
14877               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14878               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14879               board[CASTLING][5] = blackKingFile;
14880               break;
14881           case'q':
14882               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14883               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14884               board[CASTLING][5] = blackKingFile;
14885           case '-':
14886               break;
14887           default: /* FRC castlings */
14888               if(c >= 'a') { /* black rights */
14889                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14890                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14891                   if(i == BOARD_RGHT) break;
14892                   board[CASTLING][5] = i;
14893                   c -= AAA;
14894                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14895                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14896                   if(c > i)
14897                       board[CASTLING][3] = c;
14898                   else
14899                       board[CASTLING][4] = c;
14900               } else { /* white rights */
14901                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14902                     if(board[0][i] == WhiteKing) break;
14903                   if(i == BOARD_RGHT) break;
14904                   board[CASTLING][2] = i;
14905                   c -= AAA - 'a' + 'A';
14906                   if(board[0][c] >= WhiteKing) break;
14907                   if(c > i)
14908                       board[CASTLING][0] = c;
14909                   else
14910                       board[CASTLING][1] = c;
14911               }
14912         }
14913       }
14914       for(i=0; i<nrCastlingRights; i++)
14915         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14916     if (appData.debugMode) {
14917         fprintf(debugFP, "FEN castling rights:");
14918         for(i=0; i<nrCastlingRights; i++)
14919         fprintf(debugFP, " %d", board[CASTLING][i]);
14920         fprintf(debugFP, "\n");
14921     }
14922
14923       while(*p==' ') p++;
14924     }
14925
14926     /* read e.p. field in games that know e.p. capture */
14927     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14928        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14929       if(*p=='-') {
14930         p++; board[EP_STATUS] = EP_NONE;
14931       } else {
14932          char c = *p++ - AAA;
14933
14934          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14935          if(*p >= '0' && *p <='9') *p++;
14936          board[EP_STATUS] = c;
14937       }
14938     }
14939
14940
14941     if(sscanf(p, "%d", &i) == 1) {
14942         FENrulePlies = i; /* 50-move ply counter */
14943         /* (The move number is still ignored)    */
14944     }
14945
14946     return TRUE;
14947 }
14948       
14949 void
14950 EditPositionPasteFEN(char *fen)
14951 {
14952   if (fen != NULL) {
14953     Board initial_position;
14954
14955     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14956       DisplayError(_("Bad FEN position in clipboard"), 0);
14957       return ;
14958     } else {
14959       int savedBlackPlaysFirst = blackPlaysFirst;
14960       EditPositionEvent();
14961       blackPlaysFirst = savedBlackPlaysFirst;
14962       CopyBoard(boards[0], initial_position);
14963       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14964       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14965       DisplayBothClocks();
14966       DrawPosition(FALSE, boards[currentMove]);
14967     }
14968   }
14969 }
14970
14971 static char cseq[12] = "\\   ";
14972
14973 Boolean set_cont_sequence(char *new_seq)
14974 {
14975     int len;
14976     Boolean ret;
14977
14978     // handle bad attempts to set the sequence
14979         if (!new_seq)
14980                 return 0; // acceptable error - no debug
14981
14982     len = strlen(new_seq);
14983     ret = (len > 0) && (len < sizeof(cseq));
14984     if (ret)
14985         strcpy(cseq, new_seq);
14986     else if (appData.debugMode)
14987         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14988     return ret;
14989 }
14990
14991 /*
14992     reformat a source message so words don't cross the width boundary.  internal
14993     newlines are not removed.  returns the wrapped size (no null character unless
14994     included in source message).  If dest is NULL, only calculate the size required
14995     for the dest buffer.  lp argument indicats line position upon entry, and it's
14996     passed back upon exit.
14997 */
14998 int wrap(char *dest, char *src, int count, int width, int *lp)
14999 {
15000     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15001
15002     cseq_len = strlen(cseq);
15003     old_line = line = *lp;
15004     ansi = len = clen = 0;
15005
15006     for (i=0; i < count; i++)
15007     {
15008         if (src[i] == '\033')
15009             ansi = 1;
15010
15011         // if we hit the width, back up
15012         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15013         {
15014             // store i & len in case the word is too long
15015             old_i = i, old_len = len;
15016
15017             // find the end of the last word
15018             while (i && src[i] != ' ' && src[i] != '\n')
15019             {
15020                 i--;
15021                 len--;
15022             }
15023
15024             // word too long?  restore i & len before splitting it
15025             if ((old_i-i+clen) >= width)
15026             {
15027                 i = old_i;
15028                 len = old_len;
15029             }
15030
15031             // extra space?
15032             if (i && src[i-1] == ' ')
15033                 len--;
15034
15035             if (src[i] != ' ' && src[i] != '\n')
15036             {
15037                 i--;
15038                 if (len)
15039                     len--;
15040             }
15041
15042             // now append the newline and continuation sequence
15043             if (dest)
15044                 dest[len] = '\n';
15045             len++;
15046             if (dest)
15047                 strncpy(dest+len, cseq, cseq_len);
15048             len += cseq_len;
15049             line = cseq_len;
15050             clen = cseq_len;
15051             continue;
15052         }
15053
15054         if (dest)
15055             dest[len] = src[i];
15056         len++;
15057         if (!ansi)
15058             line++;
15059         if (src[i] == '\n')
15060             line = 0;
15061         if (src[i] == 'm')
15062             ansi = 0;
15063     }
15064     if (dest && appData.debugMode)
15065     {
15066         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15067             count, width, line, len, *lp);
15068         show_bytes(debugFP, src, count);
15069         fprintf(debugFP, "\ndest: ");
15070         show_bytes(debugFP, dest, len);
15071         fprintf(debugFP, "\n");
15072     }
15073     *lp = dest ? line : old_line;
15074
15075     return len;
15076 }
15077
15078 // [HGM] vari: routines for shelving variations
15079
15080 void 
15081 PushTail(int firstMove, int lastMove)
15082 {
15083         int i, j, nrMoves = lastMove - firstMove;
15084
15085         if(appData.icsActive) { // only in local mode
15086                 forwardMostMove = currentMove; // mimic old ICS behavior
15087                 return;
15088         }
15089         if(storedGames >= MAX_VARIATIONS-1) return;
15090
15091         // push current tail of game on stack
15092         savedResult[storedGames] = gameInfo.result;
15093         savedDetails[storedGames] = gameInfo.resultDetails;
15094         gameInfo.resultDetails = NULL;
15095         savedFirst[storedGames] = firstMove;
15096         savedLast [storedGames] = lastMove;
15097         savedFramePtr[storedGames] = framePtr;
15098         framePtr -= nrMoves; // reserve space for the boards
15099         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15100             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15101             for(j=0; j<MOVE_LEN; j++)
15102                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15103             for(j=0; j<2*MOVE_LEN; j++)
15104                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15105             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15106             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15107             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15108             pvInfoList[firstMove+i-1].depth = 0;
15109             commentList[framePtr+i] = commentList[firstMove+i];
15110             commentList[firstMove+i] = NULL;
15111         }
15112
15113         storedGames++;
15114         forwardMostMove = firstMove; // truncate game so we can start variation
15115         if(storedGames == 1) GreyRevert(FALSE);
15116 }
15117
15118 Boolean
15119 PopTail(Boolean annotate)
15120 {
15121         int i, j, nrMoves;
15122         char buf[8000], moveBuf[20];
15123
15124         if(appData.icsActive) return FALSE; // only in local mode
15125         if(!storedGames) return FALSE; // sanity
15126         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15127
15128         storedGames--;
15129         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15130         nrMoves = savedLast[storedGames] - currentMove;
15131         if(annotate) {
15132                 int cnt = 10;
15133                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15134                 else strcpy(buf, "(");
15135                 for(i=currentMove; i<forwardMostMove; i++) {
15136                         if(WhiteOnMove(i))
15137                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15138                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15139                         strcat(buf, moveBuf);
15140                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15141                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15142                 }
15143                 strcat(buf, ")");
15144         }
15145         for(i=1; i<=nrMoves; i++) { // copy last variation back
15146             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15147             for(j=0; j<MOVE_LEN; j++)
15148                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15149             for(j=0; j<2*MOVE_LEN; j++)
15150                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15151             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15152             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15153             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15154             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15155             commentList[currentMove+i] = commentList[framePtr+i];
15156             commentList[framePtr+i] = NULL;
15157         }
15158         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15159         framePtr = savedFramePtr[storedGames];
15160         gameInfo.result = savedResult[storedGames];
15161         if(gameInfo.resultDetails != NULL) {
15162             free(gameInfo.resultDetails);
15163       }
15164         gameInfo.resultDetails = savedDetails[storedGames];
15165         forwardMostMove = currentMove + nrMoves;
15166         if(storedGames == 0) GreyRevert(TRUE);
15167         return TRUE;
15168 }
15169
15170 void 
15171 CleanupTail()
15172 {       // remove all shelved variations
15173         int i;
15174         for(i=0; i<storedGames; i++) {
15175             if(savedDetails[i])
15176                 free(savedDetails[i]);
15177             savedDetails[i] = NULL;
15178         }
15179         for(i=framePtr; i<MAX_MOVES; i++) {
15180                 if(commentList[i]) free(commentList[i]);
15181                 commentList[i] = NULL;
15182         }
15183         framePtr = MAX_MOVES-1;
15184         storedGames = 0;
15185 }
15186
15187 void
15188 LoadVariation(int index, char *text)
15189 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15190         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15191         int level = 0, move;
15192
15193         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15194         // first find outermost bracketing variation
15195         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15196             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15197                 if(*p == '{') wait = '}'; else
15198                 if(*p == '[') wait = ']'; else
15199                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15200                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15201             }
15202             if(*p == wait) wait = NULLCHAR; // closing ]} found
15203             p++;
15204         }
15205         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15206         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15207         end[1] = NULLCHAR; // clip off comment beyond variation
15208         ToNrEvent(currentMove-1);
15209         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15210         // kludge: use ParsePV() to append variation to game
15211         move = currentMove;
15212         ParsePV(start, TRUE);
15213         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15214         ClearPremoveHighlights();
15215         CommentPopDown();
15216         ToNrEvent(currentMove+1);
15217 }
15218