Fix e.p. bug in xiangqi with -serverMoves option
[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 EscapeExpand(char *p, char *q)
1355 {       // [HGM] initstring: routine to shape up string arguments
1356         while(*p++ = *q++) if(p[-1] == '\\')
1357             switch(*q++) {
1358                 case 'n': p[-1] = '\n'; break;
1359                 case 'r': p[-1] = '\r'; break;
1360                 case 't': p[-1] = '\t'; break;
1361                 case '\\': p[-1] = '\\'; break;
1362                 case 0: *p = 0; return;
1363                 default: p[-1] = q[-1]; break;
1364             }
1365 }
1366
1367 void
1368 show_bytes(fp, buf, count)
1369      FILE *fp;
1370      char *buf;
1371      int count;
1372 {
1373     while (count--) {
1374         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1375             fprintf(fp, "\\%03o", *buf & 0xff);
1376         } else {
1377             putc(*buf, fp);
1378         }
1379         buf++;
1380     }
1381     fflush(fp);
1382 }
1383
1384 /* Returns an errno value */
1385 int
1386 OutputMaybeTelnet(pr, message, count, outError)
1387      ProcRef pr;
1388      char *message;
1389      int count;
1390      int *outError;
1391 {
1392     char buf[8192], *p, *q, *buflim;
1393     int left, newcount, outcount;
1394
1395     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1396         *appData.gateway != NULLCHAR) {
1397         if (appData.debugMode) {
1398             fprintf(debugFP, ">ICS: ");
1399             show_bytes(debugFP, message, count);
1400             fprintf(debugFP, "\n");
1401         }
1402         return OutputToProcess(pr, message, count, outError);
1403     }
1404
1405     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1406     p = message;
1407     q = buf;
1408     left = count;
1409     newcount = 0;
1410     while (left) {
1411         if (q >= buflim) {
1412             if (appData.debugMode) {
1413                 fprintf(debugFP, ">ICS: ");
1414                 show_bytes(debugFP, buf, newcount);
1415                 fprintf(debugFP, "\n");
1416             }
1417             outcount = OutputToProcess(pr, buf, newcount, outError);
1418             if (outcount < newcount) return -1; /* to be sure */
1419             q = buf;
1420             newcount = 0;
1421         }
1422         if (*p == '\n') {
1423             *q++ = '\r';
1424             newcount++;
1425         } else if (((unsigned char) *p) == TN_IAC) {
1426             *q++ = (char) TN_IAC;
1427             newcount ++;
1428         }
1429         *q++ = *p++;
1430         newcount++;
1431         left--;
1432     }
1433     if (appData.debugMode) {
1434         fprintf(debugFP, ">ICS: ");
1435         show_bytes(debugFP, buf, newcount);
1436         fprintf(debugFP, "\n");
1437     }
1438     outcount = OutputToProcess(pr, buf, newcount, outError);
1439     if (outcount < newcount) return -1; /* to be sure */
1440     return count;
1441 }
1442
1443 void
1444 read_from_player(isr, closure, message, count, error)
1445      InputSourceRef isr;
1446      VOIDSTAR closure;
1447      char *message;
1448      int count;
1449      int error;
1450 {
1451     int outError, outCount;
1452     static int gotEof = 0;
1453
1454     /* Pass data read from player on to ICS */
1455     if (count > 0) {
1456         gotEof = 0;
1457         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1458         if (outCount < count) {
1459             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460         }
1461     } else if (count < 0) {
1462         RemoveInputSource(isr);
1463         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1464     } else if (gotEof++ > 0) {
1465         RemoveInputSource(isr);
1466         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1467     }
1468 }
1469
1470 void
1471 KeepAlive()
1472 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1473     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1474     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1475     SendToICS("date\n");
1476     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1477 }
1478
1479 /* added routine for printf style output to ics */
1480 void ics_printf(char *format, ...)
1481 {
1482     char buffer[MSG_SIZ];
1483     va_list args;
1484
1485     va_start(args, format);
1486     vsnprintf(buffer, sizeof(buffer), format, args);
1487     buffer[sizeof(buffer)-1] = '\0';
1488     SendToICS(buffer);
1489     va_end(args);
1490 }
1491
1492 void
1493 SendToICS(s)
1494      char *s;
1495 {
1496     int count, outCount, outError;
1497
1498     if (icsPR == NULL) return;
1499
1500     count = strlen(s);
1501     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507 /* This is used for sending logon scripts to the ICS. Sending
1508    without a delay causes problems when using timestamp on ICC
1509    (at least on my machine). */
1510 void
1511 SendToICSDelayed(s,msdelay)
1512      char *s;
1513      long msdelay;
1514 {
1515     int count, outCount, outError;
1516
1517     if (icsPR == NULL) return;
1518
1519     count = strlen(s);
1520     if (appData.debugMode) {
1521         fprintf(debugFP, ">ICS: ");
1522         show_bytes(debugFP, s, count);
1523         fprintf(debugFP, "\n");
1524     }
1525     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1526                                       msdelay);
1527     if (outCount < count) {
1528         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1529     }
1530 }
1531
1532
1533 /* Remove all highlighting escape sequences in s
1534    Also deletes any suffix starting with '(' 
1535    */
1536 char *
1537 StripHighlightAndTitle(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             if (*s == '(' || *s == '[') {
1550                 *p = NULLCHAR;
1551                 return retbuf;
1552             }
1553             *p++ = *s++;
1554         }
1555     }
1556     *p = NULLCHAR;
1557     return retbuf;
1558 }
1559
1560 /* Remove all highlighting escape sequences in s */
1561 char *
1562 StripHighlight(s)
1563      char *s;
1564 {
1565     static char retbuf[MSG_SIZ];
1566     char *p = retbuf;
1567
1568     while (*s != NULLCHAR) {
1569         while (*s == '\033') {
1570             while (*s != NULLCHAR && !isalpha(*s)) s++;
1571             if (*s != NULLCHAR) s++;
1572         }
1573         while (*s != NULLCHAR && *s != '\033') {
1574             *p++ = *s++;
1575         }
1576     }
1577     *p = NULLCHAR;
1578     return retbuf;
1579 }
1580
1581 char *variantNames[] = VARIANT_NAMES;
1582 char *
1583 VariantName(v)
1584      VariantClass v;
1585 {
1586     return variantNames[v];
1587 }
1588
1589
1590 /* Identify a variant from the strings the chess servers use or the
1591    PGN Variant tag names we use. */
1592 VariantClass
1593 StringToVariant(e)
1594      char *e;
1595 {
1596     char *p;
1597     int wnum = -1;
1598     VariantClass v = VariantNormal;
1599     int i, found = FALSE;
1600     char buf[MSG_SIZ];
1601
1602     if (!e) return v;
1603
1604     /* [HGM] skip over optional board-size prefixes */
1605     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1606         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1607         while( *e++ != '_');
1608     }
1609
1610     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1611         v = VariantNormal;
1612         found = TRUE;
1613     } else
1614     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1615       if (StrCaseStr(e, variantNames[i])) {
1616         v = (VariantClass) i;
1617         found = TRUE;
1618         break;
1619       }
1620     }
1621
1622     if (!found) {
1623       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1624           || StrCaseStr(e, "wild/fr") 
1625           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1626         v = VariantFischeRandom;
1627       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1628                  (i = 1, p = StrCaseStr(e, "w"))) {
1629         p += i;
1630         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1631         if (isdigit(*p)) {
1632           wnum = atoi(p);
1633         } else {
1634           wnum = -1;
1635         }
1636         switch (wnum) {
1637         case 0: /* FICS only, actually */
1638         case 1:
1639           /* Castling legal even if K starts on d-file */
1640           v = VariantWildCastle;
1641           break;
1642         case 2:
1643         case 3:
1644         case 4:
1645           /* Castling illegal even if K & R happen to start in
1646              normal positions. */
1647           v = VariantNoCastle;
1648           break;
1649         case 5:
1650         case 7:
1651         case 8:
1652         case 10:
1653         case 11:
1654         case 12:
1655         case 13:
1656         case 14:
1657         case 15:
1658         case 18:
1659         case 19:
1660           /* Castling legal iff K & R start in normal positions */
1661           v = VariantNormal;
1662           break;
1663         case 6:
1664         case 20:
1665         case 21:
1666           /* Special wilds for position setup; unclear what to do here */
1667           v = VariantLoadable;
1668           break;
1669         case 9:
1670           /* Bizarre ICC game */
1671           v = VariantTwoKings;
1672           break;
1673         case 16:
1674           v = VariantKriegspiel;
1675           break;
1676         case 17:
1677           v = VariantLosers;
1678           break;
1679         case 22:
1680           v = VariantFischeRandom;
1681           break;
1682         case 23:
1683           v = VariantCrazyhouse;
1684           break;
1685         case 24:
1686           v = VariantBughouse;
1687           break;
1688         case 25:
1689           v = Variant3Check;
1690           break;
1691         case 26:
1692           /* Not quite the same as FICS suicide! */
1693           v = VariantGiveaway;
1694           break;
1695         case 27:
1696           v = VariantAtomic;
1697           break;
1698         case 28:
1699           v = VariantShatranj;
1700           break;
1701
1702         /* Temporary names for future ICC types.  The name *will* change in 
1703            the next xboard/WinBoard release after ICC defines it. */
1704         case 29:
1705           v = Variant29;
1706           break;
1707         case 30:
1708           v = Variant30;
1709           break;
1710         case 31:
1711           v = Variant31;
1712           break;
1713         case 32:
1714           v = Variant32;
1715           break;
1716         case 33:
1717           v = Variant33;
1718           break;
1719         case 34:
1720           v = Variant34;
1721           break;
1722         case 35:
1723           v = Variant35;
1724           break;
1725         case 36:
1726           v = Variant36;
1727           break;
1728         case 37:
1729           v = VariantShogi;
1730           break;
1731         case 38:
1732           v = VariantXiangqi;
1733           break;
1734         case 39:
1735           v = VariantCourier;
1736           break;
1737         case 40:
1738           v = VariantGothic;
1739           break;
1740         case 41:
1741           v = VariantCapablanca;
1742           break;
1743         case 42:
1744           v = VariantKnightmate;
1745           break;
1746         case 43:
1747           v = VariantFairy;
1748           break;
1749         case 44:
1750           v = VariantCylinder;
1751           break;
1752         case 45:
1753           v = VariantFalcon;
1754           break;
1755         case 46:
1756           v = VariantCapaRandom;
1757           break;
1758         case 47:
1759           v = VariantBerolina;
1760           break;
1761         case 48:
1762           v = VariantJanus;
1763           break;
1764         case 49:
1765           v = VariantSuper;
1766           break;
1767         case 50:
1768           v = VariantGreat;
1769           break;
1770         case -1:
1771           /* Found "wild" or "w" in the string but no number;
1772              must assume it's normal chess. */
1773           v = VariantNormal;
1774           break;
1775         default:
1776           sprintf(buf, _("Unknown wild type %d"), wnum);
1777           DisplayError(buf, 0);
1778           v = VariantUnknown;
1779           break;
1780         }
1781       }
1782     }
1783     if (appData.debugMode) {
1784       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1785               e, wnum, VariantName(v));
1786     }
1787     return v;
1788 }
1789
1790 static int leftover_start = 0, leftover_len = 0;
1791 char star_match[STAR_MATCH_N][MSG_SIZ];
1792
1793 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1794    advance *index beyond it, and set leftover_start to the new value of
1795    *index; else return FALSE.  If pattern contains the character '*', it
1796    matches any sequence of characters not containing '\r', '\n', or the
1797    character following the '*' (if any), and the matched sequence(s) are
1798    copied into star_match.
1799    */
1800 int
1801 looking_at(buf, index, pattern)
1802      char *buf;
1803      int *index;
1804      char *pattern;
1805 {
1806     char *bufp = &buf[*index], *patternp = pattern;
1807     int star_count = 0;
1808     char *matchp = star_match[0];
1809     
1810     for (;;) {
1811         if (*patternp == NULLCHAR) {
1812             *index = leftover_start = bufp - buf;
1813             *matchp = NULLCHAR;
1814             return TRUE;
1815         }
1816         if (*bufp == NULLCHAR) return FALSE;
1817         if (*patternp == '*') {
1818             if (*bufp == *(patternp + 1)) {
1819                 *matchp = NULLCHAR;
1820                 matchp = star_match[++star_count];
1821                 patternp += 2;
1822                 bufp++;
1823                 continue;
1824             } else if (*bufp == '\n' || *bufp == '\r') {
1825                 patternp++;
1826                 if (*patternp == NULLCHAR)
1827                   continue;
1828                 else
1829                   return FALSE;
1830             } else {
1831                 *matchp++ = *bufp++;
1832                 continue;
1833             }
1834         }
1835         if (*patternp != *bufp) return FALSE;
1836         patternp++;
1837         bufp++;
1838     }
1839 }
1840
1841 void
1842 SendToPlayer(data, length)
1843      char *data;
1844      int length;
1845 {
1846     int error, outCount;
1847     outCount = OutputToProcess(NoProc, data, length, &error);
1848     if (outCount < length) {
1849         DisplayFatalError(_("Error writing to display"), error, 1);
1850     }
1851 }
1852
1853 void
1854 PackHolding(packed, holding)
1855      char packed[];
1856      char *holding;
1857 {
1858     char *p = holding;
1859     char *q = packed;
1860     int runlength = 0;
1861     int curr = 9999;
1862     do {
1863         if (*p == curr) {
1864             runlength++;
1865         } else {
1866             switch (runlength) {
1867               case 0:
1868                 break;
1869               case 1:
1870                 *q++ = curr;
1871                 break;
1872               case 2:
1873                 *q++ = curr;
1874                 *q++ = curr;
1875                 break;
1876               default:
1877                 sprintf(q, "%d", runlength);
1878                 while (*q) q++;
1879                 *q++ = curr;
1880                 break;
1881             }
1882             runlength = 1;
1883             curr = *p;
1884         }
1885     } while (*p++);
1886     *q = NULLCHAR;
1887 }
1888
1889 /* Telnet protocol requests from the front end */
1890 void
1891 TelnetRequest(ddww, option)
1892      unsigned char ddww, option;
1893 {
1894     unsigned char msg[3];
1895     int outCount, outError;
1896
1897     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1898
1899     if (appData.debugMode) {
1900         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1901         switch (ddww) {
1902           case TN_DO:
1903             ddwwStr = "DO";
1904             break;
1905           case TN_DONT:
1906             ddwwStr = "DONT";
1907             break;
1908           case TN_WILL:
1909             ddwwStr = "WILL";
1910             break;
1911           case TN_WONT:
1912             ddwwStr = "WONT";
1913             break;
1914           default:
1915             ddwwStr = buf1;
1916             sprintf(buf1, "%d", ddww);
1917             break;
1918         }
1919         switch (option) {
1920           case TN_ECHO:
1921             optionStr = "ECHO";
1922             break;
1923           default:
1924             optionStr = buf2;
1925             sprintf(buf2, "%d", option);
1926             break;
1927         }
1928         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1929     }
1930     msg[0] = TN_IAC;
1931     msg[1] = ddww;
1932     msg[2] = option;
1933     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1934     if (outCount < 3) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 void
1940 DoEcho()
1941 {
1942     if (!appData.icsActive) return;
1943     TelnetRequest(TN_DO, TN_ECHO);
1944 }
1945
1946 void
1947 DontEcho()
1948 {
1949     if (!appData.icsActive) return;
1950     TelnetRequest(TN_DONT, TN_ECHO);
1951 }
1952
1953 void
1954 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1955 {
1956     /* put the holdings sent to us by the server on the board holdings area */
1957     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1958     char p;
1959     ChessSquare piece;
1960
1961     if(gameInfo.holdingsWidth < 2)  return;
1962     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1963         return; // prevent overwriting by pre-board holdings
1964
1965     if( (int)lowestPiece >= BlackPawn ) {
1966         holdingsColumn = 0;
1967         countsColumn = 1;
1968         holdingsStartRow = BOARD_HEIGHT-1;
1969         direction = -1;
1970     } else {
1971         holdingsColumn = BOARD_WIDTH-1;
1972         countsColumn = BOARD_WIDTH-2;
1973         holdingsStartRow = 0;
1974         direction = 1;
1975     }
1976
1977     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1978         board[i][holdingsColumn] = EmptySquare;
1979         board[i][countsColumn]   = (ChessSquare) 0;
1980     }
1981     while( (p=*holdings++) != NULLCHAR ) {
1982         piece = CharToPiece( ToUpper(p) );
1983         if(piece == EmptySquare) continue;
1984         /*j = (int) piece - (int) WhitePawn;*/
1985         j = PieceToNumber(piece);
1986         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1987         if(j < 0) continue;               /* should not happen */
1988         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1989         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1990         board[holdingsStartRow+j*direction][countsColumn]++;
1991     }
1992 }
1993
1994
1995 void
1996 VariantSwitch(Board board, VariantClass newVariant)
1997 {
1998    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1999    static Board oldBoard;
2000
2001    startedFromPositionFile = FALSE;
2002    if(gameInfo.variant == newVariant) return;
2003
2004    /* [HGM] This routine is called each time an assignment is made to
2005     * gameInfo.variant during a game, to make sure the board sizes
2006     * are set to match the new variant. If that means adding or deleting
2007     * holdings, we shift the playing board accordingly
2008     * This kludge is needed because in ICS observe mode, we get boards
2009     * of an ongoing game without knowing the variant, and learn about the
2010     * latter only later. This can be because of the move list we requested,
2011     * in which case the game history is refilled from the beginning anyway,
2012     * but also when receiving holdings of a crazyhouse game. In the latter
2013     * case we want to add those holdings to the already received position.
2014     */
2015
2016    
2017    if (appData.debugMode) {
2018      fprintf(debugFP, "Switch board from %s to %s\n",
2019              VariantName(gameInfo.variant), VariantName(newVariant));
2020      setbuf(debugFP, NULL);
2021    }
2022    shuffleOpenings = 0;       /* [HGM] shuffle */
2023    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2024    switch(newVariant) 
2025      {
2026      case VariantShogi:
2027        newWidth = 9;  newHeight = 9;
2028        gameInfo.holdingsSize = 7;
2029      case VariantBughouse:
2030      case VariantCrazyhouse:
2031        newHoldingsWidth = 2; break;
2032      case VariantGreat:
2033        newWidth = 10;
2034      case VariantSuper:
2035        newHoldingsWidth = 2;
2036        gameInfo.holdingsSize = 8;
2037        break;
2038      case VariantGothic:
2039      case VariantCapablanca:
2040      case VariantCapaRandom:
2041        newWidth = 10;
2042      default:
2043        newHoldingsWidth = gameInfo.holdingsSize = 0;
2044      };
2045    
2046    if(newWidth  != gameInfo.boardWidth  ||
2047       newHeight != gameInfo.boardHeight ||
2048       newHoldingsWidth != gameInfo.holdingsWidth ) {
2049      
2050      /* shift position to new playing area, if needed */
2051      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2052        for(i=0; i<BOARD_HEIGHT; i++) 
2053          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2054            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2055              board[i][j];
2056        for(i=0; i<newHeight; i++) {
2057          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2058          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2059        }
2060      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++)
2062          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065      }
2066      gameInfo.boardWidth  = newWidth;
2067      gameInfo.boardHeight = newHeight;
2068      gameInfo.holdingsWidth = newHoldingsWidth;
2069      gameInfo.variant = newVariant;
2070      InitDrawingSizes(-2, 0);
2071    } else gameInfo.variant = newVariant;
2072    CopyBoard(oldBoard, board);   // remember correctly formatted board
2073      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2074    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2075 }
2076
2077 static int loggedOn = FALSE;
2078
2079 /*-- Game start info cache: --*/
2080 int gs_gamenum;
2081 char gs_kind[MSG_SIZ];
2082 static char player1Name[128] = "";
2083 static char player2Name[128] = "";
2084 static char cont_seq[] = "\n\\   ";
2085 static int player1Rating = -1;
2086 static int player2Rating = -1;
2087 /*----------------------------*/
2088
2089 ColorClass curColor = ColorNormal;
2090 int suppressKibitz = 0;
2091
2092 // [HGM] seekgraph
2093 Boolean soughtPending = FALSE;
2094 Boolean seekGraphUp;
2095 #define MAX_SEEK_ADS 200
2096 #define SQUARE 0x80
2097 char *seekAdList[MAX_SEEK_ADS];
2098 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2099 float tcList[MAX_SEEK_ADS];
2100 char colorList[MAX_SEEK_ADS];
2101 int nrOfSeekAds = 0;
2102 int minRating = 1010, maxRating = 2800;
2103 int hMargin = 10, vMargin = 20, h, w;
2104 extern int squareSize, lineGap;
2105
2106 void
2107 PlotSeekAd(int i)
2108 {
2109         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2110         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2111         if(r < minRating+100 && r >=0 ) r = minRating+100;
2112         if(r > maxRating) r = maxRating;
2113         if(tc < 1.) tc = 1.;
2114         if(tc > 95.) tc = 95.;
2115         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2116         y = ((double)r - minRating)/(maxRating - minRating)
2117             * (h-vMargin-squareSize/8-1) + vMargin;
2118         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2119         if(strstr(seekAdList[i], " u ")) color = 1;
2120         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2121            !strstr(seekAdList[i], "bullet") &&
2122            !strstr(seekAdList[i], "blitz") &&
2123            !strstr(seekAdList[i], "standard") ) color = 2;
2124         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2125         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2126 }
2127
2128 void
2129 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2130 {
2131         char buf[MSG_SIZ], *ext = "";
2132         VariantClass v = StringToVariant(type);
2133         if(strstr(type, "wild")) {
2134             ext = type + 4; // append wild number
2135             if(v == VariantFischeRandom) type = "chess960"; else
2136             if(v == VariantLoadable) type = "setup"; else
2137             type = VariantName(v);
2138         }
2139         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2140         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2141             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2142             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2143             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2144             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2145             seekNrList[nrOfSeekAds] = nr;
2146             zList[nrOfSeekAds] = 0;
2147             seekAdList[nrOfSeekAds++] = StrSave(buf);
2148             if(plot) PlotSeekAd(nrOfSeekAds-1);
2149         }
2150 }
2151
2152 void
2153 EraseSeekDot(int i)
2154 {
2155     int x = xList[i], y = yList[i], d=squareSize/4, k;
2156     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2157     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2158     // now replot every dot that overlapped
2159     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2160         int xx = xList[k], yy = yList[k];
2161         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2162             DrawSeekDot(xx, yy, colorList[k]);
2163     }
2164 }
2165
2166 void
2167 RemoveSeekAd(int nr)
2168 {
2169         int i;
2170         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2171             EraseSeekDot(i);
2172             if(seekAdList[i]) free(seekAdList[i]);
2173             seekAdList[i] = seekAdList[--nrOfSeekAds];
2174             seekNrList[i] = seekNrList[nrOfSeekAds];
2175             ratingList[i] = ratingList[nrOfSeekAds];
2176             colorList[i]  = colorList[nrOfSeekAds];
2177             tcList[i] = tcList[nrOfSeekAds];
2178             xList[i]  = xList[nrOfSeekAds];
2179             yList[i]  = yList[nrOfSeekAds];
2180             zList[i]  = zList[nrOfSeekAds];
2181             seekAdList[nrOfSeekAds] = NULL;
2182             break;
2183         }
2184 }
2185
2186 Boolean
2187 MatchSoughtLine(char *line)
2188 {
2189     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2190     int nr, base, inc, u=0; char dummy;
2191
2192     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2193        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2194        (u=1) &&
2195        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2196         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2197         // match: compact and save the line
2198         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2199         return TRUE;
2200     }
2201     return FALSE;
2202 }
2203
2204 int
2205 DrawSeekGraph()
2206 {
2207     if(!seekGraphUp) return FALSE;
2208     int i;
2209     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2210     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2211
2212     DrawSeekBackground(0, 0, w, h);
2213     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2214     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2215     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2216         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2217         yy = h-1-yy;
2218         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2219         if(i%500 == 0) {
2220             char buf[MSG_SIZ];
2221             sprintf(buf, "%d", i);
2222             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2223         }
2224     }
2225     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2226     for(i=1; i<100; i+=(i<10?1:5)) {
2227         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2228         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2229         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2230             char buf[MSG_SIZ];
2231             sprintf(buf, "%d", i);
2232             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2233         }
2234     }
2235     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2236     return TRUE;
2237 }
2238
2239 int SeekGraphClick(ClickType click, int x, int y, int moving)
2240 {
2241     static int lastDown = 0, displayed = 0, lastSecond;
2242     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2243         if(click == Release || moving) return FALSE;
2244         nrOfSeekAds = 0;
2245         soughtPending = TRUE;
2246         SendToICS(ics_prefix);
2247         SendToICS("sought\n"); // should this be "sought all"?
2248     } else { // issue challenge based on clicked ad
2249         int dist = 10000; int i, closest = 0, second = 0;
2250         for(i=0; i<nrOfSeekAds; i++) {
2251             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2252             if(d < dist) { dist = d; closest = i; }
2253             second += (d - zList[i] < 120); // count in-range ads
2254             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2255         }
2256         if(dist < 120) {
2257             char buf[MSG_SIZ];
2258             second = (second > 1);
2259             if(displayed != closest || second != lastSecond) {
2260                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2261                 lastSecond = second; displayed = closest;
2262             }
2263             if(click == Press) {
2264                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2265                 lastDown = closest;
2266                 return TRUE;
2267             } // on press 'hit', only show info
2268             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2269             sprintf(buf, "play %d\n", seekNrList[closest]);
2270             SendToICS(ics_prefix);
2271             SendToICS(buf);
2272             return TRUE; // let incoming board of started game pop down the graph
2273         } else if(click == Release) { // release 'miss' is ignored
2274             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2275             if(moving == 2) { // right up-click
2276                 nrOfSeekAds = 0; // refresh graph
2277                 soughtPending = TRUE;
2278                 SendToICS(ics_prefix);
2279                 SendToICS("sought\n"); // should this be "sought all"?
2280             }
2281             return TRUE;
2282         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2283         // press miss or release hit 'pop down' seek graph
2284         seekGraphUp = FALSE;
2285         DrawPosition(TRUE, NULL);
2286     }
2287     return TRUE;
2288 }
2289
2290 void
2291 read_from_ics(isr, closure, data, count, error)
2292      InputSourceRef isr;
2293      VOIDSTAR closure;
2294      char *data;
2295      int count;
2296      int error;
2297 {
2298 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2299 #define STARTED_NONE 0
2300 #define STARTED_MOVES 1
2301 #define STARTED_BOARD 2
2302 #define STARTED_OBSERVE 3
2303 #define STARTED_HOLDINGS 4
2304 #define STARTED_CHATTER 5
2305 #define STARTED_COMMENT 6
2306 #define STARTED_MOVES_NOHIDE 7
2307     
2308     static int started = STARTED_NONE;
2309     static char parse[20000];
2310     static int parse_pos = 0;
2311     static char buf[BUF_SIZE + 1];
2312     static int firstTime = TRUE, intfSet = FALSE;
2313     static ColorClass prevColor = ColorNormal;
2314     static int savingComment = FALSE;
2315     static int cmatch = 0; // continuation sequence match
2316     char *bp;
2317     char str[500];
2318     int i, oldi;
2319     int buf_len;
2320     int next_out;
2321     int tkind;
2322     int backup;    /* [DM] For zippy color lines */
2323     char *p;
2324     char talker[MSG_SIZ]; // [HGM] chat
2325     int channel;
2326
2327     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2328
2329     if (appData.debugMode) {
2330       if (!error) {
2331         fprintf(debugFP, "<ICS: ");
2332         show_bytes(debugFP, data, count);
2333         fprintf(debugFP, "\n");
2334       }
2335     }
2336
2337     if (appData.debugMode) { int f = forwardMostMove;
2338         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2339                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2340                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2341     }
2342     if (count > 0) {
2343         /* If last read ended with a partial line that we couldn't parse,
2344            prepend it to the new read and try again. */
2345         if (leftover_len > 0) {
2346             for (i=0; i<leftover_len; i++)
2347               buf[i] = buf[leftover_start + i];
2348         }
2349
2350     /* copy new characters into the buffer */
2351     bp = buf + leftover_len;
2352     buf_len=leftover_len;
2353     for (i=0; i<count; i++)
2354     {
2355         // ignore these
2356         if (data[i] == '\r')
2357             continue;
2358
2359         // join lines split by ICS?
2360         if (!appData.noJoin)
2361         {
2362             /*
2363                 Joining just consists of finding matches against the
2364                 continuation sequence, and discarding that sequence
2365                 if found instead of copying it.  So, until a match
2366                 fails, there's nothing to do since it might be the
2367                 complete sequence, and thus, something we don't want
2368                 copied.
2369             */
2370             if (data[i] == cont_seq[cmatch])
2371             {
2372                 cmatch++;
2373                 if (cmatch == strlen(cont_seq))
2374                 {
2375                     cmatch = 0; // complete match.  just reset the counter
2376
2377                     /*
2378                         it's possible for the ICS to not include the space
2379                         at the end of the last word, making our [correct]
2380                         join operation fuse two separate words.  the server
2381                         does this when the space occurs at the width setting.
2382                     */
2383                     if (!buf_len || buf[buf_len-1] != ' ')
2384                     {
2385                         *bp++ = ' ';
2386                         buf_len++;
2387                     }
2388                 }
2389                 continue;
2390             }
2391             else if (cmatch)
2392             {
2393                 /*
2394                     match failed, so we have to copy what matched before
2395                     falling through and copying this character.  In reality,
2396                     this will only ever be just the newline character, but
2397                     it doesn't hurt to be precise.
2398                 */
2399                 strncpy(bp, cont_seq, cmatch);
2400                 bp += cmatch;
2401                 buf_len += cmatch;
2402                 cmatch = 0;
2403             }
2404         }
2405
2406         // copy this char
2407         *bp++ = data[i];
2408         buf_len++;
2409     }
2410
2411         buf[buf_len] = NULLCHAR;
2412 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2413         next_out = 0;
2414         leftover_start = 0;
2415         
2416         i = 0;
2417         while (i < buf_len) {
2418             /* Deal with part of the TELNET option negotiation
2419                protocol.  We refuse to do anything beyond the
2420                defaults, except that we allow the WILL ECHO option,
2421                which ICS uses to turn off password echoing when we are
2422                directly connected to it.  We reject this option
2423                if localLineEditing mode is on (always on in xboard)
2424                and we are talking to port 23, which might be a real
2425                telnet server that will try to keep WILL ECHO on permanently.
2426              */
2427             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2428                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2429                 unsigned char option;
2430                 oldi = i;
2431                 switch ((unsigned char) buf[++i]) {
2432                   case TN_WILL:
2433                     if (appData.debugMode)
2434                       fprintf(debugFP, "\n<WILL ");
2435                     switch (option = (unsigned char) buf[++i]) {
2436                       case TN_ECHO:
2437                         if (appData.debugMode)
2438                           fprintf(debugFP, "ECHO ");
2439                         /* Reply only if this is a change, according
2440                            to the protocol rules. */
2441                         if (remoteEchoOption) break;
2442                         if (appData.localLineEditing &&
2443                             atoi(appData.icsPort) == TN_PORT) {
2444                             TelnetRequest(TN_DONT, TN_ECHO);
2445                         } else {
2446                             EchoOff();
2447                             TelnetRequest(TN_DO, TN_ECHO);
2448                             remoteEchoOption = TRUE;
2449                         }
2450                         break;
2451                       default:
2452                         if (appData.debugMode)
2453                           fprintf(debugFP, "%d ", option);
2454                         /* Whatever this is, we don't want it. */
2455                         TelnetRequest(TN_DONT, option);
2456                         break;
2457                     }
2458                     break;
2459                   case TN_WONT:
2460                     if (appData.debugMode)
2461                       fprintf(debugFP, "\n<WONT ");
2462                     switch (option = (unsigned char) buf[++i]) {
2463                       case TN_ECHO:
2464                         if (appData.debugMode)
2465                           fprintf(debugFP, "ECHO ");
2466                         /* Reply only if this is a change, according
2467                            to the protocol rules. */
2468                         if (!remoteEchoOption) break;
2469                         EchoOn();
2470                         TelnetRequest(TN_DONT, TN_ECHO);
2471                         remoteEchoOption = FALSE;
2472                         break;
2473                       default:
2474                         if (appData.debugMode)
2475                           fprintf(debugFP, "%d ", (unsigned char) option);
2476                         /* Whatever this is, it must already be turned
2477                            off, because we never agree to turn on
2478                            anything non-default, so according to the
2479                            protocol rules, we don't reply. */
2480                         break;
2481                     }
2482                     break;
2483                   case TN_DO:
2484                     if (appData.debugMode)
2485                       fprintf(debugFP, "\n<DO ");
2486                     switch (option = (unsigned char) buf[++i]) {
2487                       default:
2488                         /* Whatever this is, we refuse to do it. */
2489                         if (appData.debugMode)
2490                           fprintf(debugFP, "%d ", option);
2491                         TelnetRequest(TN_WONT, option);
2492                         break;
2493                     }
2494                     break;
2495                   case TN_DONT:
2496                     if (appData.debugMode)
2497                       fprintf(debugFP, "\n<DONT ");
2498                     switch (option = (unsigned char) buf[++i]) {
2499                       default:
2500                         if (appData.debugMode)
2501                           fprintf(debugFP, "%d ", option);
2502                         /* Whatever this is, we are already not doing
2503                            it, because we never agree to do anything
2504                            non-default, so according to the protocol
2505                            rules, we don't reply. */
2506                         break;
2507                     }
2508                     break;
2509                   case TN_IAC:
2510                     if (appData.debugMode)
2511                       fprintf(debugFP, "\n<IAC ");
2512                     /* Doubled IAC; pass it through */
2513                     i--;
2514                     break;
2515                   default:
2516                     if (appData.debugMode)
2517                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2518                     /* Drop all other telnet commands on the floor */
2519                     break;
2520                 }
2521                 if (oldi > next_out)
2522                   SendToPlayer(&buf[next_out], oldi - next_out);
2523                 if (++i > next_out)
2524                   next_out = i;
2525                 continue;
2526             }
2527                 
2528             /* OK, this at least will *usually* work */
2529             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2530                 loggedOn = TRUE;
2531             }
2532             
2533             if (loggedOn && !intfSet) {
2534                 if (ics_type == ICS_ICC) {
2535                   sprintf(str,
2536                           "/set-quietly interface %s\n/set-quietly style 12\n",
2537                           programVersion);
2538                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2539                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2540                 } else if (ics_type == ICS_CHESSNET) {
2541                   sprintf(str, "/style 12\n");
2542                 } else {
2543                   strcpy(str, "alias $ @\n$set interface ");
2544                   strcat(str, programVersion);
2545                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2546                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2547                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2548 #ifdef WIN32
2549                   strcat(str, "$iset nohighlight 1\n");
2550 #endif
2551                   strcat(str, "$iset lock 1\n$style 12\n");
2552                 }
2553                 SendToICS(str);
2554                 NotifyFrontendLogin();
2555                 intfSet = TRUE;
2556             }
2557
2558             if (started == STARTED_COMMENT) {
2559                 /* Accumulate characters in comment */
2560                 parse[parse_pos++] = buf[i];
2561                 if (buf[i] == '\n') {
2562                     parse[parse_pos] = NULLCHAR;
2563                     if(chattingPartner>=0) {
2564                         char mess[MSG_SIZ];
2565                         sprintf(mess, "%s%s", talker, parse);
2566                         OutputChatMessage(chattingPartner, mess);
2567                         chattingPartner = -1;
2568                         next_out = i+1; // [HGM] suppress printing in ICS window
2569                     } else
2570                     if(!suppressKibitz) // [HGM] kibitz
2571                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2572                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2573                         int nrDigit = 0, nrAlph = 0, j;
2574                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2575                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2576                         parse[parse_pos] = NULLCHAR;
2577                         // try to be smart: if it does not look like search info, it should go to
2578                         // ICS interaction window after all, not to engine-output window.
2579                         for(j=0; j<parse_pos; j++) { // count letters and digits
2580                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2581                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2582                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2583                         }
2584                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2585                             int depth=0; float score;
2586                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2587                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2588                                 pvInfoList[forwardMostMove-1].depth = depth;
2589                                 pvInfoList[forwardMostMove-1].score = 100*score;
2590                             }
2591                             OutputKibitz(suppressKibitz, parse);
2592                         } else {
2593                             char tmp[MSG_SIZ];
2594                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2595                             SendToPlayer(tmp, strlen(tmp));
2596                         }
2597                         next_out = i+1; // [HGM] suppress printing in ICS window
2598                     }
2599                     started = STARTED_NONE;
2600                 } else {
2601                     /* Don't match patterns against characters in comment */
2602                     i++;
2603                     continue;
2604                 }
2605             }
2606             if (started == STARTED_CHATTER) {
2607                 if (buf[i] != '\n') {
2608                     /* Don't match patterns against characters in chatter */
2609                     i++;
2610                     continue;
2611                 }
2612                 started = STARTED_NONE;
2613                 if(suppressKibitz) next_out = i+1;
2614             }
2615
2616             /* Kludge to deal with rcmd protocol */
2617             if (firstTime && looking_at(buf, &i, "\001*")) {
2618                 DisplayFatalError(&buf[1], 0, 1);
2619                 continue;
2620             } else {
2621                 firstTime = FALSE;
2622             }
2623
2624             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2625                 ics_type = ICS_ICC;
2626                 ics_prefix = "/";
2627                 if (appData.debugMode)
2628                   fprintf(debugFP, "ics_type %d\n", ics_type);
2629                 continue;
2630             }
2631             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2632                 ics_type = ICS_FICS;
2633                 ics_prefix = "$";
2634                 if (appData.debugMode)
2635                   fprintf(debugFP, "ics_type %d\n", ics_type);
2636                 continue;
2637             }
2638             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2639                 ics_type = ICS_CHESSNET;
2640                 ics_prefix = "/";
2641                 if (appData.debugMode)
2642                   fprintf(debugFP, "ics_type %d\n", ics_type);
2643                 continue;
2644             }
2645
2646             if (!loggedOn &&
2647                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2648                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2649                  looking_at(buf, &i, "will be \"*\""))) {
2650               strcpy(ics_handle, star_match[0]);
2651               continue;
2652             }
2653
2654             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2655               char buf[MSG_SIZ];
2656               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2657               DisplayIcsInteractionTitle(buf);
2658               have_set_title = TRUE;
2659             }
2660
2661             /* skip finger notes */
2662             if (started == STARTED_NONE &&
2663                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2664                  (buf[i] == '1' && buf[i+1] == '0')) &&
2665                 buf[i+2] == ':' && buf[i+3] == ' ') {
2666               started = STARTED_CHATTER;
2667               i += 3;
2668               continue;
2669             }
2670
2671             oldi = i;
2672             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2673             if(appData.seekGraph) {
2674                 if(soughtPending && MatchSoughtLine(buf+i)) {
2675                     i = strstr(buf+i, "rated") - buf;
2676                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2677                     next_out = leftover_start = i;
2678                     started = STARTED_CHATTER;
2679                     suppressKibitz = TRUE;
2680                     continue;
2681                 }
2682                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2683                         && looking_at(buf, &i, "* ads displayed")) {
2684                     soughtPending = FALSE;
2685                     seekGraphUp = TRUE;
2686                     DrawSeekGraph();
2687                     continue;
2688                 }
2689                 if(appData.autoRefresh) {
2690                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2691                         int s = (ics_type == ICS_ICC); // ICC format differs
2692                         if(seekGraphUp)
2693                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2694                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2695                         looking_at(buf, &i, "*% "); // eat prompt
2696                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2697                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2698                         next_out = i; // suppress
2699                         continue;
2700                     }
2701                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2702                         char *p = star_match[0];
2703                         while(*p) {
2704                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2705                             while(*p && *p++ != ' '); // next
2706                         }
2707                         looking_at(buf, &i, "*% "); // eat prompt
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         continue;
2711                     }
2712                 }
2713             }
2714
2715             /* skip formula vars */
2716             if (started == STARTED_NONE &&
2717                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2718               started = STARTED_CHATTER;
2719               i += 3;
2720               continue;
2721             }
2722
2723             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2724             if (appData.autoKibitz && started == STARTED_NONE && 
2725                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2726                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2727                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2728                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2729                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2730                         suppressKibitz = TRUE;
2731                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2732                         next_out = i;
2733                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2734                                 && (gameMode == IcsPlayingWhite)) ||
2735                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2736                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2737                             started = STARTED_CHATTER; // own kibitz we simply discard
2738                         else {
2739                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2740                             parse_pos = 0; parse[0] = NULLCHAR;
2741                             savingComment = TRUE;
2742                             suppressKibitz = gameMode != IcsObserving ? 2 :
2743                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2744                         } 
2745                         continue;
2746                 } else
2747                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2748                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2749                          && atoi(star_match[0])) {
2750                     // suppress the acknowledgements of our own autoKibitz
2751                     char *p;
2752                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2754                     SendToPlayer(star_match[0], strlen(star_match[0]));
2755                     if(looking_at(buf, &i, "*% ")) // eat prompt
2756                         suppressKibitz = FALSE;
2757                     next_out = i;
2758                     continue;
2759                 }
2760             } // [HGM] kibitz: end of patch
2761
2762             // [HGM] chat: intercept tells by users for which we have an open chat window
2763             channel = -1;
2764             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2765                                            looking_at(buf, &i, "* whispers:") ||
2766                                            looking_at(buf, &i, "* kibitzes:") ||
2767                                            looking_at(buf, &i, "* shouts:") ||
2768                                            looking_at(buf, &i, "* c-shouts:") ||
2769                                            looking_at(buf, &i, "--> * ") ||
2770                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2771                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2772                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2773                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2774                 int p;
2775                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2776                 chattingPartner = -1;
2777
2778                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2779                 for(p=0; p<MAX_CHAT; p++) {
2780                     if(channel == atoi(chatPartner[p])) {
2781                     talker[0] = '['; strcat(talker, "] ");
2782                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2783                     chattingPartner = p; break;
2784                     }
2785                 } else
2786                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2787                 for(p=0; p<MAX_CHAT; p++) {
2788                     if(!strcmp("kibitzes", chatPartner[p])) {
2789                         talker[0] = '['; strcat(talker, "] ");
2790                         chattingPartner = p; break;
2791                     }
2792                 } else
2793                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2794                 for(p=0; p<MAX_CHAT; p++) {
2795                     if(!strcmp("whispers", chatPartner[p])) {
2796                         talker[0] = '['; strcat(talker, "] ");
2797                         chattingPartner = p; break;
2798                     }
2799                 } else
2800                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2801                   if(buf[i-8] == '-' && buf[i-3] == 't')
2802                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2803                     if(!strcmp("c-shouts", chatPartner[p])) {
2804                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2805                         chattingPartner = p; break;
2806                     }
2807                   }
2808                   if(chattingPartner < 0)
2809                   for(p=0; p<MAX_CHAT; p++) {
2810                     if(!strcmp("shouts", chatPartner[p])) {
2811                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2812                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2813                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0; Colorize(ColorTell, FALSE);
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2825                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2826                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2827                     started = STARTED_COMMENT;
2828                     parse_pos = 0; parse[0] = NULLCHAR;
2829                     savingComment = 3 + chattingPartner; // counts as TRUE
2830                     suppressKibitz = TRUE;
2831                     continue;
2832                 }
2833             } // [HGM] chat: end of patch
2834
2835             if (appData.zippyTalk || appData.zippyPlay) {
2836                 /* [DM] Backup address for color zippy lines */
2837                 backup = i;
2838 #if ZIPPY
2839        #ifdef WIN32
2840                if (loggedOn == TRUE)
2841                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2842                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2843        #else
2844                 if (ZippyControl(buf, &i) ||
2845                     ZippyConverse(buf, &i) ||
2846                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2847                       loggedOn = TRUE;
2848                       if (!appData.colorize) continue;
2849                 }
2850        #endif
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <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
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #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"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #ifdef ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhitePromotionQueen:
4579       case BlackPromotionQueen:
4580       case WhitePromotionRook:
4581       case BlackPromotionRook:
4582       case WhitePromotionBishop:
4583       case BlackPromotionBishop:
4584       case WhitePromotionKnight:
4585       case BlackPromotionKnight:
4586       case WhitePromotionKing:
4587       case BlackPromotionKing:
4588       case WhitePromotionChancellor:
4589       case BlackPromotionChancellor:
4590       case WhitePromotionArchbishop:
4591       case BlackPromotionArchbishop:
4592         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4593             sprintf(user_move, "%c%c%c%c=%c\n",
4594                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4595                 PieceToChar(WhiteFerz));
4596         else if(gameInfo.variant == VariantGreat)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteMan));
4600         else
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(PromoPiece(moveType)));
4604         break;
4605       case WhiteDrop:
4606       case BlackDrop:
4607         sprintf(user_move, "%c@%c%c\n",
4608                 ToUpper(PieceToChar((ChessSquare) fromX)),
4609                 AAA + toX, ONE + toY);
4610         break;
4611       case NormalMove:
4612       case WhiteCapturesEnPassant:
4613       case BlackCapturesEnPassant:
4614       case IllegalMove:  /* could be a variant we don't quite understand */
4615         sprintf(user_move, "%c%c%c%c\n",
4616                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4617         break;
4618     }
4619     SendToICS(user_move);
4620     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4621         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4622 }
4623
4624 void
4625 UploadGameEvent()
4626 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4627     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4628     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4629     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4630         DisplayError("You cannot do this while you are playing or observing", 0);
4631         return;
4632     }
4633     if(gameMode != IcsExamining) { // is this ever not the case?
4634         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4635
4636         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4637             sprintf(command, "match %s", ics_handle);
4638         } else { // on FICS we must first go to general examine mode
4639             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4640         }
4641         if(gameInfo.variant != VariantNormal) {
4642             // try figure out wild number, as xboard names are not always valid on ICS
4643             for(i=1; i<=36; i++) {
4644                 sprintf(buf, "wild/%d", i);
4645                 if(StringToVariant(buf) == gameInfo.variant) break;
4646             }
4647             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4648             else if(i == 22) sprintf(buf, "%s fr\n", command);
4649             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4650         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4651         SendToICS(ics_prefix);
4652         SendToICS(buf);
4653         if(startedFromSetupPosition || backwardMostMove != 0) {
4654           fen = PositionToFEN(backwardMostMove, NULL);
4655           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4656             sprintf(buf, "loadfen %s\n", fen);
4657             SendToICS(buf);
4658           } else { // FICS: everything has to set by separate bsetup commands
4659             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4660             sprintf(buf, "bsetup fen %s\n", fen);
4661             SendToICS(buf);
4662             if(!WhiteOnMove(backwardMostMove)) {
4663                 SendToICS("bsetup tomove black\n");
4664             }
4665             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4666             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4667             SendToICS(buf);
4668             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4669             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4670             SendToICS(buf);
4671             i = boards[backwardMostMove][EP_STATUS];
4672             if(i >= 0) { // set e.p.
4673                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4674                 SendToICS(buf);
4675             }
4676             bsetup++;
4677           }
4678         }
4679       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4680             SendToICS("bsetup done\n"); // switch to normal examining.
4681     }
4682     for(i = backwardMostMove; i<last; i++) {
4683         char buf[20];
4684         sprintf(buf, "%s\n", parseList[i]);
4685         SendToICS(buf);
4686     }
4687     SendToICS(ics_prefix);
4688     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4689 }
4690
4691 void
4692 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4693      int rf, ff, rt, ft;
4694      char promoChar;
4695      char move[7];
4696 {
4697     if (rf == DROP_RANK) {
4698         sprintf(move, "%c@%c%c\n",
4699                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4700     } else {
4701         if (promoChar == 'x' || promoChar == NULLCHAR) {
4702             sprintf(move, "%c%c%c%c\n",
4703                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4704         } else {
4705             sprintf(move, "%c%c%c%c%c\n",
4706                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4707         }
4708     }
4709 }
4710
4711 void
4712 ProcessICSInitScript(f)
4713      FILE *f;
4714 {
4715     char buf[MSG_SIZ];
4716
4717     while (fgets(buf, MSG_SIZ, f)) {
4718         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4719     }
4720
4721     fclose(f);
4722 }
4723
4724
4725 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4726 void
4727 AlphaRank(char *move, int n)
4728 {
4729 //    char *p = move, c; int x, y;
4730
4731     if (appData.debugMode) {
4732         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4733     }
4734
4735     if(move[1]=='*' && 
4736        move[2]>='0' && move[2]<='9' &&
4737        move[3]>='a' && move[3]<='x'    ) {
4738         move[1] = '@';
4739         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4740         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4741     } else
4742     if(move[0]>='0' && move[0]<='9' &&
4743        move[1]>='a' && move[1]<='x' &&
4744        move[2]>='0' && move[2]<='9' &&
4745        move[3]>='a' && move[3]<='x'    ) {
4746         /* input move, Shogi -> normal */
4747         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4748         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4749         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4750         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4751     } else
4752     if(move[1]=='@' &&
4753        move[3]>='0' && move[3]<='9' &&
4754        move[2]>='a' && move[2]<='x'    ) {
4755         move[1] = '*';
4756         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4757         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4758     } else
4759     if(
4760        move[0]>='a' && move[0]<='x' &&
4761        move[3]>='0' && move[3]<='9' &&
4762        move[2]>='a' && move[2]<='x'    ) {
4763          /* output move, normal -> Shogi */
4764         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4765         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4766         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4767         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4768         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4769     }
4770     if (appData.debugMode) {
4771         fprintf(debugFP, "   out = '%s'\n", move);
4772     }
4773 }
4774
4775 char yy_textstr[8000];
4776
4777 /* Parser for moves from gnuchess, ICS, or user typein box */
4778 Boolean
4779 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4780      char *move;
4781      int moveNum;
4782      ChessMove *moveType;
4783      int *fromX, *fromY, *toX, *toY;
4784      char *promoChar;
4785 {       
4786     if (appData.debugMode) {
4787         fprintf(debugFP, "move to parse: %s\n", move);
4788     }
4789     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4790
4791     switch (*moveType) {
4792       case WhitePromotionChancellor:
4793       case BlackPromotionChancellor:
4794       case WhitePromotionArchbishop:
4795       case BlackPromotionArchbishop:
4796       case WhitePromotionQueen:
4797       case BlackPromotionQueen:
4798       case WhitePromotionRook:
4799       case BlackPromotionRook:
4800       case WhitePromotionBishop:
4801       case BlackPromotionBishop:
4802       case WhitePromotionKnight:
4803       case BlackPromotionKnight:
4804       case WhitePromotionKing:
4805       case BlackPromotionKing:
4806       case NormalMove:
4807       case WhiteCapturesEnPassant:
4808       case BlackCapturesEnPassant:
4809       case WhiteKingSideCastle:
4810       case WhiteQueenSideCastle:
4811       case BlackKingSideCastle:
4812       case BlackQueenSideCastle:
4813       case WhiteKingSideCastleWild:
4814       case WhiteQueenSideCastleWild:
4815       case BlackKingSideCastleWild:
4816       case BlackQueenSideCastleWild:
4817       /* Code added by Tord: */
4818       case WhiteHSideCastleFR:
4819       case WhiteASideCastleFR:
4820       case BlackHSideCastleFR:
4821       case BlackASideCastleFR:
4822       /* End of code added by Tord */
4823       case IllegalMove:         /* bug or odd chess variant */
4824         *fromX = currentMoveString[0] - AAA;
4825         *fromY = currentMoveString[1] - ONE;
4826         *toX = currentMoveString[2] - AAA;
4827         *toY = currentMoveString[3] - ONE;
4828         *promoChar = currentMoveString[4];
4829         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4830             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4831     if (appData.debugMode) {
4832         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4833     }
4834             *fromX = *fromY = *toX = *toY = 0;
4835             return FALSE;
4836         }
4837         if (appData.testLegality) {
4838           return (*moveType != IllegalMove);
4839         } else {
4840           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4841                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4842         }
4843
4844       case WhiteDrop:
4845       case BlackDrop:
4846         *fromX = *moveType == WhiteDrop ?
4847           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4848           (int) CharToPiece(ToLower(currentMoveString[0]));
4849         *fromY = DROP_RANK;
4850         *toX = currentMoveString[2] - AAA;
4851         *toY = currentMoveString[3] - ONE;
4852         *promoChar = NULLCHAR;
4853         return TRUE;
4854
4855       case AmbiguousMove:
4856       case ImpossibleMove:
4857       case (ChessMove) 0:       /* end of file */
4858       case ElapsedTime:
4859       case Comment:
4860       case PGNTag:
4861       case NAG:
4862       case WhiteWins:
4863       case BlackWins:
4864       case GameIsDrawn:
4865       default:
4866     if (appData.debugMode) {
4867         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4868     }
4869         /* bug? */
4870         *fromX = *fromY = *toX = *toY = 0;
4871         *promoChar = NULLCHAR;
4872         return FALSE;
4873     }
4874 }
4875
4876
4877 void
4878 ParsePV(char *pv, Boolean storeComments)
4879 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4880   int fromX, fromY, toX, toY; char promoChar;
4881   ChessMove moveType;
4882   Boolean valid;
4883   int nr = 0;
4884
4885   endPV = forwardMostMove;
4886   do {
4887     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4888     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4889     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4890 if(appData.debugMode){
4891 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);
4892 }
4893     if(!valid && nr == 0 &&
4894        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4895         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4896         // Hande case where played move is different from leading PV move
4897         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4898         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4899         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4900         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4901           endPV += 2; // if position different, keep this
4902           moveList[endPV-1][0] = fromX + AAA;
4903           moveList[endPV-1][1] = fromY + ONE;
4904           moveList[endPV-1][2] = toX + AAA;
4905           moveList[endPV-1][3] = toY + ONE;
4906           parseList[endPV-1][0] = NULLCHAR;
4907           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4908         }
4909       }
4910     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4911     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4912     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4913     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4914         valid++; // allow comments in PV
4915         continue;
4916     }
4917     nr++;
4918     if(endPV+1 > framePtr) break; // no space, truncate
4919     if(!valid) break;
4920     endPV++;
4921     CopyBoard(boards[endPV], boards[endPV-1]);
4922     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4923     moveList[endPV-1][0] = fromX + AAA;
4924     moveList[endPV-1][1] = fromY + ONE;
4925     moveList[endPV-1][2] = toX + AAA;
4926     moveList[endPV-1][3] = toY + ONE;
4927     if(storeComments)
4928         CoordsToAlgebraic(boards[endPV - 1],
4929                              PosFlags(endPV - 1),
4930                              fromY, fromX, toY, toX, promoChar,
4931                              parseList[endPV - 1]);
4932     else
4933         parseList[endPV-1][0] = NULLCHAR;
4934   } while(valid);
4935   currentMove = endPV;
4936   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4937   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4938                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4939   DrawPosition(TRUE, boards[currentMove]);
4940 }
4941
4942 static int lastX, lastY;
4943
4944 Boolean
4945 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4946 {
4947         int startPV;
4948         char *p;
4949
4950         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4951         lastX = x; lastY = y;
4952         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4953         startPV = index;
4954         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4955         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4956         index = startPV;
4957         do{ while(buf[index] && buf[index] != '\n') index++;
4958         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4959         buf[index] = 0;
4960         ParsePV(buf+startPV, FALSE);
4961         *start = startPV; *end = index-1;
4962         return TRUE;
4963 }
4964
4965 Boolean
4966 LoadPV(int x, int y)
4967 { // called on right mouse click to load PV
4968   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4969   lastX = x; lastY = y;
4970   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4971   return TRUE;
4972 }
4973
4974 void
4975 UnLoadPV()
4976 {
4977   if(endPV < 0) return;
4978   endPV = -1;
4979   currentMove = forwardMostMove;
4980   ClearPremoveHighlights();
4981   DrawPosition(TRUE, boards[currentMove]);
4982 }
4983
4984 void
4985 MovePV(int x, int y, int h)
4986 { // step through PV based on mouse coordinates (called on mouse move)
4987   int margin = h>>3, step = 0;
4988
4989   if(endPV < 0) return;
4990   // we must somehow check if right button is still down (might be released off board!)
4991   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4992   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4993   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4994   if(!step) return;
4995   lastX = x; lastY = y;
4996   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4997   currentMove += step;
4998   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4999   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5000                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5001   DrawPosition(FALSE, boards[currentMove]);
5002 }
5003
5004
5005 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5006 // All positions will have equal probability, but the current method will not provide a unique
5007 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5008 #define DARK 1
5009 #define LITE 2
5010 #define ANY 3
5011
5012 int squaresLeft[4];
5013 int piecesLeft[(int)BlackPawn];
5014 int seed, nrOfShuffles;
5015
5016 void GetPositionNumber()
5017 {       // sets global variable seed
5018         int i;
5019
5020         seed = appData.defaultFrcPosition;
5021         if(seed < 0) { // randomize based on time for negative FRC position numbers
5022                 for(i=0; i<50; i++) seed += random();
5023                 seed = random() ^ random() >> 8 ^ random() << 8;
5024                 if(seed<0) seed = -seed;
5025         }
5026 }
5027
5028 int put(Board board, int pieceType, int rank, int n, int shade)
5029 // put the piece on the (n-1)-th empty squares of the given shade
5030 {
5031         int i;
5032
5033         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5034                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5035                         board[rank][i] = (ChessSquare) pieceType;
5036                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5037                         squaresLeft[ANY]--;
5038                         piecesLeft[pieceType]--; 
5039                         return i;
5040                 }
5041         }
5042         return -1;
5043 }
5044
5045
5046 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5047 // calculate where the next piece goes, (any empty square), and put it there
5048 {
5049         int i;
5050
5051         i = seed % squaresLeft[shade];
5052         nrOfShuffles *= squaresLeft[shade];
5053         seed /= squaresLeft[shade];
5054         put(board, pieceType, rank, i, shade);
5055 }
5056
5057 void AddTwoPieces(Board board, int pieceType, int rank)
5058 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5059 {
5060         int i, n=squaresLeft[ANY], j=n-1, k;
5061
5062         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5063         i = seed % k;  // pick one
5064         nrOfShuffles *= k;
5065         seed /= k;
5066         while(i >= j) i -= j--;
5067         j = n - 1 - j; i += j;
5068         put(board, pieceType, rank, j, ANY);
5069         put(board, pieceType, rank, i, ANY);
5070 }
5071
5072 void SetUpShuffle(Board board, int number)
5073 {
5074         int i, p, first=1;
5075
5076         GetPositionNumber(); nrOfShuffles = 1;
5077
5078         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5079         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5080         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5081
5082         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5083
5084         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5085             p = (int) board[0][i];
5086             if(p < (int) BlackPawn) piecesLeft[p] ++;
5087             board[0][i] = EmptySquare;
5088         }
5089
5090         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5091             // shuffles restricted to allow normal castling put KRR first
5092             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5093                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5094             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5095                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5096             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5097                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5098             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5099                 put(board, WhiteRook, 0, 0, ANY);
5100             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5101         }
5102
5103         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5104             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5105             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5106                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5107                 while(piecesLeft[p] >= 2) {
5108                     AddOnePiece(board, p, 0, LITE);
5109                     AddOnePiece(board, p, 0, DARK);
5110                 }
5111                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5112             }
5113
5114         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5115             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5116             // but we leave King and Rooks for last, to possibly obey FRC restriction
5117             if(p == (int)WhiteRook) continue;
5118             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5119             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5120         }
5121
5122         // now everything is placed, except perhaps King (Unicorn) and Rooks
5123
5124         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5125             // Last King gets castling rights
5126             while(piecesLeft[(int)WhiteUnicorn]) {
5127                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5128                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5129             }
5130
5131             while(piecesLeft[(int)WhiteKing]) {
5132                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5133                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5134             }
5135
5136
5137         } else {
5138             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5139             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5140         }
5141
5142         // Only Rooks can be left; simply place them all
5143         while(piecesLeft[(int)WhiteRook]) {
5144                 i = put(board, WhiteRook, 0, 0, ANY);
5145                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5146                         if(first) {
5147                                 first=0;
5148                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5149                         }
5150                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5151                 }
5152         }
5153         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5154             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5155         }
5156
5157         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5158 }
5159
5160 int SetCharTable( char *table, const char * map )
5161 /* [HGM] moved here from winboard.c because of its general usefulness */
5162 /*       Basically a safe strcpy that uses the last character as King */
5163 {
5164     int result = FALSE; int NrPieces;
5165
5166     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5167                     && NrPieces >= 12 && !(NrPieces&1)) {
5168         int i; /* [HGM] Accept even length from 12 to 34 */
5169
5170         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5171         for( i=0; i<NrPieces/2-1; i++ ) {
5172             table[i] = map[i];
5173             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5174         }
5175         table[(int) WhiteKing]  = map[NrPieces/2-1];
5176         table[(int) BlackKing]  = map[NrPieces-1];
5177
5178         result = TRUE;
5179     }
5180
5181     return result;
5182 }
5183
5184 void Prelude(Board board)
5185 {       // [HGM] superchess: random selection of exo-pieces
5186         int i, j, k; ChessSquare p; 
5187         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5188
5189         GetPositionNumber(); // use FRC position number
5190
5191         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5192             SetCharTable(pieceToChar, appData.pieceToCharTable);
5193             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5194                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5195         }
5196
5197         j = seed%4;                 seed /= 4; 
5198         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5199         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5200         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5201         j = seed%3 + (seed%3 >= j); seed /= 3; 
5202         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5203         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5204         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5205         j = seed%3;                 seed /= 3; 
5206         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5207         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5208         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5209         j = seed%2 + (seed%2 >= j); seed /= 2; 
5210         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5211         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5212         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5213         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5214         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5215         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5216         put(board, exoPieces[0],    0, 0, ANY);
5217         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5218 }
5219
5220 void
5221 InitPosition(redraw)
5222      int redraw;
5223 {
5224     ChessSquare (* pieces)[BOARD_FILES];
5225     int i, j, pawnRow, overrule,
5226     oldx = gameInfo.boardWidth,
5227     oldy = gameInfo.boardHeight,
5228     oldh = gameInfo.holdingsWidth,
5229     oldv = gameInfo.variant;
5230
5231     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5232
5233     /* [AS] Initialize pv info list [HGM] and game status */
5234     {
5235         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5236             pvInfoList[i].depth = 0;
5237             boards[i][EP_STATUS] = EP_NONE;
5238             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5239         }
5240
5241         initialRulePlies = 0; /* 50-move counter start */
5242
5243         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5244         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5245     }
5246
5247     
5248     /* [HGM] logic here is completely changed. In stead of full positions */
5249     /* the initialized data only consist of the two backranks. The switch */
5250     /* selects which one we will use, which is than copied to the Board   */
5251     /* initialPosition, which for the rest is initialized by Pawns and    */
5252     /* empty squares. This initial position is then copied to boards[0],  */
5253     /* possibly after shuffling, so that it remains available.            */
5254
5255     gameInfo.holdingsWidth = 0; /* default board sizes */
5256     gameInfo.boardWidth    = 8;
5257     gameInfo.boardHeight   = 8;
5258     gameInfo.holdingsSize  = 0;
5259     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5260     for(i=0; i<BOARD_FILES-2; i++)
5261       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5262     initialPosition[EP_STATUS] = EP_NONE;
5263     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5264
5265     switch (gameInfo.variant) {
5266     case VariantFischeRandom:
5267       shuffleOpenings = TRUE;
5268     default:
5269       pieces = FIDEArray;
5270       break;
5271     case VariantShatranj:
5272       pieces = ShatranjArray;
5273       nrCastlingRights = 0;
5274       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5275       break;
5276     case VariantMakruk:
5277       pieces = makrukArray;
5278       nrCastlingRights = 0;
5279       startedFromSetupPosition = TRUE;
5280       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5281       break;
5282     case VariantTwoKings:
5283       pieces = twoKingsArray;
5284       break;
5285     case VariantCapaRandom:
5286       shuffleOpenings = TRUE;
5287     case VariantCapablanca:
5288       pieces = CapablancaArray;
5289       gameInfo.boardWidth = 10;
5290       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5291       break;
5292     case VariantGothic:
5293       pieces = GothicArray;
5294       gameInfo.boardWidth = 10;
5295       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5296       break;
5297     case VariantJanus:
5298       pieces = JanusArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5301       nrCastlingRights = 6;
5302         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5303         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5304         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5305         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5306         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5307         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5308       break;
5309     case VariantFalcon:
5310       pieces = FalconArray;
5311       gameInfo.boardWidth = 10;
5312       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5313       break;
5314     case VariantXiangqi:
5315       pieces = XiangqiArray;
5316       gameInfo.boardWidth  = 9;
5317       gameInfo.boardHeight = 10;
5318       nrCastlingRights = 0;
5319       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5320       break;
5321     case VariantShogi:
5322       pieces = ShogiArray;
5323       gameInfo.boardWidth  = 9;
5324       gameInfo.boardHeight = 9;
5325       gameInfo.holdingsSize = 7;
5326       nrCastlingRights = 0;
5327       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5328       break;
5329     case VariantCourier:
5330       pieces = CourierArray;
5331       gameInfo.boardWidth  = 12;
5332       nrCastlingRights = 0;
5333       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5334       break;
5335     case VariantKnightmate:
5336       pieces = KnightmateArray;
5337       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5338       break;
5339     case VariantFairy:
5340       pieces = fairyArray;
5341       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5342       break;
5343     case VariantGreat:
5344       pieces = GreatArray;
5345       gameInfo.boardWidth = 10;
5346       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5347       gameInfo.holdingsSize = 8;
5348       break;
5349     case VariantSuper:
5350       pieces = FIDEArray;
5351       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5352       gameInfo.holdingsSize = 8;
5353       startedFromSetupPosition = TRUE;
5354       break;
5355     case VariantCrazyhouse:
5356     case VariantBughouse:
5357       pieces = FIDEArray;
5358       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5359       gameInfo.holdingsSize = 5;
5360       break;
5361     case VariantWildCastle:
5362       pieces = FIDEArray;
5363       /* !!?shuffle with kings guaranteed to be on d or e file */
5364       shuffleOpenings = 1;
5365       break;
5366     case VariantNoCastle:
5367       pieces = FIDEArray;
5368       nrCastlingRights = 0;
5369       /* !!?unconstrained back-rank shuffle */
5370       shuffleOpenings = 1;
5371       break;
5372     }
5373
5374     overrule = 0;
5375     if(appData.NrFiles >= 0) {
5376         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5377         gameInfo.boardWidth = appData.NrFiles;
5378     }
5379     if(appData.NrRanks >= 0) {
5380         gameInfo.boardHeight = appData.NrRanks;
5381     }
5382     if(appData.holdingsSize >= 0) {
5383         i = appData.holdingsSize;
5384         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5385         gameInfo.holdingsSize = i;
5386     }
5387     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5388     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5389         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5390
5391     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5392     if(pawnRow < 1) pawnRow = 1;
5393     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5394
5395     /* User pieceToChar list overrules defaults */
5396     if(appData.pieceToCharTable != NULL)
5397         SetCharTable(pieceToChar, appData.pieceToCharTable);
5398
5399     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5400
5401         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5402             s = (ChessSquare) 0; /* account holding counts in guard band */
5403         for( i=0; i<BOARD_HEIGHT; i++ )
5404             initialPosition[i][j] = s;
5405
5406         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5407         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5408         initialPosition[pawnRow][j] = WhitePawn;
5409         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5410         if(gameInfo.variant == VariantXiangqi) {
5411             if(j&1) {
5412                 initialPosition[pawnRow][j] = 
5413                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5414                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5415                    initialPosition[2][j] = WhiteCannon;
5416                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5417                 }
5418             }
5419         }
5420         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5421     }
5422     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5423
5424             j=BOARD_LEFT+1;
5425             initialPosition[1][j] = WhiteBishop;
5426             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5427             j=BOARD_RGHT-2;
5428             initialPosition[1][j] = WhiteRook;
5429             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5430     }
5431
5432     if( nrCastlingRights == -1) {
5433         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5434         /*       This sets default castling rights from none to normal corners   */
5435         /* Variants with other castling rights must set them themselves above    */
5436         nrCastlingRights = 6;
5437        
5438         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5439         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5440         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5441         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5442         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5443         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5444      }
5445
5446      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5447      if(gameInfo.variant == VariantGreat) { // promotion commoners
5448         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5449         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5450         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5451         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5452      }
5453   if (appData.debugMode) {
5454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5455   }
5456     if(shuffleOpenings) {
5457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5458         startedFromSetupPosition = TRUE;
5459     }
5460     if(startedFromPositionFile) {
5461       /* [HGM] loadPos: use PositionFile for every new game */
5462       CopyBoard(initialPosition, filePosition);
5463       for(i=0; i<nrCastlingRights; i++)
5464           initialRights[i] = filePosition[CASTLING][i];
5465       startedFromSetupPosition = TRUE;
5466     }
5467
5468     CopyBoard(boards[0], initialPosition);
5469
5470     if(oldx != gameInfo.boardWidth ||
5471        oldy != gameInfo.boardHeight ||
5472        oldh != gameInfo.holdingsWidth
5473 #ifdef GOTHIC
5474        || oldv == VariantGothic ||        // For licensing popups
5475        gameInfo.variant == VariantGothic
5476 #endif
5477 #ifdef FALCON
5478        || oldv == VariantFalcon ||
5479        gameInfo.variant == VariantFalcon
5480 #endif
5481                                          )
5482             InitDrawingSizes(-2 ,0);
5483
5484     if (redraw)
5485       DrawPosition(TRUE, boards[currentMove]);
5486 }
5487
5488 void
5489 SendBoard(cps, moveNum)
5490      ChessProgramState *cps;
5491      int moveNum;
5492 {
5493     char message[MSG_SIZ];
5494     
5495     if (cps->useSetboard) {
5496       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5497       sprintf(message, "setboard %s\n", fen);
5498       SendToProgram(message, cps);
5499       free(fen);
5500
5501     } else {
5502       ChessSquare *bp;
5503       int i, j;
5504       /* Kludge to set black to move, avoiding the troublesome and now
5505        * deprecated "black" command.
5506        */
5507       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5508
5509       SendToProgram("edit\n", cps);
5510       SendToProgram("#\n", cps);
5511       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5512         bp = &boards[moveNum][i][BOARD_LEFT];
5513         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5514           if ((int) *bp < (int) BlackPawn) {
5515             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5516                     AAA + j, ONE + i);
5517             if(message[0] == '+' || message[0] == '~') {
5518                 sprintf(message, "%c%c%c+\n",
5519                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5520                         AAA + j, ONE + i);
5521             }
5522             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5523                 message[1] = BOARD_RGHT   - 1 - j + '1';
5524                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5525             }
5526             SendToProgram(message, cps);
5527           }
5528         }
5529       }
5530     
5531       SendToProgram("c\n", cps);
5532       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5533         bp = &boards[moveNum][i][BOARD_LEFT];
5534         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5535           if (((int) *bp != (int) EmptySquare)
5536               && ((int) *bp >= (int) BlackPawn)) {
5537             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5538                     AAA + j, ONE + i);
5539             if(message[0] == '+' || message[0] == '~') {
5540                 sprintf(message, "%c%c%c+\n",
5541                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5542                         AAA + j, ONE + i);
5543             }
5544             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5545                 message[1] = BOARD_RGHT   - 1 - j + '1';
5546                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5547             }
5548             SendToProgram(message, cps);
5549           }
5550         }
5551       }
5552     
5553       SendToProgram(".\n", cps);
5554     }
5555     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5556 }
5557
5558 static int autoQueen; // [HGM] oneclick
5559
5560 int
5561 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5562 {
5563     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5564     /* [HGM] add Shogi promotions */
5565     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5566     ChessSquare piece;
5567     ChessMove moveType;
5568     Boolean premove;
5569
5570     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5571     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5572
5573     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5574       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5575         return FALSE;
5576
5577     piece = boards[currentMove][fromY][fromX];
5578     if(gameInfo.variant == VariantShogi) {
5579         promotionZoneSize = 3;
5580         highestPromotingPiece = (int)WhiteFerz;
5581     } else if(gameInfo.variant == VariantMakruk) {
5582         promotionZoneSize = 3;
5583     }
5584
5585     // next weed out all moves that do not touch the promotion zone at all
5586     if((int)piece >= BlackPawn) {
5587         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5588              return FALSE;
5589         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5590     } else {
5591         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5592            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5593     }
5594
5595     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5596
5597     // weed out mandatory Shogi promotions
5598     if(gameInfo.variant == VariantShogi) {
5599         if(piece >= BlackPawn) {
5600             if(toY == 0 && piece == BlackPawn ||
5601                toY == 0 && piece == BlackQueen ||
5602                toY <= 1 && piece == BlackKnight) {
5603                 *promoChoice = '+';
5604                 return FALSE;
5605             }
5606         } else {
5607             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5608                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5609                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5610                 *promoChoice = '+';
5611                 return FALSE;
5612             }
5613         }
5614     }
5615
5616     // weed out obviously illegal Pawn moves
5617     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5618         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5619         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5620         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5621         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5622         // note we are not allowed to test for valid (non-)capture, due to premove
5623     }
5624
5625     // we either have a choice what to promote to, or (in Shogi) whether to promote
5626     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5627         *promoChoice = PieceToChar(BlackFerz);  // no choice
5628         return FALSE;
5629     }
5630     if(autoQueen) { // predetermined
5631         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5632              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5633         else *promoChoice = PieceToChar(BlackQueen);
5634         return FALSE;
5635     }
5636
5637     // suppress promotion popup on illegal moves that are not premoves
5638     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5639               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5640     if(appData.testLegality && !premove) {
5641         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5642                         fromY, fromX, toY, toX, NULLCHAR);
5643         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5644            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5645             return FALSE;
5646     }
5647
5648     return TRUE;
5649 }
5650
5651 int
5652 InPalace(row, column)
5653      int row, column;
5654 {   /* [HGM] for Xiangqi */
5655     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5656          column < (BOARD_WIDTH + 4)/2 &&
5657          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5658     return FALSE;
5659 }
5660
5661 int
5662 PieceForSquare (x, y)
5663      int x;
5664      int y;
5665 {
5666   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5667      return -1;
5668   else
5669      return boards[currentMove][y][x];
5670 }
5671
5672 int
5673 OKToStartUserMove(x, y)
5674      int x, y;
5675 {
5676     ChessSquare from_piece;
5677     int white_piece;
5678
5679     if (matchMode) return FALSE;
5680     if (gameMode == EditPosition) return TRUE;
5681
5682     if (x >= 0 && y >= 0)
5683       from_piece = boards[currentMove][y][x];
5684     else
5685       from_piece = EmptySquare;
5686
5687     if (from_piece == EmptySquare) return FALSE;
5688
5689     white_piece = (int)from_piece >= (int)WhitePawn &&
5690       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5691
5692     switch (gameMode) {
5693       case PlayFromGameFile:
5694       case AnalyzeFile:
5695       case TwoMachinesPlay:
5696       case EndOfGame:
5697         return FALSE;
5698
5699       case IcsObserving:
5700       case IcsIdle:
5701         return FALSE;
5702
5703       case MachinePlaysWhite:
5704       case IcsPlayingBlack:
5705         if (appData.zippyPlay) return FALSE;
5706         if (white_piece) {
5707             DisplayMoveError(_("You are playing Black"));
5708             return FALSE;
5709         }
5710         break;
5711
5712       case MachinePlaysBlack:
5713       case IcsPlayingWhite:
5714         if (appData.zippyPlay) return FALSE;
5715         if (!white_piece) {
5716             DisplayMoveError(_("You are playing White"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case EditGame:
5722         if (!white_piece && WhiteOnMove(currentMove)) {
5723             DisplayMoveError(_("It is White's turn"));
5724             return FALSE;
5725         }           
5726         if (white_piece && !WhiteOnMove(currentMove)) {
5727             DisplayMoveError(_("It is Black's turn"));
5728             return FALSE;
5729         }           
5730         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5731             /* Editing correspondence game history */
5732             /* Could disallow this or prompt for confirmation */
5733             cmailOldMove = -1;
5734         }
5735         break;
5736
5737       case BeginningOfGame:
5738         if (appData.icsActive) return FALSE;
5739         if (!appData.noChessProgram) {
5740             if (!white_piece) {
5741                 DisplayMoveError(_("You are playing White"));
5742                 return FALSE;
5743             }
5744         }
5745         break;
5746         
5747       case Training:
5748         if (!white_piece && WhiteOnMove(currentMove)) {
5749             DisplayMoveError(_("It is White's turn"));
5750             return FALSE;
5751         }           
5752         if (white_piece && !WhiteOnMove(currentMove)) {
5753             DisplayMoveError(_("It is Black's turn"));
5754             return FALSE;
5755         }           
5756         break;
5757
5758       default:
5759       case IcsExamining:
5760         break;
5761     }
5762     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5763         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5764         && gameMode != AnalyzeFile && gameMode != Training) {
5765         DisplayMoveError(_("Displayed position is not current"));
5766         return FALSE;
5767     }
5768     return TRUE;
5769 }
5770
5771 Boolean
5772 OnlyMove(int *x, int *y, Boolean captures) {
5773     DisambiguateClosure cl;
5774     if (appData.zippyPlay) return FALSE;
5775     switch(gameMode) {
5776       case MachinePlaysBlack:
5777       case IcsPlayingWhite:
5778       case BeginningOfGame:
5779         if(!WhiteOnMove(currentMove)) return FALSE;
5780         break;
5781       case MachinePlaysWhite:
5782       case IcsPlayingBlack:
5783         if(WhiteOnMove(currentMove)) return FALSE;
5784         break;
5785       default:
5786         return FALSE;
5787     }
5788     cl.pieceIn = EmptySquare; 
5789     cl.rfIn = *y;
5790     cl.ffIn = *x;
5791     cl.rtIn = -1;
5792     cl.ftIn = -1;
5793     cl.promoCharIn = NULLCHAR;
5794     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5795     if( cl.kind == NormalMove ||
5796         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5797         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5798         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5799         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5800       fromX = cl.ff;
5801       fromY = cl.rf;
5802       *x = cl.ft;
5803       *y = cl.rt;
5804       return TRUE;
5805     }
5806     if(cl.kind != ImpossibleMove) return FALSE;
5807     cl.pieceIn = EmptySquare;
5808     cl.rfIn = -1;
5809     cl.ffIn = -1;
5810     cl.rtIn = *y;
5811     cl.ftIn = *x;
5812     cl.promoCharIn = NULLCHAR;
5813     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5814     if( cl.kind == NormalMove ||
5815         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5816         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5817         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5818         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5819       fromX = cl.ff;
5820       fromY = cl.rf;
5821       *x = cl.ft;
5822       *y = cl.rt;
5823       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5824       return TRUE;
5825     }
5826     return FALSE;
5827 }
5828
5829 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5830 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5831 int lastLoadGameUseList = FALSE;
5832 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5833 ChessMove lastLoadGameStart = (ChessMove) 0;
5834
5835 ChessMove
5836 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5837      int fromX, fromY, toX, toY;
5838      int promoChar;
5839      Boolean captureOwn;
5840 {
5841     ChessMove moveType;
5842     ChessSquare pdown, pup;
5843
5844     /* Check if the user is playing in turn.  This is complicated because we
5845        let the user "pick up" a piece before it is his turn.  So the piece he
5846        tried to pick up may have been captured by the time he puts it down!
5847        Therefore we use the color the user is supposed to be playing in this
5848        test, not the color of the piece that is currently on the starting
5849        square---except in EditGame mode, where the user is playing both
5850        sides; fortunately there the capture race can't happen.  (It can
5851        now happen in IcsExamining mode, but that's just too bad.  The user
5852        will get a somewhat confusing message in that case.)
5853        */
5854
5855     switch (gameMode) {
5856       case PlayFromGameFile:
5857       case AnalyzeFile:
5858       case TwoMachinesPlay:
5859       case EndOfGame:
5860       case IcsObserving:
5861       case IcsIdle:
5862         /* We switched into a game mode where moves are not accepted,
5863            perhaps while the mouse button was down. */
5864         return ImpossibleMove;
5865
5866       case MachinePlaysWhite:
5867         /* User is moving for Black */
5868         if (WhiteOnMove(currentMove)) {
5869             DisplayMoveError(_("It is White's turn"));
5870             return ImpossibleMove;
5871         }
5872         break;
5873
5874       case MachinePlaysBlack:
5875         /* User is moving for White */
5876         if (!WhiteOnMove(currentMove)) {
5877             DisplayMoveError(_("It is Black's turn"));
5878             return ImpossibleMove;
5879         }
5880         break;
5881
5882       case EditGame:
5883       case IcsExamining:
5884       case BeginningOfGame:
5885       case AnalyzeMode:
5886       case Training:
5887         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5888             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5889             /* User is moving for Black */
5890             if (WhiteOnMove(currentMove)) {
5891                 DisplayMoveError(_("It is White's turn"));
5892                 return ImpossibleMove;
5893             }
5894         } else {
5895             /* User is moving for White */
5896             if (!WhiteOnMove(currentMove)) {
5897                 DisplayMoveError(_("It is Black's turn"));
5898                 return ImpossibleMove;
5899             }
5900         }
5901         break;
5902
5903       case IcsPlayingBlack:
5904         /* User is moving for Black */
5905         if (WhiteOnMove(currentMove)) {
5906             if (!appData.premove) {
5907                 DisplayMoveError(_("It is White's turn"));
5908             } else if (toX >= 0 && toY >= 0) {
5909                 premoveToX = toX;
5910                 premoveToY = toY;
5911                 premoveFromX = fromX;
5912                 premoveFromY = fromY;
5913                 premovePromoChar = promoChar;
5914                 gotPremove = 1;
5915                 if (appData.debugMode) 
5916                     fprintf(debugFP, "Got premove: fromX %d,"
5917                             "fromY %d, toX %d, toY %d\n",
5918                             fromX, fromY, toX, toY);
5919             }
5920             return ImpossibleMove;
5921         }
5922         break;
5923
5924       case IcsPlayingWhite:
5925         /* User is moving for White */
5926         if (!WhiteOnMove(currentMove)) {
5927             if (!appData.premove) {
5928                 DisplayMoveError(_("It is Black's turn"));
5929             } else if (toX >= 0 && toY >= 0) {
5930                 premoveToX = toX;
5931                 premoveToY = toY;
5932                 premoveFromX = fromX;
5933                 premoveFromY = fromY;
5934                 premovePromoChar = promoChar;
5935                 gotPremove = 1;
5936                 if (appData.debugMode) 
5937                     fprintf(debugFP, "Got premove: fromX %d,"
5938                             "fromY %d, toX %d, toY %d\n",
5939                             fromX, fromY, toX, toY);
5940             }
5941             return ImpossibleMove;
5942         }
5943         break;
5944
5945       default:
5946         break;
5947
5948       case EditPosition:
5949         /* EditPosition, empty square, or different color piece;
5950            click-click move is possible */
5951         if (toX == -2 || toY == -2) {
5952             boards[0][fromY][fromX] = EmptySquare;
5953             return AmbiguousMove;
5954         } else if (toX >= 0 && toY >= 0) {
5955             boards[0][toY][toX] = boards[0][fromY][fromX];
5956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5957                 if(boards[0][fromY][0] != EmptySquare) {
5958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5960                 }
5961             } else
5962             if(fromX == BOARD_RGHT+1) {
5963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5966                 }
5967             } else
5968             boards[0][fromY][fromX] = EmptySquare;
5969             return AmbiguousMove;
5970         }
5971         return ImpossibleMove;
5972     }
5973
5974     if(toX < 0 || toY < 0) return ImpossibleMove;
5975     pdown = boards[currentMove][fromY][fromX];
5976     pup = boards[currentMove][toY][toX];
5977
5978     /* [HGM] If move started in holdings, it means a drop */
5979     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5980          if( pup != EmptySquare ) return ImpossibleMove;
5981          if(appData.testLegality) {
5982              /* it would be more logical if LegalityTest() also figured out
5983               * which drops are legal. For now we forbid pawns on back rank.
5984               * Shogi is on its own here...
5985               */
5986              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5987                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5988                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5989          }
5990          return WhiteDrop; /* Not needed to specify white or black yet */
5991     }
5992
5993     /* [HGM] always test for legality, to get promotion info */
5994     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5995                                          fromY, fromX, toY, toX, promoChar);
5996     /* [HGM] but possibly ignore an IllegalMove result */
5997     if (appData.testLegality) {
5998         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5999             DisplayMoveError(_("Illegal move"));
6000             return ImpossibleMove;
6001         }
6002     }
6003
6004     return moveType;
6005     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6006        function is made into one that returns an OK move type if FinishMove
6007        should be called. This to give the calling driver routine the
6008        opportunity to finish the userMove input with a promotion popup,
6009        without bothering the user with this for invalid or illegal moves */
6010
6011 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6012 }
6013
6014 /* Common tail of UserMoveEvent and DropMenuEvent */
6015 int
6016 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6017      ChessMove moveType;
6018      int fromX, fromY, toX, toY;
6019      /*char*/int promoChar;
6020 {
6021     char *bookHit = 0;
6022
6023     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6024         // [HGM] superchess: suppress promotions to non-available piece
6025         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6026         if(WhiteOnMove(currentMove)) {
6027             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6028         } else {
6029             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6030         }
6031     }
6032
6033     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6034        move type in caller when we know the move is a legal promotion */
6035     if(moveType == NormalMove && promoChar)
6036         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6037
6038     /* [HGM] convert drag-and-drop piece drops to standard form */
6039     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6040          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6041            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6042                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6043            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6044            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6045            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6046            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6047          fromY = DROP_RANK;
6048     }
6049
6050     /* [HGM] <popupFix> The following if has been moved here from
6051        UserMoveEvent(). Because it seemed to belong here (why not allow
6052        piece drops in training games?), and because it can only be
6053        performed after it is known to what we promote. */
6054     if (gameMode == Training) {
6055       /* compare the move played on the board to the next move in the
6056        * game. If they match, display the move and the opponent's response. 
6057        * If they don't match, display an error message.
6058        */
6059       int saveAnimate;
6060       Board testBoard;
6061       CopyBoard(testBoard, boards[currentMove]);
6062       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6063
6064       if (CompareBoards(testBoard, boards[currentMove+1])) {
6065         ForwardInner(currentMove+1);
6066
6067         /* Autoplay the opponent's response.
6068          * if appData.animate was TRUE when Training mode was entered,
6069          * the response will be animated.
6070          */
6071         saveAnimate = appData.animate;
6072         appData.animate = animateTraining;
6073         ForwardInner(currentMove+1);
6074         appData.animate = saveAnimate;
6075
6076         /* check for the end of the game */
6077         if (currentMove >= forwardMostMove) {
6078           gameMode = PlayFromGameFile;
6079           ModeHighlight();
6080           SetTrainingModeOff();
6081           DisplayInformation(_("End of game"));
6082         }
6083       } else {
6084         DisplayError(_("Incorrect move"), 0);
6085       }
6086       return 1;
6087     }
6088
6089   /* Ok, now we know that the move is good, so we can kill
6090      the previous line in Analysis Mode */
6091   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6092                                 && currentMove < forwardMostMove) {
6093     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6094   }
6095
6096   /* If we need the chess program but it's dead, restart it */
6097   ResurrectChessProgram();
6098
6099   /* A user move restarts a paused game*/
6100   if (pausing)
6101     PauseEvent();
6102
6103   thinkOutput[0] = NULLCHAR;
6104
6105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6106
6107   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6108
6109   if (gameMode == BeginningOfGame) {
6110     if (appData.noChessProgram) {
6111       gameMode = EditGame;
6112       SetGameInfo();
6113     } else {
6114       char buf[MSG_SIZ];
6115       gameMode = MachinePlaysBlack;
6116       StartClocks();
6117       SetGameInfo();
6118       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6119       DisplayTitle(buf);
6120       if (first.sendName) {
6121         sprintf(buf, "name %s\n", gameInfo.white);
6122         SendToProgram(buf, &first);
6123       }
6124       StartClocks();
6125     }
6126     ModeHighlight();
6127   }
6128
6129   /* Relay move to ICS or chess engine */
6130   if (appData.icsActive) {
6131     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6132         gameMode == IcsExamining) {
6133       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6134         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6135         SendToICS("draw ");
6136         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6137       }
6138       // also send plain move, in case ICS does not understand atomic claims
6139       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6140       ics_user_moved = 1;
6141     }
6142   } else {
6143     if (first.sendTime && (gameMode == BeginningOfGame ||
6144                            gameMode == MachinePlaysWhite ||
6145                            gameMode == MachinePlaysBlack)) {
6146       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6147     }
6148     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6149          // [HGM] book: if program might be playing, let it use book
6150         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6151         first.maybeThinking = TRUE;
6152     } else SendMoveToProgram(forwardMostMove-1, &first);
6153     if (currentMove == cmailOldMove + 1) {
6154       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6155     }
6156   }
6157
6158   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159
6160   switch (gameMode) {
6161   case EditGame:
6162     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6163     case MT_NONE:
6164     case MT_CHECK:
6165       break;
6166     case MT_CHECKMATE:
6167     case MT_STAINMATE:
6168       if (WhiteOnMove(currentMove)) {
6169         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6170       } else {
6171         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6172       }
6173       break;
6174     case MT_STALEMATE:
6175       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6176       break;
6177     }
6178     break;
6179     
6180   case MachinePlaysBlack:
6181   case MachinePlaysWhite:
6182     /* disable certain menu options while machine is thinking */
6183     SetMachineThinkingEnables();
6184     break;
6185
6186   default:
6187     break;
6188   }
6189
6190   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6191         
6192   if(bookHit) { // [HGM] book: simulate book reply
6193         static char bookMove[MSG_SIZ]; // a bit generous?
6194
6195         programStats.nodes = programStats.depth = programStats.time = 
6196         programStats.score = programStats.got_only_move = 0;
6197         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6198
6199         strcpy(bookMove, "move ");
6200         strcat(bookMove, bookHit);
6201         HandleMachineMove(bookMove, &first);
6202   }
6203   return 1;
6204 }
6205
6206 void
6207 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6208      int fromX, fromY, toX, toY;
6209      int promoChar;
6210 {
6211     /* [HGM] This routine was added to allow calling of its two logical
6212        parts from other modules in the old way. Before, UserMoveEvent()
6213        automatically called FinishMove() if the move was OK, and returned
6214        otherwise. I separated the two, in order to make it possible to
6215        slip a promotion popup in between. But that it always needs two
6216        calls, to the first part, (now called UserMoveTest() ), and to
6217        FinishMove if the first part succeeded. Calls that do not need
6218        to do anything in between, can call this routine the old way. 
6219     */
6220     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6221 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6222     if(moveType == AmbiguousMove)
6223         DrawPosition(FALSE, boards[currentMove]);
6224     else if(moveType != ImpossibleMove && moveType != Comment)
6225         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6226 }
6227
6228 void
6229 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6230      Board board;
6231      int flags;
6232      ChessMove kind;
6233      int rf, ff, rt, ft;
6234      VOIDSTAR closure;
6235 {
6236     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6237     Markers *m = (Markers *) closure;
6238     if(rf == fromY && ff == fromX)
6239         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6240                          || kind == WhiteCapturesEnPassant
6241                          || kind == BlackCapturesEnPassant);
6242     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6243 }
6244
6245 void
6246 MarkTargetSquares(int clear)
6247 {
6248   int x, y;
6249   if(!appData.markers || !appData.highlightDragging || 
6250      !appData.testLegality || gameMode == EditPosition) return;
6251   if(clear) {
6252     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6253   } else {
6254     int capt = 0;
6255     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6256     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6257       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6258       if(capt)
6259       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6260     }
6261   }
6262   DrawPosition(TRUE, NULL);
6263 }
6264
6265 void LeftClick(ClickType clickType, int xPix, int yPix)
6266 {
6267     int x, y;
6268     Boolean saveAnimate;
6269     static int second = 0, promotionChoice = 0, dragging = 0;
6270     char promoChoice = NULLCHAR;
6271
6272     if(appData.seekGraph && appData.icsActive && loggedOn &&
6273         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6274         SeekGraphClick(clickType, xPix, yPix, 0);
6275         return;
6276     }
6277
6278     if (clickType == Press) ErrorPopDown();
6279     MarkTargetSquares(1);
6280
6281     x = EventToSquare(xPix, BOARD_WIDTH);
6282     y = EventToSquare(yPix, BOARD_HEIGHT);
6283     if (!flipView && y >= 0) {
6284         y = BOARD_HEIGHT - 1 - y;
6285     }
6286     if (flipView && x >= 0) {
6287         x = BOARD_WIDTH - 1 - x;
6288     }
6289
6290     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6291         if(clickType == Release) return; // ignore upclick of click-click destination
6292         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6293         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6294         if(gameInfo.holdingsWidth && 
6295                 (WhiteOnMove(currentMove) 
6296                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6297                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6298             // click in right holdings, for determining promotion piece
6299             ChessSquare p = boards[currentMove][y][x];
6300             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6301             if(p != EmptySquare) {
6302                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6303                 fromX = fromY = -1;
6304                 return;
6305             }
6306         }
6307         DrawPosition(FALSE, boards[currentMove]);
6308         return;
6309     }
6310
6311     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6312     if(clickType == Press
6313             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6314               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6315               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6316         return;
6317
6318     autoQueen = appData.alwaysPromoteToQueen;
6319
6320     if (fromX == -1) {
6321       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6322         if (clickType == Press) {
6323             /* First square */
6324             if (OKToStartUserMove(x, y)) {
6325                 fromX = x;
6326                 fromY = y;
6327                 second = 0;
6328                 MarkTargetSquares(0);
6329                 DragPieceBegin(xPix, yPix); dragging = 1;
6330                 if (appData.highlightDragging) {
6331                     SetHighlights(x, y, -1, -1);
6332                 }
6333             }
6334         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6335             DragPieceEnd(xPix, yPix); dragging = 0;
6336             DrawPosition(FALSE, NULL);
6337         }
6338         return;
6339       }
6340     }
6341
6342     /* fromX != -1 */
6343     if (clickType == Press && gameMode != EditPosition) {
6344         ChessSquare fromP;
6345         ChessSquare toP;
6346         int frc;
6347
6348         // ignore off-board to clicks
6349         if(y < 0 || x < 0) return;
6350
6351         /* Check if clicking again on the same color piece */
6352         fromP = boards[currentMove][fromY][fromX];
6353         toP = boards[currentMove][y][x];
6354         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6355         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6356              WhitePawn <= toP && toP <= WhiteKing &&
6357              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6358              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6359             (BlackPawn <= fromP && fromP <= BlackKing && 
6360              BlackPawn <= toP && toP <= BlackKing &&
6361              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6362              !(fromP == BlackKing && toP == BlackRook && frc))) {
6363             /* Clicked again on same color piece -- changed his mind */
6364             second = (x == fromX && y == fromY);
6365            if(!second || !OnlyMove(&x, &y, TRUE)) {
6366             if (appData.highlightDragging) {
6367                 SetHighlights(x, y, -1, -1);
6368             } else {
6369                 ClearHighlights();
6370             }
6371             if (OKToStartUserMove(x, y)) {
6372                 fromX = x;
6373                 fromY = y; dragging = 1;
6374                 MarkTargetSquares(0);
6375                 DragPieceBegin(xPix, yPix);
6376             }
6377             return;
6378            }
6379         }
6380         // ignore clicks on holdings
6381         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6382     }
6383
6384     if (clickType == Release && x == fromX && y == fromY) {
6385         DragPieceEnd(xPix, yPix); dragging = 0;
6386         if (appData.animateDragging) {
6387             /* Undo animation damage if any */
6388             DrawPosition(FALSE, NULL);
6389         }
6390         if (second) {
6391             /* Second up/down in same square; just abort move */
6392             second = 0;
6393             fromX = fromY = -1;
6394             ClearHighlights();
6395             gotPremove = 0;
6396             ClearPremoveHighlights();
6397         } else {
6398             /* First upclick in same square; start click-click mode */
6399             SetHighlights(x, y, -1, -1);
6400         }
6401         return;
6402     }
6403
6404     /* we now have a different from- and (possibly off-board) to-square */
6405     /* Completed move */
6406     toX = x;
6407     toY = y;
6408     saveAnimate = appData.animate;
6409     if (clickType == Press) {
6410         /* Finish clickclick move */
6411         if (appData.animate || appData.highlightLastMove) {
6412             SetHighlights(fromX, fromY, toX, toY);
6413         } else {
6414             ClearHighlights();
6415         }
6416     } else {
6417         /* Finish drag move */
6418         if (appData.highlightLastMove) {
6419             SetHighlights(fromX, fromY, toX, toY);
6420         } else {
6421             ClearHighlights();
6422         }
6423         DragPieceEnd(xPix, yPix); dragging = 0;
6424         /* Don't animate move and drag both */
6425         appData.animate = FALSE;
6426     }
6427
6428     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6429     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6430         ChessSquare piece = boards[currentMove][fromY][fromX];
6431         if(gameMode == EditPosition && piece != EmptySquare &&
6432            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6433             int n;
6434              
6435             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6436                 n = PieceToNumber(piece - (int)BlackPawn);
6437                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6438                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6439                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6440             } else
6441             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6442                 n = PieceToNumber(piece);
6443                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6444                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6445                 boards[currentMove][n][BOARD_WIDTH-2]++;
6446             }
6447             boards[currentMove][fromY][fromX] = EmptySquare;
6448         }
6449         ClearHighlights();
6450         fromX = fromY = -1;
6451         DrawPosition(TRUE, boards[currentMove]);
6452         return;
6453     }
6454
6455     // off-board moves should not be highlighted
6456     if(x < 0 || x < 0) ClearHighlights();
6457
6458     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6459         SetHighlights(fromX, fromY, toX, toY);
6460         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6461             // [HGM] super: promotion to captured piece selected from holdings
6462             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6463             promotionChoice = TRUE;
6464             // kludge follows to temporarily execute move on display, without promoting yet
6465             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6466             boards[currentMove][toY][toX] = p;
6467             DrawPosition(FALSE, boards[currentMove]);
6468             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6469             boards[currentMove][toY][toX] = q;
6470             DisplayMessage("Click in holdings to choose piece", "");
6471             return;
6472         }
6473         PromotionPopUp();
6474     } else {
6475         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6476         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6477         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6478         fromX = fromY = -1;
6479     }
6480     appData.animate = saveAnimate;
6481     if (appData.animate || appData.animateDragging) {
6482         /* Undo animation damage if needed */
6483         DrawPosition(FALSE, NULL);
6484     }
6485 }
6486
6487 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6488 {   // front-end-free part taken out of PieceMenuPopup
6489     int whichMenu; int xSqr, ySqr;
6490
6491     if(seekGraphUp) { // [HGM] seekgraph
6492         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6493         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6494         return -2;
6495     }
6496
6497     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6498          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6499         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6500         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6501         if(action == Press)   {
6502             originalFlip = flipView;
6503             flipView = !flipView; // temporarily flip board to see game from partners perspective
6504             DrawPosition(TRUE, partnerBoard);
6505             DisplayMessage(partnerStatus, "");
6506             partnerUp = TRUE;
6507         } else if(action == Release) {
6508             flipView = originalFlip;
6509             DrawPosition(TRUE, boards[currentMove]);
6510             partnerUp = FALSE;
6511         }
6512         return -2;
6513     }
6514
6515     xSqr = EventToSquare(x, BOARD_WIDTH);
6516     ySqr = EventToSquare(y, BOARD_HEIGHT);
6517     if (action == Release) UnLoadPV(); // [HGM] pv
6518     if (action != Press) return -2; // return code to be ignored
6519     switch (gameMode) {
6520       case IcsExamining:
6521         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6522       case EditPosition:
6523         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6524         if (xSqr < 0 || ySqr < 0) return -1;\r
6525         whichMenu = 0; // edit-position menu
6526         break;
6527       case IcsObserving:
6528         if(!appData.icsEngineAnalyze) return -1;
6529       case IcsPlayingWhite:
6530       case IcsPlayingBlack:
6531         if(!appData.zippyPlay) goto noZip;
6532       case AnalyzeMode:
6533       case AnalyzeFile:
6534       case MachinePlaysWhite:
6535       case MachinePlaysBlack:
6536       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6537         if (!appData.dropMenu) {
6538           LoadPV(x, y);
6539           return 2; // flag front-end to grab mouse events
6540         }
6541         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6542            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6543       case EditGame:
6544       noZip:
6545         if (xSqr < 0 || ySqr < 0) return -1;
6546         if (!appData.dropMenu || appData.testLegality &&
6547             gameInfo.variant != VariantBughouse &&
6548             gameInfo.variant != VariantCrazyhouse) return -1;
6549         whichMenu = 1; // drop menu
6550         break;
6551       default:
6552         return -1;
6553     }
6554
6555     if (((*fromX = xSqr) < 0) ||
6556         ((*fromY = ySqr) < 0)) {
6557         *fromX = *fromY = -1;
6558         return -1;
6559     }
6560     if (flipView)
6561       *fromX = BOARD_WIDTH - 1 - *fromX;
6562     else
6563       *fromY = BOARD_HEIGHT - 1 - *fromY;
6564
6565     return whichMenu;
6566 }
6567
6568 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6569 {
6570 //    char * hint = lastHint;
6571     FrontEndProgramStats stats;
6572
6573     stats.which = cps == &first ? 0 : 1;
6574     stats.depth = cpstats->depth;
6575     stats.nodes = cpstats->nodes;
6576     stats.score = cpstats->score;
6577     stats.time = cpstats->time;
6578     stats.pv = cpstats->movelist;
6579     stats.hint = lastHint;
6580     stats.an_move_index = 0;
6581     stats.an_move_count = 0;
6582
6583     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6584         stats.hint = cpstats->move_name;
6585         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6586         stats.an_move_count = cpstats->nr_moves;
6587     }
6588
6589     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6590
6591     SetProgramStats( &stats );
6592 }
6593
6594 int
6595 Adjudicate(ChessProgramState *cps)
6596 {       // [HGM] some adjudications useful with buggy engines
6597         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6598         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6599         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6600         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6601         int k, count = 0; static int bare = 1;
6602         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6603         Boolean canAdjudicate = !appData.icsActive;
6604
6605         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6606         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6607             if( appData.testLegality )
6608             {   /* [HGM] Some more adjudications for obstinate engines */
6609                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6610                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6611                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6612                 static int moveCount = 6;
6613                 ChessMove result;
6614                 char *reason = NULL;
6615
6616                 /* Count what is on board. */
6617                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6618                 {   ChessSquare p = boards[forwardMostMove][i][j];
6619                     int m=i;
6620
6621                     switch((int) p)
6622                     {   /* count B,N,R and other of each side */
6623                         case WhiteKing:
6624                         case BlackKing:
6625                              NrK++; break; // [HGM] atomic: count Kings
6626                         case WhiteKnight:
6627                              NrWN++; break;
6628                         case WhiteBishop:
6629                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6630                              bishopsColor |= 1 << ((i^j)&1);
6631                              NrWB++; break;
6632                         case BlackKnight:
6633                              NrBN++; break;
6634                         case BlackBishop:
6635                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6636                              bishopsColor |= 1 << ((i^j)&1);
6637                              NrBB++; break;
6638                         case WhiteRook:
6639                              NrWR++; break;
6640                         case BlackRook:
6641                              NrBR++; break;
6642                         case WhiteQueen:
6643                              NrWQ++; break;
6644                         case BlackQueen:
6645                              NrBQ++; break;
6646                         case EmptySquare: 
6647                              break;
6648                         case BlackPawn:
6649                              m = 7-i;
6650                         case WhitePawn:
6651                              PawnAdvance += m; NrPawns++;
6652                     }
6653                     NrPieces += (p != EmptySquare);
6654                     NrW += ((int)p < (int)BlackPawn);
6655                     if(gameInfo.variant == VariantXiangqi && 
6656                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6657                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6658                         NrW -= ((int)p < (int)BlackPawn);
6659                     }
6660                 }
6661
6662                 /* Some material-based adjudications that have to be made before stalemate test */
6663                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6664                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6665                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6666                      if(canAdjudicate && appData.checkMates) {
6667                          if(engineOpponent)
6668                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6669                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6670                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6671                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6672                          return 1;
6673                      }
6674                 }
6675
6676                 /* Bare King in Shatranj (loses) or Losers (wins) */
6677                 if( NrW == 1 || NrPieces - NrW == 1) {
6678                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6679                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6680                      if(canAdjudicate && appData.checkMates) {
6681                          if(engineOpponent)
6682                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6683                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6684                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6685                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6686                          return 1;
6687                      }
6688                   } else
6689                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6690                   {    /* bare King */
6691                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6692                         if(canAdjudicate && appData.checkMates) {
6693                             /* but only adjudicate if adjudication enabled */
6694                             if(engineOpponent)
6695                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6696                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6698                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6699                             return 1;
6700                         }
6701                   }
6702                 } else bare = 1;
6703
6704
6705             // don't wait for engine to announce game end if we can judge ourselves
6706             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6707               case MT_CHECK:
6708                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6709                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6710                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6711                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6712                             checkCnt++;
6713                         if(checkCnt >= 2) {
6714                             reason = "Xboard adjudication: 3rd check";
6715                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6716                             break;
6717                         }
6718                     }
6719                 }
6720               case MT_NONE:
6721               default:
6722                 break;
6723               case MT_STALEMATE:
6724               case MT_STAINMATE:
6725                 reason = "Xboard adjudication: Stalemate";
6726                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6727                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6728                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6729                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6730                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6731                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6732                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6733                                                                         EP_CHECKMATE : EP_WINS);
6734                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6735                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6736                 }
6737                 break;
6738               case MT_CHECKMATE:
6739                 reason = "Xboard adjudication: Checkmate";
6740                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6741                 break;
6742             }
6743
6744                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6745                     case EP_STALEMATE:
6746                         result = GameIsDrawn; break;
6747                     case EP_CHECKMATE:
6748                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6749                     case EP_WINS:
6750                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6751                     default:
6752                         result = (ChessMove) 0;
6753                 }
6754                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6755                     if(engineOpponent)
6756                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6757                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6758                     GameEnds( result, reason, GE_XBOARD );
6759                     return 1;
6760                 }
6761
6762                 /* Next absolutely insufficient mating material. */
6763                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6764                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6765                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6766                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6767                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6768
6769                      /* always flag draws, for judging claims */
6770                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6771
6772                      if(canAdjudicate && appData.materialDraws) {
6773                          /* but only adjudicate them if adjudication enabled */
6774                          if(engineOpponent) {
6775                            SendToProgram("force\n", engineOpponent); // suppress reply
6776                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6777                          }
6778                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6779                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6780                          return 1;
6781                      }
6782                 }
6783
6784                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6785                 if(NrPieces == 4 && 
6786                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6787                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6788                    || NrWN==2 || NrBN==2     /* KNNK */
6789                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6790                   ) ) {
6791                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6792                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6793                           if(engineOpponent) {
6794                             SendToProgram("force\n", engineOpponent); // suppress reply
6795                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6796                           }
6797                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6798                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6799                           return 1;
6800                      }
6801                 } else moveCount = 6;
6802             }
6803         }
6804           
6805         if (appData.debugMode) { int i;
6806             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6807                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6808                     appData.drawRepeats);
6809             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6810               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6811             
6812         }
6813
6814         // Repetition draws and 50-move rule can be applied independently of legality testing
6815
6816                 /* Check for rep-draws */
6817                 count = 0;
6818                 for(k = forwardMostMove-2;
6819                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6820                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6821                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6822                     k-=2)
6823                 {   int rights=0;
6824                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6825                         /* compare castling rights */
6826                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6827                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6828                                 rights++; /* King lost rights, while rook still had them */
6829                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6830                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6831                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6832                                    rights++; /* but at least one rook lost them */
6833                         }
6834                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6835                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6836                                 rights++; 
6837                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6838                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6839                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6840                                    rights++;
6841                         }
6842                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6843                             && appData.drawRepeats > 1) {
6844                              /* adjudicate after user-specified nr of repeats */
6845                              int result = GameIsDrawn;
6846                              char *details = "XBoard adjudication: repetition draw";
6847                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6848                                 // [HGM] xiangqi: check for forbidden perpetuals
6849                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6850                                 for(m=forwardMostMove; m>k; m-=2) {
6851                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6852                                         ourPerpetual = 0; // the current mover did not always check
6853                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6854                                         hisPerpetual = 0; // the opponent did not always check
6855                                 }
6856                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6857                                                                         ourPerpetual, hisPerpetual);
6858                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6859                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6860                                     details = "Xboard adjudication: perpetual checking";
6861                                 } else
6862                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6863                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6864                                 } else
6865                                 // Now check for perpetual chases
6866                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6867                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6868                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6869                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6870                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6871                                         details = "Xboard adjudication: perpetual chasing";
6872                                     } else
6873                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6874                                         break; // Abort repetition-checking loop.
6875                                 }
6876                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6877                              }
6878                              if(engineOpponent) {
6879                                SendToProgram("force\n", engineOpponent); // suppress reply
6880                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6881                              }
6882                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6883                              GameEnds( result, details, GE_XBOARD );
6884                              return 1;
6885                         }
6886                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6887                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6888                     }
6889                 }
6890
6891                 /* Now we test for 50-move draws. Determine ply count */
6892                 count = forwardMostMove;
6893                 /* look for last irreversble move */
6894                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6895                     count--;
6896                 /* if we hit starting position, add initial plies */
6897                 if( count == backwardMostMove )
6898                     count -= initialRulePlies;
6899                 count = forwardMostMove - count; 
6900                 if( count >= 100)
6901                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6902                          /* this is used to judge if draw claims are legal */
6903                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6904                          if(engineOpponent) {
6905                            SendToProgram("force\n", engineOpponent); // suppress reply
6906                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6907                          }
6908                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6909                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6910                          return 1;
6911                 }
6912
6913                 /* if draw offer is pending, treat it as a draw claim
6914                  * when draw condition present, to allow engines a way to
6915                  * claim draws before making their move to avoid a race
6916                  * condition occurring after their move
6917                  */
6918                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6919                          char *p = NULL;
6920                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6921                              p = "Draw claim: 50-move rule";
6922                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6923                              p = "Draw claim: 3-fold repetition";
6924                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6925                              p = "Draw claim: insufficient mating material";
6926                          if( p != NULL && canAdjudicate) {
6927                              if(engineOpponent) {
6928                                SendToProgram("force\n", engineOpponent); // suppress reply
6929                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6930                              }
6931                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6932                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6933                              return 1;
6934                          }
6935                 }
6936
6937                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6938                     if(engineOpponent) {
6939                       SendToProgram("force\n", engineOpponent); // suppress reply
6940                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6941                     }
6942                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6943                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6944                     return 1;
6945                 }
6946         return 0;
6947 }
6948
6949 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6950 {   // [HGM] book: this routine intercepts moves to simulate book replies
6951     char *bookHit = NULL;
6952
6953     //first determine if the incoming move brings opponent into his book
6954     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6955         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6956     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6957     if(bookHit != NULL && !cps->bookSuspend) {
6958         // make sure opponent is not going to reply after receiving move to book position
6959         SendToProgram("force\n", cps);
6960         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6961     }
6962     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6963     // now arrange restart after book miss
6964     if(bookHit) {
6965         // after a book hit we never send 'go', and the code after the call to this routine
6966         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6967         char buf[MSG_SIZ];
6968         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6969         SendToProgram(buf, cps);
6970         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6971     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6972         SendToProgram("go\n", cps);
6973         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6974     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6975         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6976             SendToProgram("go\n", cps); 
6977         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6978     }
6979     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6980 }
6981
6982 char *savedMessage;
6983 ChessProgramState *savedState;
6984 void DeferredBookMove(void)
6985 {
6986         if(savedState->lastPing != savedState->lastPong)
6987                     ScheduleDelayedEvent(DeferredBookMove, 10);
6988         else
6989         HandleMachineMove(savedMessage, savedState);
6990 }
6991
6992 void
6993 HandleMachineMove(message, cps)
6994      char *message;
6995      ChessProgramState *cps;
6996 {
6997     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6998     char realname[MSG_SIZ];
6999     int fromX, fromY, toX, toY;
7000     ChessMove moveType;
7001     char promoChar;
7002     char *p;
7003     int machineWhite;
7004     char *bookHit;
7005
7006     cps->userError = 0;
7007
7008 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7009     /*
7010      * Kludge to ignore BEL characters
7011      */
7012     while (*message == '\007') message++;
7013
7014     /*
7015      * [HGM] engine debug message: ignore lines starting with '#' character
7016      */
7017     if(cps->debug && *message == '#') return;
7018
7019     /*
7020      * Look for book output
7021      */
7022     if (cps == &first && bookRequested) {
7023         if (message[0] == '\t' || message[0] == ' ') {
7024             /* Part of the book output is here; append it */
7025             strcat(bookOutput, message);
7026             strcat(bookOutput, "  \n");
7027             return;
7028         } else if (bookOutput[0] != NULLCHAR) {
7029             /* All of book output has arrived; display it */
7030             char *p = bookOutput;
7031             while (*p != NULLCHAR) {
7032                 if (*p == '\t') *p = ' ';
7033                 p++;
7034             }
7035             DisplayInformation(bookOutput);
7036             bookRequested = FALSE;
7037             /* Fall through to parse the current output */
7038         }
7039     }
7040
7041     /*
7042      * Look for machine move.
7043      */
7044     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7045         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7046     {
7047         /* This method is only useful on engines that support ping */
7048         if (cps->lastPing != cps->lastPong) {
7049           if (gameMode == BeginningOfGame) {
7050             /* Extra move from before last new; ignore */
7051             if (appData.debugMode) {
7052                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7053             }
7054           } else {
7055             if (appData.debugMode) {
7056                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7057                         cps->which, gameMode);
7058             }
7059
7060             SendToProgram("undo\n", cps);
7061           }
7062           return;
7063         }
7064
7065         switch (gameMode) {
7066           case BeginningOfGame:
7067             /* Extra move from before last reset; ignore */
7068             if (appData.debugMode) {
7069                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7070             }
7071             return;
7072
7073           case EndOfGame:
7074           case IcsIdle:
7075           default:
7076             /* Extra move after we tried to stop.  The mode test is
7077                not a reliable way of detecting this problem, but it's
7078                the best we can do on engines that don't support ping.
7079             */
7080             if (appData.debugMode) {
7081                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7082                         cps->which, gameMode);
7083             }
7084             SendToProgram("undo\n", cps);
7085             return;
7086
7087           case MachinePlaysWhite:
7088           case IcsPlayingWhite:
7089             machineWhite = TRUE;
7090             break;
7091
7092           case MachinePlaysBlack:
7093           case IcsPlayingBlack:
7094             machineWhite = FALSE;
7095             break;
7096
7097           case TwoMachinesPlay:
7098             machineWhite = (cps->twoMachinesColor[0] == 'w');
7099             break;
7100         }
7101         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7102             if (appData.debugMode) {
7103                 fprintf(debugFP,
7104                         "Ignoring move out of turn by %s, gameMode %d"
7105                         ", forwardMost %d\n",
7106                         cps->which, gameMode, forwardMostMove);
7107             }
7108             return;
7109         }
7110
7111     if (appData.debugMode) { int f = forwardMostMove;
7112         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7113                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7114                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7115     }
7116         if(cps->alphaRank) AlphaRank(machineMove, 4);
7117         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7118                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7119             /* Machine move could not be parsed; ignore it. */
7120             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7121                     machineMove, cps->which);
7122             DisplayError(buf1, 0);
7123             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7124                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7125             if (gameMode == TwoMachinesPlay) {
7126               GameEnds(machineWhite ? BlackWins : WhiteWins,
7127                        buf1, GE_XBOARD);
7128             }
7129             return;
7130         }
7131
7132         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7133         /* So we have to redo legality test with true e.p. status here,  */
7134         /* to make sure an illegal e.p. capture does not slip through,   */
7135         /* to cause a forfeit on a justified illegal-move complaint      */
7136         /* of the opponent.                                              */
7137         if( gameMode==TwoMachinesPlay && appData.testLegality
7138             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7139                                                               ) {
7140            ChessMove moveType;
7141            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7142                              fromY, fromX, toY, toX, promoChar);
7143             if (appData.debugMode) {
7144                 int i;
7145                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7146                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7147                 fprintf(debugFP, "castling rights\n");
7148             }
7149             if(moveType == IllegalMove) {
7150                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7151                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7152                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7153                            buf1, GE_XBOARD);
7154                 return;
7155            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7156            /* [HGM] Kludge to handle engines that send FRC-style castling
7157               when they shouldn't (like TSCP-Gothic) */
7158            switch(moveType) {
7159              case WhiteASideCastleFR:
7160              case BlackASideCastleFR:
7161                toX+=2;
7162                currentMoveString[2]++;
7163                break;
7164              case WhiteHSideCastleFR:
7165              case BlackHSideCastleFR:
7166                toX--;
7167                currentMoveString[2]--;
7168                break;
7169              default: ; // nothing to do, but suppresses warning of pedantic compilers
7170            }
7171         }
7172         hintRequested = FALSE;
7173         lastHint[0] = NULLCHAR;
7174         bookRequested = FALSE;
7175         /* Program may be pondering now */
7176         cps->maybeThinking = TRUE;
7177         if (cps->sendTime == 2) cps->sendTime = 1;
7178         if (cps->offeredDraw) cps->offeredDraw--;
7179
7180         /* currentMoveString is set as a side-effect of ParseOneMove */
7181         strcpy(machineMove, currentMoveString);
7182         strcat(machineMove, "\n");
7183         strcpy(moveList[forwardMostMove], machineMove);
7184
7185         /* [AS] Save move info*/
7186         pvInfoList[ forwardMostMove ].score = programStats.score;
7187         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7188         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7189
7190         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7191
7192         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7193         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7194             int count = 0;
7195
7196             while( count < adjudicateLossPlies ) {
7197                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7198
7199                 if( count & 1 ) {
7200                     score = -score; /* Flip score for winning side */
7201                 }
7202
7203                 if( score > adjudicateLossThreshold ) {
7204                     break;
7205                 }
7206
7207                 count++;
7208             }
7209
7210             if( count >= adjudicateLossPlies ) {
7211                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7212
7213                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7214                     "Xboard adjudication", 
7215                     GE_XBOARD );
7216
7217                 return;
7218             }
7219         }
7220
7221         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7222
7223 #if ZIPPY
7224         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7225             first.initDone) {
7226           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7227                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7228                 SendToICS("draw ");
7229                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7230           }
7231           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7232           ics_user_moved = 1;
7233           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7234                 char buf[3*MSG_SIZ];
7235
7236                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7237                         programStats.score / 100.,
7238                         programStats.depth,
7239                         programStats.time / 100.,
7240                         (unsigned int)programStats.nodes,
7241                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7242                         programStats.movelist);
7243                 SendToICS(buf);
7244 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7245           }
7246         }
7247 #endif
7248
7249         /* [AS] Clear stats for next move */
7250         ClearProgramStats();
7251         thinkOutput[0] = NULLCHAR;
7252         hiddenThinkOutputState = 0;
7253
7254         bookHit = NULL;
7255         if (gameMode == TwoMachinesPlay) {
7256             /* [HGM] relaying draw offers moved to after reception of move */
7257             /* and interpreting offer as claim if it brings draw condition */
7258             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7259                 SendToProgram("draw\n", cps->other);
7260             }
7261             if (cps->other->sendTime) {
7262                 SendTimeRemaining(cps->other,
7263                                   cps->other->twoMachinesColor[0] == 'w');
7264             }
7265             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7266             if (firstMove && !bookHit) {
7267                 firstMove = FALSE;
7268                 if (cps->other->useColors) {
7269                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7270                 }
7271                 SendToProgram("go\n", cps->other);
7272             }
7273             cps->other->maybeThinking = TRUE;
7274         }
7275
7276         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7277         
7278         if (!pausing && appData.ringBellAfterMoves) {
7279             RingBell();
7280         }
7281
7282         /* 
7283          * Reenable menu items that were disabled while
7284          * machine was thinking
7285          */
7286         if (gameMode != TwoMachinesPlay)
7287             SetUserThinkingEnables();
7288
7289         // [HGM] book: after book hit opponent has received move and is now in force mode
7290         // force the book reply into it, and then fake that it outputted this move by jumping
7291         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7292         if(bookHit) {
7293                 static char bookMove[MSG_SIZ]; // a bit generous?
7294
7295                 strcpy(bookMove, "move ");
7296                 strcat(bookMove, bookHit);
7297                 message = bookMove;
7298                 cps = cps->other;
7299                 programStats.nodes = programStats.depth = programStats.time = 
7300                 programStats.score = programStats.got_only_move = 0;
7301                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7302
7303                 if(cps->lastPing != cps->lastPong) {
7304                     savedMessage = message; // args for deferred call
7305                     savedState = cps;
7306                     ScheduleDelayedEvent(DeferredBookMove, 10);
7307                     return;
7308                 }
7309                 goto FakeBookMove;
7310         }
7311
7312         return;
7313     }
7314
7315     /* Set special modes for chess engines.  Later something general
7316      *  could be added here; for now there is just one kludge feature,
7317      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7318      *  when "xboard" is given as an interactive command.
7319      */
7320     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7321         cps->useSigint = FALSE;
7322         cps->useSigterm = FALSE;
7323     }
7324     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7325       ParseFeatures(message+8, cps);
7326       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7327     }
7328
7329     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7330      * want this, I was asked to put it in, and obliged.
7331      */
7332     if (!strncmp(message, "setboard ", 9)) {
7333         Board initial_position;
7334
7335         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7336
7337         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7338             DisplayError(_("Bad FEN received from engine"), 0);
7339             return ;
7340         } else {
7341            Reset(TRUE, FALSE);
7342            CopyBoard(boards[0], initial_position);
7343            initialRulePlies = FENrulePlies;
7344            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7345            else gameMode = MachinePlaysBlack;                 
7346            DrawPosition(FALSE, boards[currentMove]);
7347         }
7348         return;
7349     }
7350
7351     /*
7352      * Look for communication commands
7353      */
7354     if (!strncmp(message, "telluser ", 9)) {
7355         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7356         DisplayNote(message + 9);
7357         return;
7358     }
7359     if (!strncmp(message, "tellusererror ", 14)) {
7360         cps->userError = 1;
7361         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7362         DisplayError(message + 14, 0);
7363         return;
7364     }
7365     if (!strncmp(message, "tellopponent ", 13)) {
7366       if (appData.icsActive) {
7367         if (loggedOn) {
7368           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7369           SendToICS(buf1);
7370         }
7371       } else {
7372         DisplayNote(message + 13);
7373       }
7374       return;
7375     }
7376     if (!strncmp(message, "tellothers ", 11)) {
7377       if (appData.icsActive) {
7378         if (loggedOn) {
7379           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7380           SendToICS(buf1);
7381         }
7382       }
7383       return;
7384     }
7385     if (!strncmp(message, "tellall ", 8)) {
7386       if (appData.icsActive) {
7387         if (loggedOn) {
7388           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7389           SendToICS(buf1);
7390         }
7391       } else {
7392         DisplayNote(message + 8);
7393       }
7394       return;
7395     }
7396     if (strncmp(message, "warning", 7) == 0) {
7397         /* Undocumented feature, use tellusererror in new code */
7398         DisplayError(message, 0);
7399         return;
7400     }
7401     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7402         strcpy(realname, cps->tidy);
7403         strcat(realname, " query");
7404         AskQuestion(realname, buf2, buf1, cps->pr);
7405         return;
7406     }
7407     /* Commands from the engine directly to ICS.  We don't allow these to be 
7408      *  sent until we are logged on. Crafty kibitzes have been known to 
7409      *  interfere with the login process.
7410      */
7411     if (loggedOn) {
7412         if (!strncmp(message, "tellics ", 8)) {
7413             SendToICS(message + 8);
7414             SendToICS("\n");
7415             return;
7416         }
7417         if (!strncmp(message, "tellicsnoalias ", 15)) {
7418             SendToICS(ics_prefix);
7419             SendToICS(message + 15);
7420             SendToICS("\n");
7421             return;
7422         }
7423         /* The following are for backward compatibility only */
7424         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7425             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7426             SendToICS(ics_prefix);
7427             SendToICS(message);
7428             SendToICS("\n");
7429             return;
7430         }
7431     }
7432     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7433         return;
7434     }
7435     /*
7436      * If the move is illegal, cancel it and redraw the board.
7437      * Also deal with other error cases.  Matching is rather loose
7438      * here to accommodate engines written before the spec.
7439      */
7440     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7441         strncmp(message, "Error", 5) == 0) {
7442         if (StrStr(message, "name") || 
7443             StrStr(message, "rating") || StrStr(message, "?") ||
7444             StrStr(message, "result") || StrStr(message, "board") ||
7445             StrStr(message, "bk") || StrStr(message, "computer") ||
7446             StrStr(message, "variant") || StrStr(message, "hint") ||
7447             StrStr(message, "random") || StrStr(message, "depth") ||
7448             StrStr(message, "accepted")) {
7449             return;
7450         }
7451         if (StrStr(message, "protover")) {
7452           /* Program is responding to input, so it's apparently done
7453              initializing, and this error message indicates it is
7454              protocol version 1.  So we don't need to wait any longer
7455              for it to initialize and send feature commands. */
7456           FeatureDone(cps, 1);
7457           cps->protocolVersion = 1;
7458           return;
7459         }
7460         cps->maybeThinking = FALSE;
7461
7462         if (StrStr(message, "draw")) {
7463             /* Program doesn't have "draw" command */
7464             cps->sendDrawOffers = 0;
7465             return;
7466         }
7467         if (cps->sendTime != 1 &&
7468             (StrStr(message, "time") || StrStr(message, "otim"))) {
7469           /* Program apparently doesn't have "time" or "otim" command */
7470           cps->sendTime = 0;
7471           return;
7472         }
7473         if (StrStr(message, "analyze")) {
7474             cps->analysisSupport = FALSE;
7475             cps->analyzing = FALSE;
7476             Reset(FALSE, TRUE);
7477             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7478             DisplayError(buf2, 0);
7479             return;
7480         }
7481         if (StrStr(message, "(no matching move)st")) {
7482           /* Special kludge for GNU Chess 4 only */
7483           cps->stKludge = TRUE;
7484           SendTimeControl(cps, movesPerSession, timeControl,
7485                           timeIncrement, appData.searchDepth,
7486                           searchTime);
7487           return;
7488         }
7489         if (StrStr(message, "(no matching move)sd")) {
7490           /* Special kludge for GNU Chess 4 only */
7491           cps->sdKludge = TRUE;
7492           SendTimeControl(cps, movesPerSession, timeControl,
7493                           timeIncrement, appData.searchDepth,
7494                           searchTime);
7495           return;
7496         }
7497         if (!StrStr(message, "llegal")) {
7498             return;
7499         }
7500         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7501             gameMode == IcsIdle) return;
7502         if (forwardMostMove <= backwardMostMove) return;
7503         if (pausing) PauseEvent();
7504       if(appData.forceIllegal) {
7505             // [HGM] illegal: machine refused move; force position after move into it
7506           SendToProgram("force\n", cps);
7507           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7508                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7509                 // when black is to move, while there might be nothing on a2 or black
7510                 // might already have the move. So send the board as if white has the move.
7511                 // But first we must change the stm of the engine, as it refused the last move
7512                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7513                 if(WhiteOnMove(forwardMostMove)) {
7514                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7515                     SendBoard(cps, forwardMostMove); // kludgeless board
7516                 } else {
7517                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7518                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7519                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7520                 }
7521           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7522             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7523                  gameMode == TwoMachinesPlay)
7524               SendToProgram("go\n", cps);
7525             return;
7526       } else
7527         if (gameMode == PlayFromGameFile) {
7528             /* Stop reading this game file */
7529             gameMode = EditGame;
7530             ModeHighlight();
7531         }
7532         currentMove = forwardMostMove-1;
7533         DisplayMove(currentMove-1); /* before DisplayMoveError */
7534         SwitchClocks(forwardMostMove-1); // [HGM] race
7535         DisplayBothClocks();
7536         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7537                 parseList[currentMove], cps->which);
7538         DisplayMoveError(buf1);
7539         DrawPosition(FALSE, boards[currentMove]);
7540
7541         /* [HGM] illegal-move claim should forfeit game when Xboard */
7542         /* only passes fully legal moves                            */
7543         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7544             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7545                                 "False illegal-move claim", GE_XBOARD );
7546         }
7547         return;
7548     }
7549     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7550         /* Program has a broken "time" command that
7551            outputs a string not ending in newline.
7552            Don't use it. */
7553         cps->sendTime = 0;
7554     }
7555     
7556     /*
7557      * If chess program startup fails, exit with an error message.
7558      * Attempts to recover here are futile.
7559      */
7560     if ((StrStr(message, "unknown host") != NULL)
7561         || (StrStr(message, "No remote directory") != NULL)
7562         || (StrStr(message, "not found") != NULL)
7563         || (StrStr(message, "No such file") != NULL)
7564         || (StrStr(message, "can't alloc") != NULL)
7565         || (StrStr(message, "Permission denied") != NULL)) {
7566
7567         cps->maybeThinking = FALSE;
7568         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7569                 cps->which, cps->program, cps->host, message);
7570         RemoveInputSource(cps->isr);
7571         DisplayFatalError(buf1, 0, 1);
7572         return;
7573     }
7574     
7575     /* 
7576      * Look for hint output
7577      */
7578     if (sscanf(message, "Hint: %s", buf1) == 1) {
7579         if (cps == &first && hintRequested) {
7580             hintRequested = FALSE;
7581             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7582                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7583                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7584                                     PosFlags(forwardMostMove),
7585                                     fromY, fromX, toY, toX, promoChar, buf1);
7586                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7587                 DisplayInformation(buf2);
7588             } else {
7589                 /* Hint move could not be parsed!? */
7590               snprintf(buf2, sizeof(buf2),
7591                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7592                         buf1, cps->which);
7593                 DisplayError(buf2, 0);
7594             }
7595         } else {
7596             strcpy(lastHint, buf1);
7597         }
7598         return;
7599     }
7600
7601     /*
7602      * Ignore other messages if game is not in progress
7603      */
7604     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7605         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7606
7607     /*
7608      * look for win, lose, draw, or draw offer
7609      */
7610     if (strncmp(message, "1-0", 3) == 0) {
7611         char *p, *q, *r = "";
7612         p = strchr(message, '{');
7613         if (p) {
7614             q = strchr(p, '}');
7615             if (q) {
7616                 *q = NULLCHAR;
7617                 r = p + 1;
7618             }
7619         }
7620         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7621         return;
7622     } else if (strncmp(message, "0-1", 3) == 0) {
7623         char *p, *q, *r = "";
7624         p = strchr(message, '{');
7625         if (p) {
7626             q = strchr(p, '}');
7627             if (q) {
7628                 *q = NULLCHAR;
7629                 r = p + 1;
7630             }
7631         }
7632         /* Kludge for Arasan 4.1 bug */
7633         if (strcmp(r, "Black resigns") == 0) {
7634             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7635             return;
7636         }
7637         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7638         return;
7639     } else if (strncmp(message, "1/2", 3) == 0) {
7640         char *p, *q, *r = "";
7641         p = strchr(message, '{');
7642         if (p) {
7643             q = strchr(p, '}');
7644             if (q) {
7645                 *q = NULLCHAR;
7646                 r = p + 1;
7647             }
7648         }
7649             
7650         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7651         return;
7652
7653     } else if (strncmp(message, "White resign", 12) == 0) {
7654         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7655         return;
7656     } else if (strncmp(message, "Black resign", 12) == 0) {
7657         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7658         return;
7659     } else if (strncmp(message, "White matches", 13) == 0 ||
7660                strncmp(message, "Black matches", 13) == 0   ) {
7661         /* [HGM] ignore GNUShogi noises */
7662         return;
7663     } else if (strncmp(message, "White", 5) == 0 &&
7664                message[5] != '(' &&
7665                StrStr(message, "Black") == NULL) {
7666         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7667         return;
7668     } else if (strncmp(message, "Black", 5) == 0 &&
7669                message[5] != '(') {
7670         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7671         return;
7672     } else if (strcmp(message, "resign") == 0 ||
7673                strcmp(message, "computer resigns") == 0) {
7674         switch (gameMode) {
7675           case MachinePlaysBlack:
7676           case IcsPlayingBlack:
7677             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7678             break;
7679           case MachinePlaysWhite:
7680           case IcsPlayingWhite:
7681             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7682             break;
7683           case TwoMachinesPlay:
7684             if (cps->twoMachinesColor[0] == 'w')
7685               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7686             else
7687               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7688             break;
7689           default:
7690             /* can't happen */
7691             break;
7692         }
7693         return;
7694     } else if (strncmp(message, "opponent mates", 14) == 0) {
7695         switch (gameMode) {
7696           case MachinePlaysBlack:
7697           case IcsPlayingBlack:
7698             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7699             break;
7700           case MachinePlaysWhite:
7701           case IcsPlayingWhite:
7702             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7703             break;
7704           case TwoMachinesPlay:
7705             if (cps->twoMachinesColor[0] == 'w')
7706               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7707             else
7708               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7709             break;
7710           default:
7711             /* can't happen */
7712             break;
7713         }
7714         return;
7715     } else if (strncmp(message, "computer mates", 14) == 0) {
7716         switch (gameMode) {
7717           case MachinePlaysBlack:
7718           case IcsPlayingBlack:
7719             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7720             break;
7721           case MachinePlaysWhite:
7722           case IcsPlayingWhite:
7723             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7724             break;
7725           case TwoMachinesPlay:
7726             if (cps->twoMachinesColor[0] == 'w')
7727               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7728             else
7729               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7730             break;
7731           default:
7732             /* can't happen */
7733             break;
7734         }
7735         return;
7736     } else if (strncmp(message, "checkmate", 9) == 0) {
7737         if (WhiteOnMove(forwardMostMove)) {
7738             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7739         } else {
7740             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7741         }
7742         return;
7743     } else if (strstr(message, "Draw") != NULL ||
7744                strstr(message, "game is a draw") != NULL) {
7745         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7746         return;
7747     } else if (strstr(message, "offer") != NULL &&
7748                strstr(message, "draw") != NULL) {
7749 #if ZIPPY
7750         if (appData.zippyPlay && first.initDone) {
7751             /* Relay offer to ICS */
7752             SendToICS(ics_prefix);
7753             SendToICS("draw\n");
7754         }
7755 #endif
7756         cps->offeredDraw = 2; /* valid until this engine moves twice */
7757         if (gameMode == TwoMachinesPlay) {
7758             if (cps->other->offeredDraw) {
7759                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7760             /* [HGM] in two-machine mode we delay relaying draw offer      */
7761             /* until after we also have move, to see if it is really claim */
7762             }
7763         } else if (gameMode == MachinePlaysWhite ||
7764                    gameMode == MachinePlaysBlack) {
7765           if (userOfferedDraw) {
7766             DisplayInformation(_("Machine accepts your draw offer"));
7767             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7768           } else {
7769             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7770           }
7771         }
7772     }
7773
7774     
7775     /*
7776      * Look for thinking output
7777      */
7778     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7779           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7780                                 ) {
7781         int plylev, mvleft, mvtot, curscore, time;
7782         char mvname[MOVE_LEN];
7783         u64 nodes; // [DM]
7784         char plyext;
7785         int ignore = FALSE;
7786         int prefixHint = FALSE;
7787         mvname[0] = NULLCHAR;
7788
7789         switch (gameMode) {
7790           case MachinePlaysBlack:
7791           case IcsPlayingBlack:
7792             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7793             break;
7794           case MachinePlaysWhite:
7795           case IcsPlayingWhite:
7796             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7797             break;
7798           case AnalyzeMode:
7799           case AnalyzeFile:
7800             break;
7801           case IcsObserving: /* [DM] icsEngineAnalyze */
7802             if (!appData.icsEngineAnalyze) ignore = TRUE;
7803             break;
7804           case TwoMachinesPlay:
7805             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7806                 ignore = TRUE;
7807             }
7808             break;
7809           default:
7810             ignore = TRUE;
7811             break;
7812         }
7813
7814         if (!ignore) {
7815             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7816             buf1[0] = NULLCHAR;
7817             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7818                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7819
7820                 if (plyext != ' ' && plyext != '\t') {
7821                     time *= 100;
7822                 }
7823
7824                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7825                 if( cps->scoreIsAbsolute && 
7826                     ( gameMode == MachinePlaysBlack ||
7827                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7828                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7829                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7830                      !WhiteOnMove(currentMove)
7831                     ) )
7832                 {
7833                     curscore = -curscore;
7834                 }
7835
7836
7837                 tempStats.depth = plylev;
7838                 tempStats.nodes = nodes;
7839                 tempStats.time = time;
7840                 tempStats.score = curscore;
7841                 tempStats.got_only_move = 0;
7842
7843                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7844                         int ticklen;
7845
7846                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7847                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7848                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7849                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7850                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7851                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7852                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7853                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7854                 }
7855
7856                 /* Buffer overflow protection */
7857                 if (buf1[0] != NULLCHAR) {
7858                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7859                         && appData.debugMode) {
7860                         fprintf(debugFP,
7861                                 "PV is too long; using the first %u bytes.\n",
7862                                 (unsigned) sizeof(tempStats.movelist) - 1);
7863                     }
7864
7865                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7866                 } else {
7867                     sprintf(tempStats.movelist, " no PV\n");
7868                 }
7869
7870                 if (tempStats.seen_stat) {
7871                     tempStats.ok_to_send = 1;
7872                 }
7873
7874                 if (strchr(tempStats.movelist, '(') != NULL) {
7875                     tempStats.line_is_book = 1;
7876                     tempStats.nr_moves = 0;
7877                     tempStats.moves_left = 0;
7878                 } else {
7879                     tempStats.line_is_book = 0;
7880                 }
7881
7882                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7883                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7884
7885                 SendProgramStatsToFrontend( cps, &tempStats );
7886
7887                 /* 
7888                     [AS] Protect the thinkOutput buffer from overflow... this
7889                     is only useful if buf1 hasn't overflowed first!
7890                 */
7891                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7892                         plylev, 
7893                         (gameMode == TwoMachinesPlay ?
7894                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7895                         ((double) curscore) / 100.0,
7896                         prefixHint ? lastHint : "",
7897                         prefixHint ? " " : "" );
7898
7899                 if( buf1[0] != NULLCHAR ) {
7900                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7901
7902                     if( strlen(buf1) > max_len ) {
7903                         if( appData.debugMode) {
7904                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7905                         }
7906                         buf1[max_len+1] = '\0';
7907                     }
7908
7909                     strcat( thinkOutput, buf1 );
7910                 }
7911
7912                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7913                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7914                     DisplayMove(currentMove - 1);
7915                 }
7916                 return;
7917
7918             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7919                 /* crafty (9.25+) says "(only move) <move>"
7920                  * if there is only 1 legal move
7921                  */
7922                 sscanf(p, "(only move) %s", buf1);
7923                 sprintf(thinkOutput, "%s (only move)", buf1);
7924                 sprintf(programStats.movelist, "%s (only move)", buf1);
7925                 programStats.depth = 1;
7926                 programStats.nr_moves = 1;
7927                 programStats.moves_left = 1;
7928                 programStats.nodes = 1;
7929                 programStats.time = 1;
7930                 programStats.got_only_move = 1;
7931
7932                 /* Not really, but we also use this member to
7933                    mean "line isn't going to change" (Crafty
7934                    isn't searching, so stats won't change) */
7935                 programStats.line_is_book = 1;
7936
7937                 SendProgramStatsToFrontend( cps, &programStats );
7938                 
7939                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7940                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7941                     DisplayMove(currentMove - 1);
7942                 }
7943                 return;
7944             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7945                               &time, &nodes, &plylev, &mvleft,
7946                               &mvtot, mvname) >= 5) {
7947                 /* The stat01: line is from Crafty (9.29+) in response
7948                    to the "." command */
7949                 programStats.seen_stat = 1;
7950                 cps->maybeThinking = TRUE;
7951
7952                 if (programStats.got_only_move || !appData.periodicUpdates)
7953                   return;
7954
7955                 programStats.depth = plylev;
7956                 programStats.time = time;
7957                 programStats.nodes = nodes;
7958                 programStats.moves_left = mvleft;
7959                 programStats.nr_moves = mvtot;
7960                 strcpy(programStats.move_name, mvname);
7961                 programStats.ok_to_send = 1;
7962                 programStats.movelist[0] = '\0';
7963
7964                 SendProgramStatsToFrontend( cps, &programStats );
7965
7966                 return;
7967
7968             } else if (strncmp(message,"++",2) == 0) {
7969                 /* Crafty 9.29+ outputs this */
7970                 programStats.got_fail = 2;
7971                 return;
7972
7973             } else if (strncmp(message,"--",2) == 0) {
7974                 /* Crafty 9.29+ outputs this */
7975                 programStats.got_fail = 1;
7976                 return;
7977
7978             } else if (thinkOutput[0] != NULLCHAR &&
7979                        strncmp(message, "    ", 4) == 0) {
7980                 unsigned message_len;
7981
7982                 p = message;
7983                 while (*p && *p == ' ') p++;
7984
7985                 message_len = strlen( p );
7986
7987                 /* [AS] Avoid buffer overflow */
7988                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7989                     strcat(thinkOutput, " ");
7990                     strcat(thinkOutput, p);
7991                 }
7992
7993                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7994                     strcat(programStats.movelist, " ");
7995                     strcat(programStats.movelist, p);
7996                 }
7997
7998                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7999                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8000                     DisplayMove(currentMove - 1);
8001                 }
8002                 return;
8003             }
8004         }
8005         else {
8006             buf1[0] = NULLCHAR;
8007
8008             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8009                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8010             {
8011                 ChessProgramStats cpstats;
8012
8013                 if (plyext != ' ' && plyext != '\t') {
8014                     time *= 100;
8015                 }
8016
8017                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8018                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8019                     curscore = -curscore;
8020                 }
8021
8022                 cpstats.depth = plylev;
8023                 cpstats.nodes = nodes;
8024                 cpstats.time = time;
8025                 cpstats.score = curscore;
8026                 cpstats.got_only_move = 0;
8027                 cpstats.movelist[0] = '\0';
8028
8029                 if (buf1[0] != NULLCHAR) {
8030                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8031                 }
8032
8033                 cpstats.ok_to_send = 0;
8034                 cpstats.line_is_book = 0;
8035                 cpstats.nr_moves = 0;
8036                 cpstats.moves_left = 0;
8037
8038                 SendProgramStatsToFrontend( cps, &cpstats );
8039             }
8040         }
8041     }
8042 }
8043
8044
8045 /* Parse a game score from the character string "game", and
8046    record it as the history of the current game.  The game
8047    score is NOT assumed to start from the standard position. 
8048    The display is not updated in any way.
8049    */
8050 void
8051 ParseGameHistory(game)
8052      char *game;
8053 {
8054     ChessMove moveType;
8055     int fromX, fromY, toX, toY, boardIndex;
8056     char promoChar;
8057     char *p, *q;
8058     char buf[MSG_SIZ];
8059
8060     if (appData.debugMode)
8061       fprintf(debugFP, "Parsing game history: %s\n", game);
8062
8063     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8064     gameInfo.site = StrSave(appData.icsHost);
8065     gameInfo.date = PGNDate();
8066     gameInfo.round = StrSave("-");
8067
8068     /* Parse out names of players */
8069     while (*game == ' ') game++;
8070     p = buf;
8071     while (*game != ' ') *p++ = *game++;
8072     *p = NULLCHAR;
8073     gameInfo.white = StrSave(buf);
8074     while (*game == ' ') game++;
8075     p = buf;
8076     while (*game != ' ' && *game != '\n') *p++ = *game++;
8077     *p = NULLCHAR;
8078     gameInfo.black = StrSave(buf);
8079
8080     /* Parse moves */
8081     boardIndex = blackPlaysFirst ? 1 : 0;
8082     yynewstr(game);
8083     for (;;) {
8084         yyboardindex = boardIndex;
8085         moveType = (ChessMove) yylex();
8086         switch (moveType) {
8087           case IllegalMove:             /* maybe suicide chess, etc. */
8088   if (appData.debugMode) {
8089     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8090     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8091     setbuf(debugFP, NULL);
8092   }
8093           case WhitePromotionChancellor:
8094           case BlackPromotionChancellor:
8095           case WhitePromotionArchbishop:
8096           case BlackPromotionArchbishop:
8097           case WhitePromotionQueen:
8098           case BlackPromotionQueen:
8099           case WhitePromotionRook:
8100           case BlackPromotionRook:
8101           case WhitePromotionBishop:
8102           case BlackPromotionBishop:
8103           case WhitePromotionKnight:
8104           case BlackPromotionKnight:
8105           case WhitePromotionKing:
8106           case BlackPromotionKing:
8107           case NormalMove:
8108           case WhiteCapturesEnPassant:
8109           case BlackCapturesEnPassant:
8110           case WhiteKingSideCastle:
8111           case WhiteQueenSideCastle:
8112           case BlackKingSideCastle:
8113           case BlackQueenSideCastle:
8114           case WhiteKingSideCastleWild:
8115           case WhiteQueenSideCastleWild:
8116           case BlackKingSideCastleWild:
8117           case BlackQueenSideCastleWild:
8118           /* PUSH Fabien */
8119           case WhiteHSideCastleFR:
8120           case WhiteASideCastleFR:
8121           case BlackHSideCastleFR:
8122           case BlackASideCastleFR:
8123           /* POP Fabien */
8124             fromX = currentMoveString[0] - AAA;
8125             fromY = currentMoveString[1] - ONE;
8126             toX = currentMoveString[2] - AAA;
8127             toY = currentMoveString[3] - ONE;
8128             promoChar = currentMoveString[4];
8129             break;
8130           case WhiteDrop:
8131           case BlackDrop:
8132             fromX = moveType == WhiteDrop ?
8133               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8134             (int) CharToPiece(ToLower(currentMoveString[0]));
8135             fromY = DROP_RANK;
8136             toX = currentMoveString[2] - AAA;
8137             toY = currentMoveString[3] - ONE;
8138             promoChar = NULLCHAR;
8139             break;
8140           case AmbiguousMove:
8141             /* bug? */
8142             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8143   if (appData.debugMode) {
8144     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8145     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8146     setbuf(debugFP, NULL);
8147   }
8148             DisplayError(buf, 0);
8149             return;
8150           case ImpossibleMove:
8151             /* bug? */
8152             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8153   if (appData.debugMode) {
8154     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8155     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8156     setbuf(debugFP, NULL);
8157   }
8158             DisplayError(buf, 0);
8159             return;
8160           case (ChessMove) 0:   /* end of file */
8161             if (boardIndex < backwardMostMove) {
8162                 /* Oops, gap.  How did that happen? */
8163                 DisplayError(_("Gap in move list"), 0);
8164                 return;
8165             }
8166             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8167             if (boardIndex > forwardMostMove) {
8168                 forwardMostMove = boardIndex;
8169             }
8170             return;
8171           case ElapsedTime:
8172             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8173                 strcat(parseList[boardIndex-1], " ");
8174                 strcat(parseList[boardIndex-1], yy_text);
8175             }
8176             continue;
8177           case Comment:
8178           case PGNTag:
8179           case NAG:
8180           default:
8181             /* ignore */
8182             continue;
8183           case WhiteWins:
8184           case BlackWins:
8185           case GameIsDrawn:
8186           case GameUnfinished:
8187             if (gameMode == IcsExamining) {
8188                 if (boardIndex < backwardMostMove) {
8189                     /* Oops, gap.  How did that happen? */
8190                     return;
8191                 }
8192                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8193                 return;
8194             }
8195             gameInfo.result = moveType;
8196             p = strchr(yy_text, '{');
8197             if (p == NULL) p = strchr(yy_text, '(');
8198             if (p == NULL) {
8199                 p = yy_text;
8200                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8201             } else {
8202                 q = strchr(p, *p == '{' ? '}' : ')');
8203                 if (q != NULL) *q = NULLCHAR;
8204                 p++;
8205             }
8206             gameInfo.resultDetails = StrSave(p);
8207             continue;
8208         }
8209         if (boardIndex >= forwardMostMove &&
8210             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8211             backwardMostMove = blackPlaysFirst ? 1 : 0;
8212             return;
8213         }
8214         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8215                                  fromY, fromX, toY, toX, promoChar,
8216                                  parseList[boardIndex]);
8217         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8218         /* currentMoveString is set as a side-effect of yylex */
8219         strcpy(moveList[boardIndex], currentMoveString);
8220         strcat(moveList[boardIndex], "\n");
8221         boardIndex++;
8222         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8223         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8224           case MT_NONE:
8225           case MT_STALEMATE:
8226           default:
8227             break;
8228           case MT_CHECK:
8229             if(gameInfo.variant != VariantShogi)
8230                 strcat(parseList[boardIndex - 1], "+");
8231             break;
8232           case MT_CHECKMATE:
8233           case MT_STAINMATE:
8234             strcat(parseList[boardIndex - 1], "#");
8235             break;
8236         }
8237     }
8238 }
8239
8240
8241 /* Apply a move to the given board  */
8242 void
8243 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8244      int fromX, fromY, toX, toY;
8245      int promoChar;
8246      Board board;
8247 {
8248   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8249   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8250
8251     /* [HGM] compute & store e.p. status and castling rights for new position */
8252     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8253     { int i;
8254
8255       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8256       oldEP = (signed char)board[EP_STATUS];
8257       board[EP_STATUS] = EP_NONE;
8258
8259       if( board[toY][toX] != EmptySquare ) 
8260            board[EP_STATUS] = EP_CAPTURE;  
8261
8262       if( board[fromY][fromX] == WhitePawn ) {
8263            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8264                board[EP_STATUS] = EP_PAWN_MOVE;
8265            if( toY-fromY==2) {
8266                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8267                         gameInfo.variant != VariantBerolina || toX < fromX)
8268                       board[EP_STATUS] = toX | berolina;
8269                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8270                         gameInfo.variant != VariantBerolina || toX > fromX) 
8271                       board[EP_STATUS] = toX;
8272            }
8273       } else 
8274       if( board[fromY][fromX] == BlackPawn ) {
8275            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8276                board[EP_STATUS] = EP_PAWN_MOVE; 
8277            if( toY-fromY== -2) {
8278                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8279                         gameInfo.variant != VariantBerolina || toX < fromX)
8280                       board[EP_STATUS] = toX | berolina;
8281                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8282                         gameInfo.variant != VariantBerolina || toX > fromX) 
8283                       board[EP_STATUS] = toX;
8284            }
8285        }
8286
8287        for(i=0; i<nrCastlingRights; i++) {
8288            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8289               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8290              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8291        }
8292
8293     }
8294
8295   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8296   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8297        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8298          
8299   if (fromX == toX && fromY == toY) return;
8300
8301   if (fromY == DROP_RANK) {
8302         /* must be first */
8303         piece = board[toY][toX] = (ChessSquare) fromX;
8304   } else {
8305      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8306      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8307      if(gameInfo.variant == VariantKnightmate)
8308          king += (int) WhiteUnicorn - (int) WhiteKing;
8309
8310     /* Code added by Tord: */
8311     /* FRC castling assumed when king captures friendly rook. */
8312     if (board[fromY][fromX] == WhiteKing &&
8313              board[toY][toX] == WhiteRook) {
8314       board[fromY][fromX] = EmptySquare;
8315       board[toY][toX] = EmptySquare;
8316       if(toX > fromX) {
8317         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8318       } else {
8319         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8320       }
8321     } else if (board[fromY][fromX] == BlackKing &&
8322                board[toY][toX] == BlackRook) {
8323       board[fromY][fromX] = EmptySquare;
8324       board[toY][toX] = EmptySquare;
8325       if(toX > fromX) {
8326         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8327       } else {
8328         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8329       }
8330     /* End of code added by Tord */
8331
8332     } else if (board[fromY][fromX] == king
8333         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8334         && toY == fromY && toX > fromX+1) {
8335         board[fromY][fromX] = EmptySquare;
8336         board[toY][toX] = king;
8337         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8338         board[fromY][BOARD_RGHT-1] = EmptySquare;
8339     } else if (board[fromY][fromX] == king
8340         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8341                && toY == fromY && toX < fromX-1) {
8342         board[fromY][fromX] = EmptySquare;
8343         board[toY][toX] = king;
8344         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8345         board[fromY][BOARD_LEFT] = EmptySquare;
8346     } else if (board[fromY][fromX] == WhitePawn
8347                && toY >= BOARD_HEIGHT-promoRank
8348                && gameInfo.variant != VariantXiangqi
8349                ) {
8350         /* white pawn promotion */
8351         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8352         if (board[toY][toX] == EmptySquare) {
8353             board[toY][toX] = WhiteQueen;
8354         }
8355         if(gameInfo.variant==VariantBughouse ||
8356            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8357             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8358         board[fromY][fromX] = EmptySquare;
8359     } else if ((fromY == BOARD_HEIGHT-4)
8360                && (toX != fromX)
8361                && gameInfo.variant != VariantXiangqi
8362                && gameInfo.variant != VariantBerolina
8363                && (board[fromY][fromX] == WhitePawn)
8364                && (board[toY][toX] == EmptySquare)) {
8365         board[fromY][fromX] = EmptySquare;
8366         board[toY][toX] = WhitePawn;
8367         captured = board[toY - 1][toX];
8368         board[toY - 1][toX] = EmptySquare;
8369     } else if ((fromY == BOARD_HEIGHT-4)
8370                && (toX == fromX)
8371                && gameInfo.variant == VariantBerolina
8372                && (board[fromY][fromX] == WhitePawn)
8373                && (board[toY][toX] == EmptySquare)) {
8374         board[fromY][fromX] = EmptySquare;
8375         board[toY][toX] = WhitePawn;
8376         if(oldEP & EP_BEROLIN_A) {
8377                 captured = board[fromY][fromX-1];
8378                 board[fromY][fromX-1] = EmptySquare;
8379         }else{  captured = board[fromY][fromX+1];
8380                 board[fromY][fromX+1] = EmptySquare;
8381         }
8382     } else if (board[fromY][fromX] == king
8383         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8384                && toY == fromY && toX > fromX+1) {
8385         board[fromY][fromX] = EmptySquare;
8386         board[toY][toX] = king;
8387         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8388         board[fromY][BOARD_RGHT-1] = EmptySquare;
8389     } else if (board[fromY][fromX] == king
8390         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8391                && toY == fromY && toX < fromX-1) {
8392         board[fromY][fromX] = EmptySquare;
8393         board[toY][toX] = king;
8394         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8395         board[fromY][BOARD_LEFT] = EmptySquare;
8396     } else if (fromY == 7 && fromX == 3
8397                && board[fromY][fromX] == BlackKing
8398                && toY == 7 && toX == 5) {
8399         board[fromY][fromX] = EmptySquare;
8400         board[toY][toX] = BlackKing;
8401         board[fromY][7] = EmptySquare;
8402         board[toY][4] = BlackRook;
8403     } else if (fromY == 7 && fromX == 3
8404                && board[fromY][fromX] == BlackKing
8405                && toY == 7 && toX == 1) {
8406         board[fromY][fromX] = EmptySquare;
8407         board[toY][toX] = BlackKing;
8408         board[fromY][0] = EmptySquare;
8409         board[toY][2] = BlackRook;
8410     } else if (board[fromY][fromX] == BlackPawn
8411                && toY < promoRank
8412                && gameInfo.variant != VariantXiangqi
8413                ) {
8414         /* black pawn promotion */
8415         board[toY][toX] = CharToPiece(ToLower(promoChar));
8416         if (board[toY][toX] == EmptySquare) {
8417             board[toY][toX] = BlackQueen;
8418         }
8419         if(gameInfo.variant==VariantBughouse ||
8420            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8421             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8422         board[fromY][fromX] = EmptySquare;
8423     } else if ((fromY == 3)
8424                && (toX != fromX)
8425                && gameInfo.variant != VariantXiangqi
8426                && gameInfo.variant != VariantBerolina
8427                && (board[fromY][fromX] == BlackPawn)
8428                && (board[toY][toX] == EmptySquare)) {
8429         board[fromY][fromX] = EmptySquare;
8430         board[toY][toX] = BlackPawn;
8431         captured = board[toY + 1][toX];
8432         board[toY + 1][toX] = EmptySquare;
8433     } else if ((fromY == 3)
8434                && (toX == fromX)
8435                && gameInfo.variant == VariantBerolina
8436                && (board[fromY][fromX] == BlackPawn)
8437                && (board[toY][toX] == EmptySquare)) {
8438         board[fromY][fromX] = EmptySquare;
8439         board[toY][toX] = BlackPawn;
8440         if(oldEP & EP_BEROLIN_A) {
8441                 captured = board[fromY][fromX-1];
8442                 board[fromY][fromX-1] = EmptySquare;
8443         }else{  captured = board[fromY][fromX+1];
8444                 board[fromY][fromX+1] = EmptySquare;
8445         }
8446     } else {
8447         board[toY][toX] = board[fromY][fromX];
8448         board[fromY][fromX] = EmptySquare;
8449     }
8450
8451     /* [HGM] now we promote for Shogi, if needed */
8452     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8453         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8454   }
8455
8456     if (gameInfo.holdingsWidth != 0) {
8457
8458       /* !!A lot more code needs to be written to support holdings  */
8459       /* [HGM] OK, so I have written it. Holdings are stored in the */
8460       /* penultimate board files, so they are automaticlly stored   */
8461       /* in the game history.                                       */
8462       if (fromY == DROP_RANK) {
8463         /* Delete from holdings, by decreasing count */
8464         /* and erasing image if necessary            */
8465         p = (int) fromX;
8466         if(p < (int) BlackPawn) { /* white drop */
8467              p -= (int)WhitePawn;
8468                  p = PieceToNumber((ChessSquare)p);
8469              if(p >= gameInfo.holdingsSize) p = 0;
8470              if(--board[p][BOARD_WIDTH-2] <= 0)
8471                   board[p][BOARD_WIDTH-1] = EmptySquare;
8472              if((int)board[p][BOARD_WIDTH-2] < 0)
8473                         board[p][BOARD_WIDTH-2] = 0;
8474         } else {                  /* black drop */
8475              p -= (int)BlackPawn;
8476                  p = PieceToNumber((ChessSquare)p);
8477              if(p >= gameInfo.holdingsSize) p = 0;
8478              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8479                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8480              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8481                         board[BOARD_HEIGHT-1-p][1] = 0;
8482         }
8483       }
8484       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8485           && gameInfo.variant != VariantBughouse        ) {
8486         /* [HGM] holdings: Add to holdings, if holdings exist */
8487         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8488                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8489                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8490         }
8491         p = (int) captured;
8492         if (p >= (int) BlackPawn) {
8493           p -= (int)BlackPawn;
8494           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8495                   /* in Shogi restore piece to its original  first */
8496                   captured = (ChessSquare) (DEMOTED captured);
8497                   p = DEMOTED p;
8498           }
8499           p = PieceToNumber((ChessSquare)p);
8500           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8501           board[p][BOARD_WIDTH-2]++;
8502           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8503         } else {
8504           p -= (int)WhitePawn;
8505           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8506                   captured = (ChessSquare) (DEMOTED captured);
8507                   p = DEMOTED p;
8508           }
8509           p = PieceToNumber((ChessSquare)p);
8510           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8511           board[BOARD_HEIGHT-1-p][1]++;
8512           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8513         }
8514       }
8515     } else if (gameInfo.variant == VariantAtomic) {
8516       if (captured != EmptySquare) {
8517         int y, x;
8518         for (y = toY-1; y <= toY+1; y++) {
8519           for (x = toX-1; x <= toX+1; x++) {
8520             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8521                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8522               board[y][x] = EmptySquare;
8523             }
8524           }
8525         }
8526         board[toY][toX] = EmptySquare;
8527       }
8528     }
8529     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8530         /* [HGM] Shogi promotions */
8531         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8532     }
8533
8534     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8535                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8536         // [HGM] superchess: take promotion piece out of holdings
8537         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8538         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8539             if(!--board[k][BOARD_WIDTH-2])
8540                 board[k][BOARD_WIDTH-1] = EmptySquare;
8541         } else {
8542             if(!--board[BOARD_HEIGHT-1-k][1])
8543                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8544         }
8545     }
8546
8547 }
8548
8549 /* Updates forwardMostMove */
8550 void
8551 MakeMove(fromX, fromY, toX, toY, promoChar)
8552      int fromX, fromY, toX, toY;
8553      int promoChar;
8554 {
8555 //    forwardMostMove++; // [HGM] bare: moved downstream
8556
8557     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8558         int timeLeft; static int lastLoadFlag=0; int king, piece;
8559         piece = boards[forwardMostMove][fromY][fromX];
8560         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8561         if(gameInfo.variant == VariantKnightmate)
8562             king += (int) WhiteUnicorn - (int) WhiteKing;
8563         if(forwardMostMove == 0) {
8564             if(blackPlaysFirst) 
8565                 fprintf(serverMoves, "%s;", second.tidy);
8566             fprintf(serverMoves, "%s;", first.tidy);
8567             if(!blackPlaysFirst) 
8568                 fprintf(serverMoves, "%s;", second.tidy);
8569         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8570         lastLoadFlag = loadFlag;
8571         // print base move
8572         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8573         // print castling suffix
8574         if( toY == fromY && piece == king ) {
8575             if(toX-fromX > 1)
8576                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8577             if(fromX-toX >1)
8578                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8579         }
8580         // e.p. suffix
8581         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8582              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8583              boards[forwardMostMove][toY][toX] == EmptySquare
8584              && fromX != toX && fromY != toY)
8585                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8586         // promotion suffix
8587         if(promoChar != NULLCHAR)
8588                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8589         if(!loadFlag) {
8590             fprintf(serverMoves, "/%d/%d",
8591                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8592             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8593             else                      timeLeft = blackTimeRemaining/1000;
8594             fprintf(serverMoves, "/%d", timeLeft);
8595         }
8596         fflush(serverMoves);
8597     }
8598
8599     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8600       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8601                         0, 1);
8602       return;
8603     }
8604     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8605     if (commentList[forwardMostMove+1] != NULL) {
8606         free(commentList[forwardMostMove+1]);
8607         commentList[forwardMostMove+1] = NULL;
8608     }
8609     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8610     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8611     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8612     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8613     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8614     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8615     gameInfo.result = GameUnfinished;
8616     if (gameInfo.resultDetails != NULL) {
8617         free(gameInfo.resultDetails);
8618         gameInfo.resultDetails = NULL;
8619     }
8620     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8621                               moveList[forwardMostMove - 1]);
8622     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8623                              PosFlags(forwardMostMove - 1),
8624                              fromY, fromX, toY, toX, promoChar,
8625                              parseList[forwardMostMove - 1]);
8626     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8627       case MT_NONE:
8628       case MT_STALEMATE:
8629       default:
8630         break;
8631       case MT_CHECK:
8632         if(gameInfo.variant != VariantShogi)
8633             strcat(parseList[forwardMostMove - 1], "+");
8634         break;
8635       case MT_CHECKMATE:
8636       case MT_STAINMATE:
8637         strcat(parseList[forwardMostMove - 1], "#");
8638         break;
8639     }
8640     if (appData.debugMode) {
8641         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8642     }
8643
8644 }
8645
8646 /* Updates currentMove if not pausing */
8647 void
8648 ShowMove(fromX, fromY, toX, toY)
8649 {
8650     int instant = (gameMode == PlayFromGameFile) ?
8651         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8652     if(appData.noGUI) return;
8653     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8654         if (!instant) {
8655             if (forwardMostMove == currentMove + 1) {
8656                 AnimateMove(boards[forwardMostMove - 1],
8657                             fromX, fromY, toX, toY);
8658             }
8659             if (appData.highlightLastMove) {
8660                 SetHighlights(fromX, fromY, toX, toY);
8661             }
8662         }
8663         currentMove = forwardMostMove;
8664     }
8665
8666     if (instant) return;
8667
8668     DisplayMove(currentMove - 1);
8669     DrawPosition(FALSE, boards[currentMove]);
8670     DisplayBothClocks();
8671     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8672 }
8673
8674 void SendEgtPath(ChessProgramState *cps)
8675 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8676         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8677
8678         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8679
8680         while(*p) {
8681             char c, *q = name+1, *r, *s;
8682
8683             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8684             while(*p && *p != ',') *q++ = *p++;
8685             *q++ = ':'; *q = 0;
8686             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8687                 strcmp(name, ",nalimov:") == 0 ) {
8688                 // take nalimov path from the menu-changeable option first, if it is defined
8689                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8690                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8691             } else
8692             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8693                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8694                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8695                 s = r = StrStr(s, ":") + 1; // beginning of path info
8696                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8697                 c = *r; *r = 0;             // temporarily null-terminate path info
8698                     *--q = 0;               // strip of trailig ':' from name
8699                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8700                 *r = c;
8701                 SendToProgram(buf,cps);     // send egtbpath command for this format
8702             }
8703             if(*p == ',') p++; // read away comma to position for next format name
8704         }
8705 }
8706
8707 void
8708 InitChessProgram(cps, setup)
8709      ChessProgramState *cps;
8710      int setup; /* [HGM] needed to setup FRC opening position */
8711 {
8712     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8713     if (appData.noChessProgram) return;
8714     hintRequested = FALSE;
8715     bookRequested = FALSE;
8716
8717     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8718     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8719     if(cps->memSize) { /* [HGM] memory */
8720         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8721         SendToProgram(buf, cps);
8722     }
8723     SendEgtPath(cps); /* [HGM] EGT */
8724     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8725         sprintf(buf, "cores %d\n", appData.smpCores);
8726         SendToProgram(buf, cps);
8727     }
8728
8729     SendToProgram(cps->initString, cps);
8730     if (gameInfo.variant != VariantNormal &&
8731         gameInfo.variant != VariantLoadable
8732         /* [HGM] also send variant if board size non-standard */
8733         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8734                                             ) {
8735       char *v = VariantName(gameInfo.variant);
8736       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8737         /* [HGM] in protocol 1 we have to assume all variants valid */
8738         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8739         DisplayFatalError(buf, 0, 1);
8740         return;
8741       }
8742
8743       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8744       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8745       if( gameInfo.variant == VariantXiangqi )
8746            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8747       if( gameInfo.variant == VariantShogi )
8748            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8749       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8750            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8751       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8752                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8753            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8754       if( gameInfo.variant == VariantCourier )
8755            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8756       if( gameInfo.variant == VariantSuper )
8757            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8758       if( gameInfo.variant == VariantGreat )
8759            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8760
8761       if(overruled) {
8762            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8763                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8764            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8765            if(StrStr(cps->variants, b) == NULL) { 
8766                // specific sized variant not known, check if general sizing allowed
8767                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8768                    if(StrStr(cps->variants, "boardsize") == NULL) {
8769                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8770                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8771                        DisplayFatalError(buf, 0, 1);
8772                        return;
8773                    }
8774                    /* [HGM] here we really should compare with the maximum supported board size */
8775                }
8776            }
8777       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8778       sprintf(buf, "variant %s\n", b);
8779       SendToProgram(buf, cps);
8780     }
8781     currentlyInitializedVariant = gameInfo.variant;
8782
8783     /* [HGM] send opening position in FRC to first engine */
8784     if(setup) {
8785           SendToProgram("force\n", cps);
8786           SendBoard(cps, 0);
8787           /* engine is now in force mode! Set flag to wake it up after first move. */
8788           setboardSpoiledMachineBlack = 1;
8789     }
8790
8791     if (cps->sendICS) {
8792       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8793       SendToProgram(buf, cps);
8794     }
8795     cps->maybeThinking = FALSE;
8796     cps->offeredDraw = 0;
8797     if (!appData.icsActive) {
8798         SendTimeControl(cps, movesPerSession, timeControl,
8799                         timeIncrement, appData.searchDepth,
8800                         searchTime);
8801     }
8802     if (appData.showThinking 
8803         // [HGM] thinking: four options require thinking output to be sent
8804         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8805                                 ) {
8806         SendToProgram("post\n", cps);
8807     }
8808     SendToProgram("hard\n", cps);
8809     if (!appData.ponderNextMove) {
8810         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8811            it without being sure what state we are in first.  "hard"
8812            is not a toggle, so that one is OK.
8813          */
8814         SendToProgram("easy\n", cps);
8815     }
8816     if (cps->usePing) {
8817       sprintf(buf, "ping %d\n", ++cps->lastPing);
8818       SendToProgram(buf, cps);
8819     }
8820     cps->initDone = TRUE;
8821 }   
8822
8823
8824 void
8825 StartChessProgram(cps)
8826      ChessProgramState *cps;
8827 {
8828     char buf[MSG_SIZ];
8829     int err;
8830
8831     if (appData.noChessProgram) return;
8832     cps->initDone = FALSE;
8833
8834     if (strcmp(cps->host, "localhost") == 0) {
8835         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8836     } else if (*appData.remoteShell == NULLCHAR) {
8837         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8838     } else {
8839         if (*appData.remoteUser == NULLCHAR) {
8840           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8841                     cps->program);
8842         } else {
8843           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8844                     cps->host, appData.remoteUser, cps->program);
8845         }
8846         err = StartChildProcess(buf, "", &cps->pr);
8847     }
8848     
8849     if (err != 0) {
8850         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8851         DisplayFatalError(buf, err, 1);
8852         cps->pr = NoProc;
8853         cps->isr = NULL;
8854         return;
8855     }
8856     
8857     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8858     if (cps->protocolVersion > 1) {
8859       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8860       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8861       cps->comboCnt = 0;  //                and values of combo boxes
8862       SendToProgram(buf, cps);
8863     } else {
8864       SendToProgram("xboard\n", cps);
8865     }
8866 }
8867
8868
8869 void
8870 TwoMachinesEventIfReady P((void))
8871 {
8872   if (first.lastPing != first.lastPong) {
8873     DisplayMessage("", _("Waiting for first chess program"));
8874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8875     return;
8876   }
8877   if (second.lastPing != second.lastPong) {
8878     DisplayMessage("", _("Waiting for second chess program"));
8879     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8880     return;
8881   }
8882   ThawUI();
8883   TwoMachinesEvent();
8884 }
8885
8886 void
8887 NextMatchGame P((void))
8888 {
8889     int index; /* [HGM] autoinc: step load index during match */
8890     Reset(FALSE, TRUE);
8891     if (*appData.loadGameFile != NULLCHAR) {
8892         index = appData.loadGameIndex;
8893         if(index < 0) { // [HGM] autoinc
8894             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8895             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8896         } 
8897         LoadGameFromFile(appData.loadGameFile,
8898                          index,
8899                          appData.loadGameFile, FALSE);
8900     } else if (*appData.loadPositionFile != NULLCHAR) {
8901         index = appData.loadPositionIndex;
8902         if(index < 0) { // [HGM] autoinc
8903             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8904             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8905         } 
8906         LoadPositionFromFile(appData.loadPositionFile,
8907                              index,
8908                              appData.loadPositionFile);
8909     }
8910     TwoMachinesEventIfReady();
8911 }
8912
8913 void UserAdjudicationEvent( int result )
8914 {
8915     ChessMove gameResult = GameIsDrawn;
8916
8917     if( result > 0 ) {
8918         gameResult = WhiteWins;
8919     }
8920     else if( result < 0 ) {
8921         gameResult = BlackWins;
8922     }
8923
8924     if( gameMode == TwoMachinesPlay ) {
8925         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8926     }
8927 }
8928
8929
8930 // [HGM] save: calculate checksum of game to make games easily identifiable
8931 int StringCheckSum(char *s)
8932 {
8933         int i = 0;
8934         if(s==NULL) return 0;
8935         while(*s) i = i*259 + *s++;
8936         return i;
8937 }
8938
8939 int GameCheckSum()
8940 {
8941         int i, sum=0;
8942         for(i=backwardMostMove; i<forwardMostMove; i++) {
8943                 sum += pvInfoList[i].depth;
8944                 sum += StringCheckSum(parseList[i]);
8945                 sum += StringCheckSum(commentList[i]);
8946                 sum *= 261;
8947         }
8948         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8949         return sum + StringCheckSum(commentList[i]);
8950 } // end of save patch
8951
8952 void
8953 GameEnds(result, resultDetails, whosays)
8954      ChessMove result;
8955      char *resultDetails;
8956      int whosays;
8957 {
8958     GameMode nextGameMode;
8959     int isIcsGame;
8960     char buf[MSG_SIZ];
8961
8962     if(endingGame) return; /* [HGM] crash: forbid recursion */
8963     endingGame = 1;
8964     if(twoBoards) { // [HGM] dual: switch back to one board
8965         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8966         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8967     }
8968     if (appData.debugMode) {
8969       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8970               result, resultDetails ? resultDetails : "(null)", whosays);
8971     }
8972
8973     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8974
8975     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8976         /* If we are playing on ICS, the server decides when the
8977            game is over, but the engine can offer to draw, claim 
8978            a draw, or resign. 
8979          */
8980 #if ZIPPY
8981         if (appData.zippyPlay && first.initDone) {
8982             if (result == GameIsDrawn) {
8983                 /* In case draw still needs to be claimed */
8984                 SendToICS(ics_prefix);
8985                 SendToICS("draw\n");
8986             } else if (StrCaseStr(resultDetails, "resign")) {
8987                 SendToICS(ics_prefix);
8988                 SendToICS("resign\n");
8989             }
8990         }
8991 #endif
8992         endingGame = 0; /* [HGM] crash */
8993         return;
8994     }
8995
8996     /* If we're loading the game from a file, stop */
8997     if (whosays == GE_FILE) {
8998       (void) StopLoadGameTimer();
8999       gameFileFP = NULL;
9000     }
9001
9002     /* Cancel draw offers */
9003     first.offeredDraw = second.offeredDraw = 0;
9004
9005     /* If this is an ICS game, only ICS can really say it's done;
9006        if not, anyone can. */
9007     isIcsGame = (gameMode == IcsPlayingWhite || 
9008                  gameMode == IcsPlayingBlack || 
9009                  gameMode == IcsObserving    || 
9010                  gameMode == IcsExamining);
9011
9012     if (!isIcsGame || whosays == GE_ICS) {
9013         /* OK -- not an ICS game, or ICS said it was done */
9014         StopClocks();
9015         if (!isIcsGame && !appData.noChessProgram) 
9016           SetUserThinkingEnables();
9017     
9018         /* [HGM] if a machine claims the game end we verify this claim */
9019         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9020             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9021                 char claimer;
9022                 ChessMove trueResult = (ChessMove) -1;
9023
9024                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9025                                             first.twoMachinesColor[0] :
9026                                             second.twoMachinesColor[0] ;
9027
9028                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9029                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9030                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9031                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9032                 } else
9033                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9034                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9035                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9036                 } else
9037                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9038                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9039                 }
9040
9041                 // now verify win claims, but not in drop games, as we don't understand those yet
9042                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9043                                                  || gameInfo.variant == VariantGreat) &&
9044                     (result == WhiteWins && claimer == 'w' ||
9045                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9046                       if (appData.debugMode) {
9047                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9048                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9049                       }
9050                       if(result != trueResult) {
9051                               sprintf(buf, "False win claim: '%s'", resultDetails);
9052                               result = claimer == 'w' ? BlackWins : WhiteWins;
9053                               resultDetails = buf;
9054                       }
9055                 } else
9056                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9057                     && (forwardMostMove <= backwardMostMove ||
9058                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9059                         (claimer=='b')==(forwardMostMove&1))
9060                                                                                   ) {
9061                       /* [HGM] verify: draws that were not flagged are false claims */
9062                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9063                       result = claimer == 'w' ? BlackWins : WhiteWins;
9064                       resultDetails = buf;
9065                 }
9066                 /* (Claiming a loss is accepted no questions asked!) */
9067             }
9068             /* [HGM] bare: don't allow bare King to win */
9069             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9070                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9071                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9072                && result != GameIsDrawn)
9073             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9074                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9075                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9076                         if(p >= 0 && p <= (int)WhiteKing) k++;
9077                 }
9078                 if (appData.debugMode) {
9079                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9080                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9081                 }
9082                 if(k <= 1) {
9083                         result = GameIsDrawn;
9084                         sprintf(buf, "%s but bare king", resultDetails);
9085                         resultDetails = buf;
9086                 }
9087             }
9088         }
9089
9090
9091         if(serverMoves != NULL && !loadFlag) { char c = '=';
9092             if(result==WhiteWins) c = '+';
9093             if(result==BlackWins) c = '-';
9094             if(resultDetails != NULL)
9095                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9096         }
9097         if (resultDetails != NULL) {
9098             gameInfo.result = result;
9099             gameInfo.resultDetails = StrSave(resultDetails);
9100
9101             /* display last move only if game was not loaded from file */
9102             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9103                 DisplayMove(currentMove - 1);
9104     
9105             if (forwardMostMove != 0) {
9106                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9107                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9108                                                                 ) {
9109                     if (*appData.saveGameFile != NULLCHAR) {
9110                         SaveGameToFile(appData.saveGameFile, TRUE);
9111                     } else if (appData.autoSaveGames) {
9112                         AutoSaveGame();
9113                     }
9114                     if (*appData.savePositionFile != NULLCHAR) {
9115                         SavePositionToFile(appData.savePositionFile);
9116                     }
9117                 }
9118             }
9119
9120             /* Tell program how game ended in case it is learning */
9121             /* [HGM] Moved this to after saving the PGN, just in case */
9122             /* engine died and we got here through time loss. In that */
9123             /* case we will get a fatal error writing the pipe, which */
9124             /* would otherwise lose us the PGN.                       */
9125             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9126             /* output during GameEnds should never be fatal anymore   */
9127             if (gameMode == MachinePlaysWhite ||
9128                 gameMode == MachinePlaysBlack ||
9129                 gameMode == TwoMachinesPlay ||
9130                 gameMode == IcsPlayingWhite ||
9131                 gameMode == IcsPlayingBlack ||
9132                 gameMode == BeginningOfGame) {
9133                 char buf[MSG_SIZ];
9134                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9135                         resultDetails);
9136                 if (first.pr != NoProc) {
9137                     SendToProgram(buf, &first);
9138                 }
9139                 if (second.pr != NoProc &&
9140                     gameMode == TwoMachinesPlay) {
9141                     SendToProgram(buf, &second);
9142                 }
9143             }
9144         }
9145
9146         if (appData.icsActive) {
9147             if (appData.quietPlay &&
9148                 (gameMode == IcsPlayingWhite ||
9149                  gameMode == IcsPlayingBlack)) {
9150                 SendToICS(ics_prefix);
9151                 SendToICS("set shout 1\n");
9152             }
9153             nextGameMode = IcsIdle;
9154             ics_user_moved = FALSE;
9155             /* clean up premove.  It's ugly when the game has ended and the
9156              * premove highlights are still on the board.
9157              */
9158             if (gotPremove) {
9159               gotPremove = FALSE;
9160               ClearPremoveHighlights();
9161               DrawPosition(FALSE, boards[currentMove]);
9162             }
9163             if (whosays == GE_ICS) {
9164                 switch (result) {
9165                 case WhiteWins:
9166                     if (gameMode == IcsPlayingWhite)
9167                         PlayIcsWinSound();
9168                     else if(gameMode == IcsPlayingBlack)
9169                         PlayIcsLossSound();
9170                     break;
9171                 case BlackWins:
9172                     if (gameMode == IcsPlayingBlack)
9173                         PlayIcsWinSound();
9174                     else if(gameMode == IcsPlayingWhite)
9175                         PlayIcsLossSound();
9176                     break;
9177                 case GameIsDrawn:
9178                     PlayIcsDrawSound();
9179                     break;
9180                 default:
9181                     PlayIcsUnfinishedSound();
9182                 }
9183             }
9184         } else if (gameMode == EditGame ||
9185                    gameMode == PlayFromGameFile || 
9186                    gameMode == AnalyzeMode || 
9187                    gameMode == AnalyzeFile) {
9188             nextGameMode = gameMode;
9189         } else {
9190             nextGameMode = EndOfGame;
9191         }
9192         pausing = FALSE;
9193         ModeHighlight();
9194     } else {
9195         nextGameMode = gameMode;
9196     }
9197
9198     if (appData.noChessProgram) {
9199         gameMode = nextGameMode;
9200         ModeHighlight();
9201         endingGame = 0; /* [HGM] crash */
9202         return;
9203     }
9204
9205     if (first.reuse) {
9206         /* Put first chess program into idle state */
9207         if (first.pr != NoProc &&
9208             (gameMode == MachinePlaysWhite ||
9209              gameMode == MachinePlaysBlack ||
9210              gameMode == TwoMachinesPlay ||
9211              gameMode == IcsPlayingWhite ||
9212              gameMode == IcsPlayingBlack ||
9213              gameMode == BeginningOfGame)) {
9214             SendToProgram("force\n", &first);
9215             if (first.usePing) {
9216               char buf[MSG_SIZ];
9217               sprintf(buf, "ping %d\n", ++first.lastPing);
9218               SendToProgram(buf, &first);
9219             }
9220         }
9221     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9222         /* Kill off first chess program */
9223         if (first.isr != NULL)
9224           RemoveInputSource(first.isr);
9225         first.isr = NULL;
9226     
9227         if (first.pr != NoProc) {
9228             ExitAnalyzeMode();
9229             DoSleep( appData.delayBeforeQuit );
9230             SendToProgram("quit\n", &first);
9231             DoSleep( appData.delayAfterQuit );
9232             DestroyChildProcess(first.pr, first.useSigterm);
9233         }
9234         first.pr = NoProc;
9235     }
9236     if (second.reuse) {
9237         /* Put second chess program into idle state */
9238         if (second.pr != NoProc &&
9239             gameMode == TwoMachinesPlay) {
9240             SendToProgram("force\n", &second);
9241             if (second.usePing) {
9242               char buf[MSG_SIZ];
9243               sprintf(buf, "ping %d\n", ++second.lastPing);
9244               SendToProgram(buf, &second);
9245             }
9246         }
9247     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9248         /* Kill off second chess program */
9249         if (second.isr != NULL)
9250           RemoveInputSource(second.isr);
9251         second.isr = NULL;
9252     
9253         if (second.pr != NoProc) {
9254             DoSleep( appData.delayBeforeQuit );
9255             SendToProgram("quit\n", &second);
9256             DoSleep( appData.delayAfterQuit );
9257             DestroyChildProcess(second.pr, second.useSigterm);
9258         }
9259         second.pr = NoProc;
9260     }
9261
9262     if (matchMode && gameMode == TwoMachinesPlay) {
9263         switch (result) {
9264         case WhiteWins:
9265           if (first.twoMachinesColor[0] == 'w') {
9266             first.matchWins++;
9267           } else {
9268             second.matchWins++;
9269           }
9270           break;
9271         case BlackWins:
9272           if (first.twoMachinesColor[0] == 'b') {
9273             first.matchWins++;
9274           } else {
9275             second.matchWins++;
9276           }
9277           break;
9278         default:
9279           break;
9280         }
9281         if (matchGame < appData.matchGames) {
9282             char *tmp;
9283             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9284                 tmp = first.twoMachinesColor;
9285                 first.twoMachinesColor = second.twoMachinesColor;
9286                 second.twoMachinesColor = tmp;
9287             }
9288             gameMode = nextGameMode;
9289             matchGame++;
9290             if(appData.matchPause>10000 || appData.matchPause<10)
9291                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9292             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9293             endingGame = 0; /* [HGM] crash */
9294             return;
9295         } else {
9296             char buf[MSG_SIZ];
9297             gameMode = nextGameMode;
9298             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9299                     first.tidy, second.tidy,
9300                     first.matchWins, second.matchWins,
9301                     appData.matchGames - (first.matchWins + second.matchWins));
9302             DisplayFatalError(buf, 0, 0);
9303         }
9304     }
9305     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9306         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9307       ExitAnalyzeMode();
9308     gameMode = nextGameMode;
9309     ModeHighlight();
9310     endingGame = 0;  /* [HGM] crash */
9311 }
9312
9313 /* Assumes program was just initialized (initString sent).
9314    Leaves program in force mode. */
9315 void
9316 FeedMovesToProgram(cps, upto) 
9317      ChessProgramState *cps;
9318      int upto;
9319 {
9320     int i;
9321     
9322     if (appData.debugMode)
9323       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9324               startedFromSetupPosition ? "position and " : "",
9325               backwardMostMove, upto, cps->which);
9326     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9327         // [HGM] variantswitch: make engine aware of new variant
9328         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9329                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9330         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9331         SendToProgram(buf, cps);
9332         currentlyInitializedVariant = gameInfo.variant;
9333     }
9334     SendToProgram("force\n", cps);
9335     if (startedFromSetupPosition) {
9336         SendBoard(cps, backwardMostMove);
9337     if (appData.debugMode) {
9338         fprintf(debugFP, "feedMoves\n");
9339     }
9340     }
9341     for (i = backwardMostMove; i < upto; i++) {
9342         SendMoveToProgram(i, cps);
9343     }
9344 }
9345
9346
9347 void
9348 ResurrectChessProgram()
9349 {
9350      /* The chess program may have exited.
9351         If so, restart it and feed it all the moves made so far. */
9352
9353     if (appData.noChessProgram || first.pr != NoProc) return;
9354     
9355     StartChessProgram(&first);
9356     InitChessProgram(&first, FALSE);
9357     FeedMovesToProgram(&first, currentMove);
9358
9359     if (!first.sendTime) {
9360         /* can't tell gnuchess what its clock should read,
9361            so we bow to its notion. */
9362         ResetClocks();
9363         timeRemaining[0][currentMove] = whiteTimeRemaining;
9364         timeRemaining[1][currentMove] = blackTimeRemaining;
9365     }
9366
9367     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9368                 appData.icsEngineAnalyze) && first.analysisSupport) {
9369       SendToProgram("analyze\n", &first);
9370       first.analyzing = TRUE;
9371     }
9372 }
9373
9374 /*
9375  * Button procedures
9376  */
9377 void
9378 Reset(redraw, init)
9379      int redraw, init;
9380 {
9381     int i;
9382
9383     if (appData.debugMode) {
9384         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9385                 redraw, init, gameMode);
9386     }
9387     CleanupTail(); // [HGM] vari: delete any stored variations
9388     pausing = pauseExamInvalid = FALSE;
9389     startedFromSetupPosition = blackPlaysFirst = FALSE;
9390     firstMove = TRUE;
9391     whiteFlag = blackFlag = FALSE;
9392     userOfferedDraw = FALSE;
9393     hintRequested = bookRequested = FALSE;
9394     first.maybeThinking = FALSE;
9395     second.maybeThinking = FALSE;
9396     first.bookSuspend = FALSE; // [HGM] book
9397     second.bookSuspend = FALSE;
9398     thinkOutput[0] = NULLCHAR;
9399     lastHint[0] = NULLCHAR;
9400     ClearGameInfo(&gameInfo);
9401     gameInfo.variant = StringToVariant(appData.variant);
9402     ics_user_moved = ics_clock_paused = FALSE;
9403     ics_getting_history = H_FALSE;
9404     ics_gamenum = -1;
9405     white_holding[0] = black_holding[0] = NULLCHAR;
9406     ClearProgramStats();
9407     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9408     
9409     ResetFrontEnd();
9410     ClearHighlights();
9411     flipView = appData.flipView;
9412     ClearPremoveHighlights();
9413     gotPremove = FALSE;
9414     alarmSounded = FALSE;
9415
9416     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9417     if(appData.serverMovesName != NULL) {
9418         /* [HGM] prepare to make moves file for broadcasting */
9419         clock_t t = clock();
9420         if(serverMoves != NULL) fclose(serverMoves);
9421         serverMoves = fopen(appData.serverMovesName, "r");
9422         if(serverMoves != NULL) {
9423             fclose(serverMoves);
9424             /* delay 15 sec before overwriting, so all clients can see end */
9425             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9426         }
9427         serverMoves = fopen(appData.serverMovesName, "w");
9428     }
9429
9430     ExitAnalyzeMode();
9431     gameMode = BeginningOfGame;
9432     ModeHighlight();
9433     if(appData.icsActive) gameInfo.variant = VariantNormal;
9434     currentMove = forwardMostMove = backwardMostMove = 0;
9435     InitPosition(redraw);
9436     for (i = 0; i < MAX_MOVES; i++) {
9437         if (commentList[i] != NULL) {
9438             free(commentList[i]);
9439             commentList[i] = NULL;
9440         }
9441     }
9442     ResetClocks();
9443     timeRemaining[0][0] = whiteTimeRemaining;
9444     timeRemaining[1][0] = blackTimeRemaining;
9445     if (first.pr == NULL) {
9446         StartChessProgram(&first);
9447     }
9448     if (init) {
9449             InitChessProgram(&first, startedFromSetupPosition);
9450     }
9451     DisplayTitle("");
9452     DisplayMessage("", "");
9453     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9454     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9455 }
9456
9457 void
9458 AutoPlayGameLoop()
9459 {
9460     for (;;) {
9461         if (!AutoPlayOneMove())
9462           return;
9463         if (matchMode || appData.timeDelay == 0)
9464           continue;
9465         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9466           return;
9467         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9468         break;
9469     }
9470 }
9471
9472
9473 int
9474 AutoPlayOneMove()
9475 {
9476     int fromX, fromY, toX, toY;
9477
9478     if (appData.debugMode) {
9479       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9480     }
9481
9482     if (gameMode != PlayFromGameFile)
9483       return FALSE;
9484
9485     if (currentMove >= forwardMostMove) {
9486       gameMode = EditGame;
9487       ModeHighlight();
9488
9489       /* [AS] Clear current move marker at the end of a game */
9490       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9491
9492       return FALSE;
9493     }
9494     
9495     toX = moveList[currentMove][2] - AAA;
9496     toY = moveList[currentMove][3] - ONE;
9497
9498     if (moveList[currentMove][1] == '@') {
9499         if (appData.highlightLastMove) {
9500             SetHighlights(-1, -1, toX, toY);
9501         }
9502     } else {
9503         fromX = moveList[currentMove][0] - AAA;
9504         fromY = moveList[currentMove][1] - ONE;
9505
9506         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9507
9508         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9509
9510         if (appData.highlightLastMove) {
9511             SetHighlights(fromX, fromY, toX, toY);
9512         }
9513     }
9514     DisplayMove(currentMove);
9515     SendMoveToProgram(currentMove++, &first);
9516     DisplayBothClocks();
9517     DrawPosition(FALSE, boards[currentMove]);
9518     // [HGM] PV info: always display, routine tests if empty
9519     DisplayComment(currentMove - 1, commentList[currentMove]);
9520     return TRUE;
9521 }
9522
9523
9524 int
9525 LoadGameOneMove(readAhead)
9526      ChessMove readAhead;
9527 {
9528     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9529     char promoChar = NULLCHAR;
9530     ChessMove moveType;
9531     char move[MSG_SIZ];
9532     char *p, *q;
9533     
9534     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9535         gameMode != AnalyzeMode && gameMode != Training) {
9536         gameFileFP = NULL;
9537         return FALSE;
9538     }
9539     
9540     yyboardindex = forwardMostMove;
9541     if (readAhead != (ChessMove)0) {
9542       moveType = readAhead;
9543     } else {
9544       if (gameFileFP == NULL)
9545           return FALSE;
9546       moveType = (ChessMove) yylex();
9547     }
9548     
9549     done = FALSE;
9550     switch (moveType) {
9551       case Comment:
9552         if (appData.debugMode) 
9553           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9554         p = yy_text;
9555
9556         /* append the comment but don't display it */
9557         AppendComment(currentMove, p, FALSE);
9558         return TRUE;
9559
9560       case WhiteCapturesEnPassant:
9561       case BlackCapturesEnPassant:
9562       case WhitePromotionChancellor:
9563       case BlackPromotionChancellor:
9564       case WhitePromotionArchbishop:
9565       case BlackPromotionArchbishop:
9566       case WhitePromotionCentaur:
9567       case BlackPromotionCentaur:
9568       case WhitePromotionQueen:
9569       case BlackPromotionQueen:
9570       case WhitePromotionRook:
9571       case BlackPromotionRook:
9572       case WhitePromotionBishop:
9573       case BlackPromotionBishop:
9574       case WhitePromotionKnight:
9575       case BlackPromotionKnight:
9576       case WhitePromotionKing:
9577       case BlackPromotionKing:
9578       case NormalMove:
9579       case WhiteKingSideCastle:
9580       case WhiteQueenSideCastle:
9581       case BlackKingSideCastle:
9582       case BlackQueenSideCastle:
9583       case WhiteKingSideCastleWild:
9584       case WhiteQueenSideCastleWild:
9585       case BlackKingSideCastleWild:
9586       case BlackQueenSideCastleWild:
9587       /* PUSH Fabien */
9588       case WhiteHSideCastleFR:
9589       case WhiteASideCastleFR:
9590       case BlackHSideCastleFR:
9591       case BlackASideCastleFR:
9592       /* POP Fabien */
9593         if (appData.debugMode)
9594           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9595         fromX = currentMoveString[0] - AAA;
9596         fromY = currentMoveString[1] - ONE;
9597         toX = currentMoveString[2] - AAA;
9598         toY = currentMoveString[3] - ONE;
9599         promoChar = currentMoveString[4];
9600         break;
9601
9602       case WhiteDrop:
9603       case BlackDrop:
9604         if (appData.debugMode)
9605           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9606         fromX = moveType == WhiteDrop ?
9607           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9608         (int) CharToPiece(ToLower(currentMoveString[0]));
9609         fromY = DROP_RANK;
9610         toX = currentMoveString[2] - AAA;
9611         toY = currentMoveString[3] - ONE;
9612         break;
9613
9614       case WhiteWins:
9615       case BlackWins:
9616       case GameIsDrawn:
9617       case GameUnfinished:
9618         if (appData.debugMode)
9619           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9620         p = strchr(yy_text, '{');
9621         if (p == NULL) p = strchr(yy_text, '(');
9622         if (p == NULL) {
9623             p = yy_text;
9624             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9625         } else {
9626             q = strchr(p, *p == '{' ? '}' : ')');
9627             if (q != NULL) *q = NULLCHAR;
9628             p++;
9629         }
9630         GameEnds(moveType, p, GE_FILE);
9631         done = TRUE;
9632         if (cmailMsgLoaded) {
9633             ClearHighlights();
9634             flipView = WhiteOnMove(currentMove);
9635             if (moveType == GameUnfinished) flipView = !flipView;
9636             if (appData.debugMode)
9637               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9638         }
9639         break;
9640
9641       case (ChessMove) 0:       /* end of file */
9642         if (appData.debugMode)
9643           fprintf(debugFP, "Parser hit end of file\n");
9644         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9645           case MT_NONE:
9646           case MT_CHECK:
9647             break;
9648           case MT_CHECKMATE:
9649           case MT_STAINMATE:
9650             if (WhiteOnMove(currentMove)) {
9651                 GameEnds(BlackWins, "Black mates", GE_FILE);
9652             } else {
9653                 GameEnds(WhiteWins, "White mates", GE_FILE);
9654             }
9655             break;
9656           case MT_STALEMATE:
9657             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9658             break;
9659         }
9660         done = TRUE;
9661         break;
9662
9663       case MoveNumberOne:
9664         if (lastLoadGameStart == GNUChessGame) {
9665             /* GNUChessGames have numbers, but they aren't move numbers */
9666             if (appData.debugMode)
9667               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9668                       yy_text, (int) moveType);
9669             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9670         }
9671         /* else fall thru */
9672
9673       case XBoardGame:
9674       case GNUChessGame:
9675       case PGNTag:
9676         /* Reached start of next game in file */
9677         if (appData.debugMode)
9678           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9679         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9680           case MT_NONE:
9681           case MT_CHECK:
9682             break;
9683           case MT_CHECKMATE:
9684           case MT_STAINMATE:
9685             if (WhiteOnMove(currentMove)) {
9686                 GameEnds(BlackWins, "Black mates", GE_FILE);
9687             } else {
9688                 GameEnds(WhiteWins, "White mates", GE_FILE);
9689             }
9690             break;
9691           case MT_STALEMATE:
9692             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9693             break;
9694         }
9695         done = TRUE;
9696         break;
9697
9698       case PositionDiagram:     /* should not happen; ignore */
9699       case ElapsedTime:         /* ignore */
9700       case NAG:                 /* ignore */
9701         if (appData.debugMode)
9702           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9703                   yy_text, (int) moveType);
9704         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9705
9706       case IllegalMove:
9707         if (appData.testLegality) {
9708             if (appData.debugMode)
9709               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9710             sprintf(move, _("Illegal move: %d.%s%s"),
9711                     (forwardMostMove / 2) + 1,
9712                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9713             DisplayError(move, 0);
9714             done = TRUE;
9715         } else {
9716             if (appData.debugMode)
9717               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9718                       yy_text, currentMoveString);
9719             fromX = currentMoveString[0] - AAA;
9720             fromY = currentMoveString[1] - ONE;
9721             toX = currentMoveString[2] - AAA;
9722             toY = currentMoveString[3] - ONE;
9723             promoChar = currentMoveString[4];
9724         }
9725         break;
9726
9727       case AmbiguousMove:
9728         if (appData.debugMode)
9729           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9730         sprintf(move, _("Ambiguous move: %d.%s%s"),
9731                 (forwardMostMove / 2) + 1,
9732                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9733         DisplayError(move, 0);
9734         done = TRUE;
9735         break;
9736
9737       default:
9738       case ImpossibleMove:
9739         if (appData.debugMode)
9740           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9741         sprintf(move, _("Illegal move: %d.%s%s"),
9742                 (forwardMostMove / 2) + 1,
9743                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9744         DisplayError(move, 0);
9745         done = TRUE;
9746         break;
9747     }
9748
9749     if (done) {
9750         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9751             DrawPosition(FALSE, boards[currentMove]);
9752             DisplayBothClocks();
9753             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9754               DisplayComment(currentMove - 1, commentList[currentMove]);
9755         }
9756         (void) StopLoadGameTimer();
9757         gameFileFP = NULL;
9758         cmailOldMove = forwardMostMove;
9759         return FALSE;
9760     } else {
9761         /* currentMoveString is set as a side-effect of yylex */
9762         strcat(currentMoveString, "\n");
9763         strcpy(moveList[forwardMostMove], currentMoveString);
9764         
9765         thinkOutput[0] = NULLCHAR;
9766         MakeMove(fromX, fromY, toX, toY, promoChar);
9767         currentMove = forwardMostMove;
9768         return TRUE;
9769     }
9770 }
9771
9772 /* Load the nth game from the given file */
9773 int
9774 LoadGameFromFile(filename, n, title, useList)
9775      char *filename;
9776      int n;
9777      char *title;
9778      /*Boolean*/ int useList;
9779 {
9780     FILE *f;
9781     char buf[MSG_SIZ];
9782
9783     if (strcmp(filename, "-") == 0) {
9784         f = stdin;
9785         title = "stdin";
9786     } else {
9787         f = fopen(filename, "rb");
9788         if (f == NULL) {
9789           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9790             DisplayError(buf, errno);
9791             return FALSE;
9792         }
9793     }
9794     if (fseek(f, 0, 0) == -1) {
9795         /* f is not seekable; probably a pipe */
9796         useList = FALSE;
9797     }
9798     if (useList && n == 0) {
9799         int error = GameListBuild(f);
9800         if (error) {
9801             DisplayError(_("Cannot build game list"), error);
9802         } else if (!ListEmpty(&gameList) &&
9803                    ((ListGame *) gameList.tailPred)->number > 1) {
9804             GameListPopUp(f, title);
9805             return TRUE;
9806         }
9807         GameListDestroy();
9808         n = 1;
9809     }
9810     if (n == 0) n = 1;
9811     return LoadGame(f, n, title, FALSE);
9812 }
9813
9814
9815 void
9816 MakeRegisteredMove()
9817 {
9818     int fromX, fromY, toX, toY;
9819     char promoChar;
9820     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9821         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9822           case CMAIL_MOVE:
9823           case CMAIL_DRAW:
9824             if (appData.debugMode)
9825               fprintf(debugFP, "Restoring %s for game %d\n",
9826                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9827     
9828             thinkOutput[0] = NULLCHAR;
9829             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9830             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9831             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9832             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9833             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9834             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9835             MakeMove(fromX, fromY, toX, toY, promoChar);
9836             ShowMove(fromX, fromY, toX, toY);
9837               
9838             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9839               case MT_NONE:
9840               case MT_CHECK:
9841                 break;
9842                 
9843               case MT_CHECKMATE:
9844               case MT_STAINMATE:
9845                 if (WhiteOnMove(currentMove)) {
9846                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9847                 } else {
9848                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9849                 }
9850                 break;
9851                 
9852               case MT_STALEMATE:
9853                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9854                 break;
9855             }
9856
9857             break;
9858             
9859           case CMAIL_RESIGN:
9860             if (WhiteOnMove(currentMove)) {
9861                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9862             } else {
9863                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9864             }
9865             break;
9866             
9867           case CMAIL_ACCEPT:
9868             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9869             break;
9870               
9871           default:
9872             break;
9873         }
9874     }
9875
9876     return;
9877 }
9878
9879 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9880 int
9881 CmailLoadGame(f, gameNumber, title, useList)
9882      FILE *f;
9883      int gameNumber;
9884      char *title;
9885      int useList;
9886 {
9887     int retVal;
9888
9889     if (gameNumber > nCmailGames) {
9890         DisplayError(_("No more games in this message"), 0);
9891         return FALSE;
9892     }
9893     if (f == lastLoadGameFP) {
9894         int offset = gameNumber - lastLoadGameNumber;
9895         if (offset == 0) {
9896             cmailMsg[0] = NULLCHAR;
9897             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9898                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9899                 nCmailMovesRegistered--;
9900             }
9901             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9902             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9903                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9904             }
9905         } else {
9906             if (! RegisterMove()) return FALSE;
9907         }
9908     }
9909
9910     retVal = LoadGame(f, gameNumber, title, useList);
9911
9912     /* Make move registered during previous look at this game, if any */
9913     MakeRegisteredMove();
9914
9915     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9916         commentList[currentMove]
9917           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9918         DisplayComment(currentMove - 1, commentList[currentMove]);
9919     }
9920
9921     return retVal;
9922 }
9923
9924 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9925 int
9926 ReloadGame(offset)
9927      int offset;
9928 {
9929     int gameNumber = lastLoadGameNumber + offset;
9930     if (lastLoadGameFP == NULL) {
9931         DisplayError(_("No game has been loaded yet"), 0);
9932         return FALSE;
9933     }
9934     if (gameNumber <= 0) {
9935         DisplayError(_("Can't back up any further"), 0);
9936         return FALSE;
9937     }
9938     if (cmailMsgLoaded) {
9939         return CmailLoadGame(lastLoadGameFP, gameNumber,
9940                              lastLoadGameTitle, lastLoadGameUseList);
9941     } else {
9942         return LoadGame(lastLoadGameFP, gameNumber,
9943                         lastLoadGameTitle, lastLoadGameUseList);
9944     }
9945 }
9946
9947
9948
9949 /* Load the nth game from open file f */
9950 int
9951 LoadGame(f, gameNumber, title, useList)
9952      FILE *f;
9953      int gameNumber;
9954      char *title;
9955      int useList;
9956 {
9957     ChessMove cm;
9958     char buf[MSG_SIZ];
9959     int gn = gameNumber;
9960     ListGame *lg = NULL;
9961     int numPGNTags = 0;
9962     int err;
9963     GameMode oldGameMode;
9964     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9965
9966     if (appData.debugMode) 
9967         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9968
9969     if (gameMode == Training )
9970         SetTrainingModeOff();
9971
9972     oldGameMode = gameMode;
9973     if (gameMode != BeginningOfGame) {
9974       Reset(FALSE, TRUE);
9975     }
9976
9977     gameFileFP = f;
9978     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9979         fclose(lastLoadGameFP);
9980     }
9981
9982     if (useList) {
9983         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9984         
9985         if (lg) {
9986             fseek(f, lg->offset, 0);
9987             GameListHighlight(gameNumber);
9988             gn = 1;
9989         }
9990         else {
9991             DisplayError(_("Game number out of range"), 0);
9992             return FALSE;
9993         }
9994     } else {
9995         GameListDestroy();
9996         if (fseek(f, 0, 0) == -1) {
9997             if (f == lastLoadGameFP ?
9998                 gameNumber == lastLoadGameNumber + 1 :
9999                 gameNumber == 1) {
10000                 gn = 1;
10001             } else {
10002                 DisplayError(_("Can't seek on game file"), 0);
10003                 return FALSE;
10004             }
10005         }
10006     }
10007     lastLoadGameFP = f;
10008     lastLoadGameNumber = gameNumber;
10009     strcpy(lastLoadGameTitle, title);
10010     lastLoadGameUseList = useList;
10011
10012     yynewfile(f);
10013
10014     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10015       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10016                 lg->gameInfo.black);
10017             DisplayTitle(buf);
10018     } else if (*title != NULLCHAR) {
10019         if (gameNumber > 1) {
10020             sprintf(buf, "%s %d", title, gameNumber);
10021             DisplayTitle(buf);
10022         } else {
10023             DisplayTitle(title);
10024         }
10025     }
10026
10027     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10028         gameMode = PlayFromGameFile;
10029         ModeHighlight();
10030     }
10031
10032     currentMove = forwardMostMove = backwardMostMove = 0;
10033     CopyBoard(boards[0], initialPosition);
10034     StopClocks();
10035
10036     /*
10037      * Skip the first gn-1 games in the file.
10038      * Also skip over anything that precedes an identifiable 
10039      * start of game marker, to avoid being confused by 
10040      * garbage at the start of the file.  Currently 
10041      * recognized start of game markers are the move number "1",
10042      * the pattern "gnuchess .* game", the pattern
10043      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10044      * A game that starts with one of the latter two patterns
10045      * will also have a move number 1, possibly
10046      * following a position diagram.
10047      * 5-4-02: Let's try being more lenient and allowing a game to
10048      * start with an unnumbered move.  Does that break anything?
10049      */
10050     cm = lastLoadGameStart = (ChessMove) 0;
10051     while (gn > 0) {
10052         yyboardindex = forwardMostMove;
10053         cm = (ChessMove) yylex();
10054         switch (cm) {
10055           case (ChessMove) 0:
10056             if (cmailMsgLoaded) {
10057                 nCmailGames = CMAIL_MAX_GAMES - gn;
10058             } else {
10059                 Reset(TRUE, TRUE);
10060                 DisplayError(_("Game not found in file"), 0);
10061             }
10062             return FALSE;
10063
10064           case GNUChessGame:
10065           case XBoardGame:
10066             gn--;
10067             lastLoadGameStart = cm;
10068             break;
10069             
10070           case MoveNumberOne:
10071             switch (lastLoadGameStart) {
10072               case GNUChessGame:
10073               case XBoardGame:
10074               case PGNTag:
10075                 break;
10076               case MoveNumberOne:
10077               case (ChessMove) 0:
10078                 gn--;           /* count this game */
10079                 lastLoadGameStart = cm;
10080                 break;
10081               default:
10082                 /* impossible */
10083                 break;
10084             }
10085             break;
10086
10087           case PGNTag:
10088             switch (lastLoadGameStart) {
10089               case GNUChessGame:
10090               case PGNTag:
10091               case MoveNumberOne:
10092               case (ChessMove) 0:
10093                 gn--;           /* count this game */
10094                 lastLoadGameStart = cm;
10095                 break;
10096               case XBoardGame:
10097                 lastLoadGameStart = cm; /* game counted already */
10098                 break;
10099               default:
10100                 /* impossible */
10101                 break;
10102             }
10103             if (gn > 0) {
10104                 do {
10105                     yyboardindex = forwardMostMove;
10106                     cm = (ChessMove) yylex();
10107                 } while (cm == PGNTag || cm == Comment);
10108             }
10109             break;
10110
10111           case WhiteWins:
10112           case BlackWins:
10113           case GameIsDrawn:
10114             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10115                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10116                     != CMAIL_OLD_RESULT) {
10117                     nCmailResults ++ ;
10118                     cmailResult[  CMAIL_MAX_GAMES
10119                                 - gn - 1] = CMAIL_OLD_RESULT;
10120                 }
10121             }
10122             break;
10123
10124           case NormalMove:
10125             /* Only a NormalMove can be at the start of a game
10126              * without a position diagram. */
10127             if (lastLoadGameStart == (ChessMove) 0) {
10128               gn--;
10129               lastLoadGameStart = MoveNumberOne;
10130             }
10131             break;
10132
10133           default:
10134             break;
10135         }
10136     }
10137     
10138     if (appData.debugMode)
10139       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10140
10141     if (cm == XBoardGame) {
10142         /* Skip any header junk before position diagram and/or move 1 */
10143         for (;;) {
10144             yyboardindex = forwardMostMove;
10145             cm = (ChessMove) yylex();
10146
10147             if (cm == (ChessMove) 0 ||
10148                 cm == GNUChessGame || cm == XBoardGame) {
10149                 /* Empty game; pretend end-of-file and handle later */
10150                 cm = (ChessMove) 0;
10151                 break;
10152             }
10153
10154             if (cm == MoveNumberOne || cm == PositionDiagram ||
10155                 cm == PGNTag || cm == Comment)
10156               break;
10157         }
10158     } else if (cm == GNUChessGame) {
10159         if (gameInfo.event != NULL) {
10160             free(gameInfo.event);
10161         }
10162         gameInfo.event = StrSave(yy_text);
10163     }   
10164
10165     startedFromSetupPosition = FALSE;
10166     while (cm == PGNTag) {
10167         if (appData.debugMode) 
10168           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10169         err = ParsePGNTag(yy_text, &gameInfo);
10170         if (!err) numPGNTags++;
10171
10172         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10173         if(gameInfo.variant != oldVariant) {
10174             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10175             InitPosition(TRUE);
10176             oldVariant = gameInfo.variant;
10177             if (appData.debugMode) 
10178               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10179         }
10180
10181
10182         if (gameInfo.fen != NULL) {
10183           Board initial_position;
10184           startedFromSetupPosition = TRUE;
10185           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10186             Reset(TRUE, TRUE);
10187             DisplayError(_("Bad FEN position in file"), 0);
10188             return FALSE;
10189           }
10190           CopyBoard(boards[0], initial_position);
10191           if (blackPlaysFirst) {
10192             currentMove = forwardMostMove = backwardMostMove = 1;
10193             CopyBoard(boards[1], initial_position);
10194             strcpy(moveList[0], "");
10195             strcpy(parseList[0], "");
10196             timeRemaining[0][1] = whiteTimeRemaining;
10197             timeRemaining[1][1] = blackTimeRemaining;
10198             if (commentList[0] != NULL) {
10199               commentList[1] = commentList[0];
10200               commentList[0] = NULL;
10201             }
10202           } else {
10203             currentMove = forwardMostMove = backwardMostMove = 0;
10204           }
10205           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10206           {   int i;
10207               initialRulePlies = FENrulePlies;
10208               for( i=0; i< nrCastlingRights; i++ )
10209                   initialRights[i] = initial_position[CASTLING][i];
10210           }
10211           yyboardindex = forwardMostMove;
10212           free(gameInfo.fen);
10213           gameInfo.fen = NULL;
10214         }
10215
10216         yyboardindex = forwardMostMove;
10217         cm = (ChessMove) yylex();
10218
10219         /* Handle comments interspersed among the tags */
10220         while (cm == Comment) {
10221             char *p;
10222             if (appData.debugMode) 
10223               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10224             p = yy_text;
10225             AppendComment(currentMove, p, FALSE);
10226             yyboardindex = forwardMostMove;
10227             cm = (ChessMove) yylex();
10228         }
10229     }
10230
10231     /* don't rely on existence of Event tag since if game was
10232      * pasted from clipboard the Event tag may not exist
10233      */
10234     if (numPGNTags > 0){
10235         char *tags;
10236         if (gameInfo.variant == VariantNormal) {
10237           VariantClass v = StringToVariant(gameInfo.event);
10238           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10239           if(v < VariantShogi) gameInfo.variant = v;
10240         }
10241         if (!matchMode) {
10242           if( appData.autoDisplayTags ) {
10243             tags = PGNTags(&gameInfo);
10244             TagsPopUp(tags, CmailMsg());
10245             free(tags);
10246           }
10247         }
10248     } else {
10249         /* Make something up, but don't display it now */
10250         SetGameInfo();
10251         TagsPopDown();
10252     }
10253
10254     if (cm == PositionDiagram) {
10255         int i, j;
10256         char *p;
10257         Board initial_position;
10258
10259         if (appData.debugMode)
10260           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10261
10262         if (!startedFromSetupPosition) {
10263             p = yy_text;
10264             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10265               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10266                 switch (*p) {
10267                   case '[':
10268                   case '-':
10269                   case ' ':
10270                   case '\t':
10271                   case '\n':
10272                   case '\r':
10273                     break;
10274                   default:
10275                     initial_position[i][j++] = CharToPiece(*p);
10276                     break;
10277                 }
10278             while (*p == ' ' || *p == '\t' ||
10279                    *p == '\n' || *p == '\r') p++;
10280         
10281             if (strncmp(p, "black", strlen("black"))==0)
10282               blackPlaysFirst = TRUE;
10283             else
10284               blackPlaysFirst = FALSE;
10285             startedFromSetupPosition = TRUE;
10286         
10287             CopyBoard(boards[0], initial_position);
10288             if (blackPlaysFirst) {
10289                 currentMove = forwardMostMove = backwardMostMove = 1;
10290                 CopyBoard(boards[1], initial_position);
10291                 strcpy(moveList[0], "");
10292                 strcpy(parseList[0], "");
10293                 timeRemaining[0][1] = whiteTimeRemaining;
10294                 timeRemaining[1][1] = blackTimeRemaining;
10295                 if (commentList[0] != NULL) {
10296                     commentList[1] = commentList[0];
10297                     commentList[0] = NULL;
10298                 }
10299             } else {
10300                 currentMove = forwardMostMove = backwardMostMove = 0;
10301             }
10302         }
10303         yyboardindex = forwardMostMove;
10304         cm = (ChessMove) yylex();
10305     }
10306
10307     if (first.pr == NoProc) {
10308         StartChessProgram(&first);
10309     }
10310     InitChessProgram(&first, FALSE);
10311     SendToProgram("force\n", &first);
10312     if (startedFromSetupPosition) {
10313         SendBoard(&first, forwardMostMove);
10314     if (appData.debugMode) {
10315         fprintf(debugFP, "Load Game\n");
10316     }
10317         DisplayBothClocks();
10318     }      
10319
10320     /* [HGM] server: flag to write setup moves in broadcast file as one */
10321     loadFlag = appData.suppressLoadMoves;
10322
10323     while (cm == Comment) {
10324         char *p;
10325         if (appData.debugMode) 
10326           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10327         p = yy_text;
10328         AppendComment(currentMove, p, FALSE);
10329         yyboardindex = forwardMostMove;
10330         cm = (ChessMove) yylex();
10331     }
10332
10333     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10334         cm == WhiteWins || cm == BlackWins ||
10335         cm == GameIsDrawn || cm == GameUnfinished) {
10336         DisplayMessage("", _("No moves in game"));
10337         if (cmailMsgLoaded) {
10338             if (appData.debugMode)
10339               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10340             ClearHighlights();
10341             flipView = FALSE;
10342         }
10343         DrawPosition(FALSE, boards[currentMove]);
10344         DisplayBothClocks();
10345         gameMode = EditGame;
10346         ModeHighlight();
10347         gameFileFP = NULL;
10348         cmailOldMove = 0;
10349         return TRUE;
10350     }
10351
10352     // [HGM] PV info: routine tests if comment empty
10353     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10354         DisplayComment(currentMove - 1, commentList[currentMove]);
10355     }
10356     if (!matchMode && appData.timeDelay != 0) 
10357       DrawPosition(FALSE, boards[currentMove]);
10358
10359     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10360       programStats.ok_to_send = 1;
10361     }
10362
10363     /* if the first token after the PGN tags is a move
10364      * and not move number 1, retrieve it from the parser 
10365      */
10366     if (cm != MoveNumberOne)
10367         LoadGameOneMove(cm);
10368
10369     /* load the remaining moves from the file */
10370     while (LoadGameOneMove((ChessMove)0)) {
10371       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10372       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10373     }
10374
10375     /* rewind to the start of the game */
10376     currentMove = backwardMostMove;
10377
10378     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10379
10380     if (oldGameMode == AnalyzeFile ||
10381         oldGameMode == AnalyzeMode) {
10382       AnalyzeFileEvent();
10383     }
10384
10385     if (matchMode || appData.timeDelay == 0) {
10386       ToEndEvent();
10387       gameMode = EditGame;
10388       ModeHighlight();
10389     } else if (appData.timeDelay > 0) {
10390       AutoPlayGameLoop();
10391     }
10392
10393     if (appData.debugMode) 
10394         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10395
10396     loadFlag = 0; /* [HGM] true game starts */
10397     return TRUE;
10398 }
10399
10400 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10401 int
10402 ReloadPosition(offset)
10403      int offset;
10404 {
10405     int positionNumber = lastLoadPositionNumber + offset;
10406     if (lastLoadPositionFP == NULL) {
10407         DisplayError(_("No position has been loaded yet"), 0);
10408         return FALSE;
10409     }
10410     if (positionNumber <= 0) {
10411         DisplayError(_("Can't back up any further"), 0);
10412         return FALSE;
10413     }
10414     return LoadPosition(lastLoadPositionFP, positionNumber,
10415                         lastLoadPositionTitle);
10416 }
10417
10418 /* Load the nth position from the given file */
10419 int
10420 LoadPositionFromFile(filename, n, title)
10421      char *filename;
10422      int n;
10423      char *title;
10424 {
10425     FILE *f;
10426     char buf[MSG_SIZ];
10427
10428     if (strcmp(filename, "-") == 0) {
10429         return LoadPosition(stdin, n, "stdin");
10430     } else {
10431         f = fopen(filename, "rb");
10432         if (f == NULL) {
10433             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10434             DisplayError(buf, errno);
10435             return FALSE;
10436         } else {
10437             return LoadPosition(f, n, title);
10438         }
10439     }
10440 }
10441
10442 /* Load the nth position from the given open file, and close it */
10443 int
10444 LoadPosition(f, positionNumber, title)
10445      FILE *f;
10446      int positionNumber;
10447      char *title;
10448 {
10449     char *p, line[MSG_SIZ];
10450     Board initial_position;
10451     int i, j, fenMode, pn;
10452     
10453     if (gameMode == Training )
10454         SetTrainingModeOff();
10455
10456     if (gameMode != BeginningOfGame) {
10457         Reset(FALSE, TRUE);
10458     }
10459     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10460         fclose(lastLoadPositionFP);
10461     }
10462     if (positionNumber == 0) positionNumber = 1;
10463     lastLoadPositionFP = f;
10464     lastLoadPositionNumber = positionNumber;
10465     strcpy(lastLoadPositionTitle, title);
10466     if (first.pr == NoProc) {
10467       StartChessProgram(&first);
10468       InitChessProgram(&first, FALSE);
10469     }    
10470     pn = positionNumber;
10471     if (positionNumber < 0) {
10472         /* Negative position number means to seek to that byte offset */
10473         if (fseek(f, -positionNumber, 0) == -1) {
10474             DisplayError(_("Can't seek on position file"), 0);
10475             return FALSE;
10476         };
10477         pn = 1;
10478     } else {
10479         if (fseek(f, 0, 0) == -1) {
10480             if (f == lastLoadPositionFP ?
10481                 positionNumber == lastLoadPositionNumber + 1 :
10482                 positionNumber == 1) {
10483                 pn = 1;
10484             } else {
10485                 DisplayError(_("Can't seek on position file"), 0);
10486                 return FALSE;
10487             }
10488         }
10489     }
10490     /* See if this file is FEN or old-style xboard */
10491     if (fgets(line, MSG_SIZ, f) == NULL) {
10492         DisplayError(_("Position not found in file"), 0);
10493         return FALSE;
10494     }
10495     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10496     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10497
10498     if (pn >= 2) {
10499         if (fenMode || line[0] == '#') pn--;
10500         while (pn > 0) {
10501             /* skip positions before number pn */
10502             if (fgets(line, MSG_SIZ, f) == NULL) {
10503                 Reset(TRUE, TRUE);
10504                 DisplayError(_("Position not found in file"), 0);
10505                 return FALSE;
10506             }
10507             if (fenMode || line[0] == '#') pn--;
10508         }
10509     }
10510
10511     if (fenMode) {
10512         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10513             DisplayError(_("Bad FEN position in file"), 0);
10514             return FALSE;
10515         }
10516     } else {
10517         (void) fgets(line, MSG_SIZ, f);
10518         (void) fgets(line, MSG_SIZ, f);
10519     
10520         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10521             (void) fgets(line, MSG_SIZ, f);
10522             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10523                 if (*p == ' ')
10524                   continue;
10525                 initial_position[i][j++] = CharToPiece(*p);
10526             }
10527         }
10528     
10529         blackPlaysFirst = FALSE;
10530         if (!feof(f)) {
10531             (void) fgets(line, MSG_SIZ, f);
10532             if (strncmp(line, "black", strlen("black"))==0)
10533               blackPlaysFirst = TRUE;
10534         }
10535     }
10536     startedFromSetupPosition = TRUE;
10537     
10538     SendToProgram("force\n", &first);
10539     CopyBoard(boards[0], initial_position);
10540     if (blackPlaysFirst) {
10541         currentMove = forwardMostMove = backwardMostMove = 1;
10542         strcpy(moveList[0], "");
10543         strcpy(parseList[0], "");
10544         CopyBoard(boards[1], initial_position);
10545         DisplayMessage("", _("Black to play"));
10546     } else {
10547         currentMove = forwardMostMove = backwardMostMove = 0;
10548         DisplayMessage("", _("White to play"));
10549     }
10550     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10551     SendBoard(&first, forwardMostMove);
10552     if (appData.debugMode) {
10553 int i, j;
10554   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10555   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10556         fprintf(debugFP, "Load Position\n");
10557     }
10558
10559     if (positionNumber > 1) {
10560         sprintf(line, "%s %d", title, positionNumber);
10561         DisplayTitle(line);
10562     } else {
10563         DisplayTitle(title);
10564     }
10565     gameMode = EditGame;
10566     ModeHighlight();
10567     ResetClocks();
10568     timeRemaining[0][1] = whiteTimeRemaining;
10569     timeRemaining[1][1] = blackTimeRemaining;
10570     DrawPosition(FALSE, boards[currentMove]);
10571    
10572     return TRUE;
10573 }
10574
10575
10576 void
10577 CopyPlayerNameIntoFileName(dest, src)
10578      char **dest, *src;
10579 {
10580     while (*src != NULLCHAR && *src != ',') {
10581         if (*src == ' ') {
10582             *(*dest)++ = '_';
10583             src++;
10584         } else {
10585             *(*dest)++ = *src++;
10586         }
10587     }
10588 }
10589
10590 char *DefaultFileName(ext)
10591      char *ext;
10592 {
10593     static char def[MSG_SIZ];
10594     char *p;
10595
10596     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10597         p = def;
10598         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10599         *p++ = '-';
10600         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10601         *p++ = '.';
10602         strcpy(p, ext);
10603     } else {
10604         def[0] = NULLCHAR;
10605     }
10606     return def;
10607 }
10608
10609 /* Save the current game to the given file */
10610 int
10611 SaveGameToFile(filename, append)
10612      char *filename;
10613      int append;
10614 {
10615     FILE *f;
10616     char buf[MSG_SIZ];
10617
10618     if (strcmp(filename, "-") == 0) {
10619         return SaveGame(stdout, 0, NULL);
10620     } else {
10621         f = fopen(filename, append ? "a" : "w");
10622         if (f == NULL) {
10623             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10624             DisplayError(buf, errno);
10625             return FALSE;
10626         } else {
10627             return SaveGame(f, 0, NULL);
10628         }
10629     }
10630 }
10631
10632 char *
10633 SavePart(str)
10634      char *str;
10635 {
10636     static char buf[MSG_SIZ];
10637     char *p;
10638     
10639     p = strchr(str, ' ');
10640     if (p == NULL) return str;
10641     strncpy(buf, str, p - str);
10642     buf[p - str] = NULLCHAR;
10643     return buf;
10644 }
10645
10646 #define PGN_MAX_LINE 75
10647
10648 #define PGN_SIDE_WHITE  0
10649 #define PGN_SIDE_BLACK  1
10650
10651 /* [AS] */
10652 static int FindFirstMoveOutOfBook( int side )
10653 {
10654     int result = -1;
10655
10656     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10657         int index = backwardMostMove;
10658         int has_book_hit = 0;
10659
10660         if( (index % 2) != side ) {
10661             index++;
10662         }
10663
10664         while( index < forwardMostMove ) {
10665             /* Check to see if engine is in book */
10666             int depth = pvInfoList[index].depth;
10667             int score = pvInfoList[index].score;
10668             int in_book = 0;
10669
10670             if( depth <= 2 ) {
10671                 in_book = 1;
10672             }
10673             else if( score == 0 && depth == 63 ) {
10674                 in_book = 1; /* Zappa */
10675             }
10676             else if( score == 2 && depth == 99 ) {
10677                 in_book = 1; /* Abrok */
10678             }
10679
10680             has_book_hit += in_book;
10681
10682             if( ! in_book ) {
10683                 result = index;
10684
10685                 break;
10686             }
10687
10688             index += 2;
10689         }
10690     }
10691
10692     return result;
10693 }
10694
10695 /* [AS] */
10696 void GetOutOfBookInfo( char * buf )
10697 {
10698     int oob[2];
10699     int i;
10700     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10701
10702     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10703     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10704
10705     *buf = '\0';
10706
10707     if( oob[0] >= 0 || oob[1] >= 0 ) {
10708         for( i=0; i<2; i++ ) {
10709             int idx = oob[i];
10710
10711             if( idx >= 0 ) {
10712                 if( i > 0 && oob[0] >= 0 ) {
10713                     strcat( buf, "   " );
10714                 }
10715
10716                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10717                 sprintf( buf+strlen(buf), "%s%.2f", 
10718                     pvInfoList[idx].score >= 0 ? "+" : "",
10719                     pvInfoList[idx].score / 100.0 );
10720             }
10721         }
10722     }
10723 }
10724
10725 /* Save game in PGN style and close the file */
10726 int
10727 SaveGamePGN(f)
10728      FILE *f;
10729 {
10730     int i, offset, linelen, newblock;
10731     time_t tm;
10732 //    char *movetext;
10733     char numtext[32];
10734     int movelen, numlen, blank;
10735     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10736
10737     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10738     
10739     tm = time((time_t *) NULL);
10740     
10741     PrintPGNTags(f, &gameInfo);
10742     
10743     if (backwardMostMove > 0 || startedFromSetupPosition) {
10744         char *fen = PositionToFEN(backwardMostMove, NULL);
10745         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10746         fprintf(f, "\n{--------------\n");
10747         PrintPosition(f, backwardMostMove);
10748         fprintf(f, "--------------}\n");
10749         free(fen);
10750     }
10751     else {
10752         /* [AS] Out of book annotation */
10753         if( appData.saveOutOfBookInfo ) {
10754             char buf[64];
10755
10756             GetOutOfBookInfo( buf );
10757
10758             if( buf[0] != '\0' ) {
10759                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10760             }
10761         }
10762
10763         fprintf(f, "\n");
10764     }
10765
10766     i = backwardMostMove;
10767     linelen = 0;
10768     newblock = TRUE;
10769
10770     while (i < forwardMostMove) {
10771         /* Print comments preceding this move */
10772         if (commentList[i] != NULL) {
10773             if (linelen > 0) fprintf(f, "\n");
10774             fprintf(f, "%s", commentList[i]);
10775             linelen = 0;
10776             newblock = TRUE;
10777         }
10778
10779         /* Format move number */
10780         if ((i % 2) == 0) {
10781             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10782         } else {
10783             if (newblock) {
10784                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10785             } else {
10786                 numtext[0] = NULLCHAR;
10787             }
10788         }
10789         numlen = strlen(numtext);
10790         newblock = FALSE;
10791
10792         /* Print move number */
10793         blank = linelen > 0 && numlen > 0;
10794         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10795             fprintf(f, "\n");
10796             linelen = 0;
10797             blank = 0;
10798         }
10799         if (blank) {
10800             fprintf(f, " ");
10801             linelen++;
10802         }
10803         fprintf(f, "%s", numtext);
10804         linelen += numlen;
10805
10806         /* Get move */
10807         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10808         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10809
10810         /* Print move */
10811         blank = linelen > 0 && movelen > 0;
10812         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10813             fprintf(f, "\n");
10814             linelen = 0;
10815             blank = 0;
10816         }
10817         if (blank) {
10818             fprintf(f, " ");
10819             linelen++;
10820         }
10821         fprintf(f, "%s", move_buffer);
10822         linelen += movelen;
10823
10824         /* [AS] Add PV info if present */
10825         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10826             /* [HGM] add time */
10827             char buf[MSG_SIZ]; int seconds;
10828
10829             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10830
10831             if( seconds <= 0) buf[0] = 0; else
10832             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10833                 seconds = (seconds + 4)/10; // round to full seconds
10834                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10835                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10836             }
10837
10838             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10839                 pvInfoList[i].score >= 0 ? "+" : "",
10840                 pvInfoList[i].score / 100.0,
10841                 pvInfoList[i].depth,
10842                 buf );
10843
10844             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10845
10846             /* Print score/depth */
10847             blank = linelen > 0 && movelen > 0;
10848             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10849                 fprintf(f, "\n");
10850                 linelen = 0;
10851                 blank = 0;
10852             }
10853             if (blank) {
10854                 fprintf(f, " ");
10855                 linelen++;
10856             }
10857             fprintf(f, "%s", move_buffer);
10858             linelen += movelen;
10859         }
10860
10861         i++;
10862     }
10863     
10864     /* Start a new line */
10865     if (linelen > 0) fprintf(f, "\n");
10866
10867     /* Print comments after last move */
10868     if (commentList[i] != NULL) {
10869         fprintf(f, "%s\n", commentList[i]);
10870     }
10871
10872     /* Print result */
10873     if (gameInfo.resultDetails != NULL &&
10874         gameInfo.resultDetails[0] != NULLCHAR) {
10875         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10876                 PGNResult(gameInfo.result));
10877     } else {
10878         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10879     }
10880
10881     fclose(f);
10882     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10883     return TRUE;
10884 }
10885
10886 /* Save game in old style and close the file */
10887 int
10888 SaveGameOldStyle(f)
10889      FILE *f;
10890 {
10891     int i, offset;
10892     time_t tm;
10893     
10894     tm = time((time_t *) NULL);
10895     
10896     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10897     PrintOpponents(f);
10898     
10899     if (backwardMostMove > 0 || startedFromSetupPosition) {
10900         fprintf(f, "\n[--------------\n");
10901         PrintPosition(f, backwardMostMove);
10902         fprintf(f, "--------------]\n");
10903     } else {
10904         fprintf(f, "\n");
10905     }
10906
10907     i = backwardMostMove;
10908     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10909
10910     while (i < forwardMostMove) {
10911         if (commentList[i] != NULL) {
10912             fprintf(f, "[%s]\n", commentList[i]);
10913         }
10914
10915         if ((i % 2) == 1) {
10916             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10917             i++;
10918         } else {
10919             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10920             i++;
10921             if (commentList[i] != NULL) {
10922                 fprintf(f, "\n");
10923                 continue;
10924             }
10925             if (i >= forwardMostMove) {
10926                 fprintf(f, "\n");
10927                 break;
10928             }
10929             fprintf(f, "%s\n", parseList[i]);
10930             i++;
10931         }
10932     }
10933     
10934     if (commentList[i] != NULL) {
10935         fprintf(f, "[%s]\n", commentList[i]);
10936     }
10937
10938     /* This isn't really the old style, but it's close enough */
10939     if (gameInfo.resultDetails != NULL &&
10940         gameInfo.resultDetails[0] != NULLCHAR) {
10941         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10942                 gameInfo.resultDetails);
10943     } else {
10944         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10945     }
10946
10947     fclose(f);
10948     return TRUE;
10949 }
10950
10951 /* Save the current game to open file f and close the file */
10952 int
10953 SaveGame(f, dummy, dummy2)
10954      FILE *f;
10955      int dummy;
10956      char *dummy2;
10957 {
10958     if (gameMode == EditPosition) EditPositionDone(TRUE);
10959     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10960     if (appData.oldSaveStyle)
10961       return SaveGameOldStyle(f);
10962     else
10963       return SaveGamePGN(f);
10964 }
10965
10966 /* Save the current position to the given file */
10967 int
10968 SavePositionToFile(filename)
10969      char *filename;
10970 {
10971     FILE *f;
10972     char buf[MSG_SIZ];
10973
10974     if (strcmp(filename, "-") == 0) {
10975         return SavePosition(stdout, 0, NULL);
10976     } else {
10977         f = fopen(filename, "a");
10978         if (f == NULL) {
10979             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10980             DisplayError(buf, errno);
10981             return FALSE;
10982         } else {
10983             SavePosition(f, 0, NULL);
10984             return TRUE;
10985         }
10986     }
10987 }
10988
10989 /* Save the current position to the given open file and close the file */
10990 int
10991 SavePosition(f, dummy, dummy2)
10992      FILE *f;
10993      int dummy;
10994      char *dummy2;
10995 {
10996     time_t tm;
10997     char *fen;
10998     
10999     if (gameMode == EditPosition) EditPositionDone(TRUE);
11000     if (appData.oldSaveStyle) {
11001         tm = time((time_t *) NULL);
11002     
11003         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11004         PrintOpponents(f);
11005         fprintf(f, "[--------------\n");
11006         PrintPosition(f, currentMove);
11007         fprintf(f, "--------------]\n");
11008     } else {
11009         fen = PositionToFEN(currentMove, NULL);
11010         fprintf(f, "%s\n", fen);
11011         free(fen);
11012     }
11013     fclose(f);
11014     return TRUE;
11015 }
11016
11017 void
11018 ReloadCmailMsgEvent(unregister)
11019      int unregister;
11020 {
11021 #if !WIN32
11022     static char *inFilename = NULL;
11023     static char *outFilename;
11024     int i;
11025     struct stat inbuf, outbuf;
11026     int status;
11027     
11028     /* Any registered moves are unregistered if unregister is set, */
11029     /* i.e. invoked by the signal handler */
11030     if (unregister) {
11031         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11032             cmailMoveRegistered[i] = FALSE;
11033             if (cmailCommentList[i] != NULL) {
11034                 free(cmailCommentList[i]);
11035                 cmailCommentList[i] = NULL;
11036             }
11037         }
11038         nCmailMovesRegistered = 0;
11039     }
11040
11041     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11042         cmailResult[i] = CMAIL_NOT_RESULT;
11043     }
11044     nCmailResults = 0;
11045
11046     if (inFilename == NULL) {
11047         /* Because the filenames are static they only get malloced once  */
11048         /* and they never get freed                                      */
11049         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11050         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11051
11052         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11053         sprintf(outFilename, "%s.out", appData.cmailGameName);
11054     }
11055     
11056     status = stat(outFilename, &outbuf);
11057     if (status < 0) {
11058         cmailMailedMove = FALSE;
11059     } else {
11060         status = stat(inFilename, &inbuf);
11061         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11062     }
11063     
11064     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11065        counts the games, notes how each one terminated, etc.
11066        
11067        It would be nice to remove this kludge and instead gather all
11068        the information while building the game list.  (And to keep it
11069        in the game list nodes instead of having a bunch of fixed-size
11070        parallel arrays.)  Note this will require getting each game's
11071        termination from the PGN tags, as the game list builder does
11072        not process the game moves.  --mann
11073        */
11074     cmailMsgLoaded = TRUE;
11075     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11076     
11077     /* Load first game in the file or popup game menu */
11078     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11079
11080 #endif /* !WIN32 */
11081     return;
11082 }
11083
11084 int
11085 RegisterMove()
11086 {
11087     FILE *f;
11088     char string[MSG_SIZ];
11089
11090     if (   cmailMailedMove
11091         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11092         return TRUE;            /* Allow free viewing  */
11093     }
11094
11095     /* Unregister move to ensure that we don't leave RegisterMove        */
11096     /* with the move registered when the conditions for registering no   */
11097     /* longer hold                                                       */
11098     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11099         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11100         nCmailMovesRegistered --;
11101
11102         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11103           {
11104               free(cmailCommentList[lastLoadGameNumber - 1]);
11105               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11106           }
11107     }
11108
11109     if (cmailOldMove == -1) {
11110         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11111         return FALSE;
11112     }
11113
11114     if (currentMove > cmailOldMove + 1) {
11115         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11116         return FALSE;
11117     }
11118
11119     if (currentMove < cmailOldMove) {
11120         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11121         return FALSE;
11122     }
11123
11124     if (forwardMostMove > currentMove) {
11125         /* Silently truncate extra moves */
11126         TruncateGame();
11127     }
11128
11129     if (   (currentMove == cmailOldMove + 1)
11130         || (   (currentMove == cmailOldMove)
11131             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11132                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11133         if (gameInfo.result != GameUnfinished) {
11134             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11135         }
11136
11137         if (commentList[currentMove] != NULL) {
11138             cmailCommentList[lastLoadGameNumber - 1]
11139               = StrSave(commentList[currentMove]);
11140         }
11141         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11142
11143         if (appData.debugMode)
11144           fprintf(debugFP, "Saving %s for game %d\n",
11145                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11146
11147         sprintf(string,
11148                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11149         
11150         f = fopen(string, "w");
11151         if (appData.oldSaveStyle) {
11152             SaveGameOldStyle(f); /* also closes the file */
11153             
11154             sprintf(string, "%s.pos.out", appData.cmailGameName);
11155             f = fopen(string, "w");
11156             SavePosition(f, 0, NULL); /* also closes the file */
11157         } else {
11158             fprintf(f, "{--------------\n");
11159             PrintPosition(f, currentMove);
11160             fprintf(f, "--------------}\n\n");
11161             
11162             SaveGame(f, 0, NULL); /* also closes the file*/
11163         }
11164         
11165         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11166         nCmailMovesRegistered ++;
11167     } else if (nCmailGames == 1) {
11168         DisplayError(_("You have not made a move yet"), 0);
11169         return FALSE;
11170     }
11171
11172     return TRUE;
11173 }
11174
11175 void
11176 MailMoveEvent()
11177 {
11178 #if !WIN32
11179     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11180     FILE *commandOutput;
11181     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11182     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11183     int nBuffers;
11184     int i;
11185     int archived;
11186     char *arcDir;
11187
11188     if (! cmailMsgLoaded) {
11189         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11190         return;
11191     }
11192
11193     if (nCmailGames == nCmailResults) {
11194         DisplayError(_("No unfinished games"), 0);
11195         return;
11196     }
11197
11198 #if CMAIL_PROHIBIT_REMAIL
11199     if (cmailMailedMove) {
11200         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);
11201         DisplayError(msg, 0);
11202         return;
11203     }
11204 #endif
11205
11206     if (! (cmailMailedMove || RegisterMove())) return;
11207     
11208     if (   cmailMailedMove
11209         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11210         sprintf(string, partCommandString,
11211                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11212         commandOutput = popen(string, "r");
11213
11214         if (commandOutput == NULL) {
11215             DisplayError(_("Failed to invoke cmail"), 0);
11216         } else {
11217             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11218                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11219             }
11220             if (nBuffers > 1) {
11221                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11222                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11223                 nBytes = MSG_SIZ - 1;
11224             } else {
11225                 (void) memcpy(msg, buffer, nBytes);
11226             }
11227             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11228
11229             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11230                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11231
11232                 archived = TRUE;
11233                 for (i = 0; i < nCmailGames; i ++) {
11234                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11235                         archived = FALSE;
11236                     }
11237                 }
11238                 if (   archived
11239                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11240                         != NULL)) {
11241                     sprintf(buffer, "%s/%s.%s.archive",
11242                             arcDir,
11243                             appData.cmailGameName,
11244                             gameInfo.date);
11245                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11246                     cmailMsgLoaded = FALSE;
11247                 }
11248             }
11249
11250             DisplayInformation(msg);
11251             pclose(commandOutput);
11252         }
11253     } else {
11254         if ((*cmailMsg) != '\0') {
11255             DisplayInformation(cmailMsg);
11256         }
11257     }
11258
11259     return;
11260 #endif /* !WIN32 */
11261 }
11262
11263 char *
11264 CmailMsg()
11265 {
11266 #if WIN32
11267     return NULL;
11268 #else
11269     int  prependComma = 0;
11270     char number[5];
11271     char string[MSG_SIZ];       /* Space for game-list */
11272     int  i;
11273     
11274     if (!cmailMsgLoaded) return "";
11275
11276     if (cmailMailedMove) {
11277         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11278     } else {
11279         /* Create a list of games left */
11280         sprintf(string, "[");
11281         for (i = 0; i < nCmailGames; i ++) {
11282             if (! (   cmailMoveRegistered[i]
11283                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11284                 if (prependComma) {
11285                     sprintf(number, ",%d", i + 1);
11286                 } else {
11287                     sprintf(number, "%d", i + 1);
11288                     prependComma = 1;
11289                 }
11290                 
11291                 strcat(string, number);
11292             }
11293         }
11294         strcat(string, "]");
11295
11296         if (nCmailMovesRegistered + nCmailResults == 0) {
11297             switch (nCmailGames) {
11298               case 1:
11299                 sprintf(cmailMsg,
11300                         _("Still need to make move for game\n"));
11301                 break;
11302                 
11303               case 2:
11304                 sprintf(cmailMsg,
11305                         _("Still need to make moves for both games\n"));
11306                 break;
11307                 
11308               default:
11309                 sprintf(cmailMsg,
11310                         _("Still need to make moves for all %d games\n"),
11311                         nCmailGames);
11312                 break;
11313             }
11314         } else {
11315             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11316               case 1:
11317                 sprintf(cmailMsg,
11318                         _("Still need to make a move for game %s\n"),
11319                         string);
11320                 break;
11321                 
11322               case 0:
11323                 if (nCmailResults == nCmailGames) {
11324                     sprintf(cmailMsg, _("No unfinished games\n"));
11325                 } else {
11326                     sprintf(cmailMsg, _("Ready to send mail\n"));
11327                 }
11328                 break;
11329                 
11330               default:
11331                 sprintf(cmailMsg,
11332                         _("Still need to make moves for games %s\n"),
11333                         string);
11334             }
11335         }
11336     }
11337     return cmailMsg;
11338 #endif /* WIN32 */
11339 }
11340
11341 void
11342 ResetGameEvent()
11343 {
11344     if (gameMode == Training)
11345       SetTrainingModeOff();
11346
11347     Reset(TRUE, TRUE);
11348     cmailMsgLoaded = FALSE;
11349     if (appData.icsActive) {
11350       SendToICS(ics_prefix);
11351       SendToICS("refresh\n");
11352     }
11353 }
11354
11355 void
11356 ExitEvent(status)
11357      int status;
11358 {
11359     exiting++;
11360     if (exiting > 2) {
11361       /* Give up on clean exit */
11362       exit(status);
11363     }
11364     if (exiting > 1) {
11365       /* Keep trying for clean exit */
11366       return;
11367     }
11368
11369     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11370
11371     if (telnetISR != NULL) {
11372       RemoveInputSource(telnetISR);
11373     }
11374     if (icsPR != NoProc) {
11375       DestroyChildProcess(icsPR, TRUE);
11376     }
11377
11378     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11379     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11380
11381     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11382     /* make sure this other one finishes before killing it!                  */
11383     if(endingGame) { int count = 0;
11384         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11385         while(endingGame && count++ < 10) DoSleep(1);
11386         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11387     }
11388
11389     /* Kill off chess programs */
11390     if (first.pr != NoProc) {
11391         ExitAnalyzeMode();
11392         
11393         DoSleep( appData.delayBeforeQuit );
11394         SendToProgram("quit\n", &first);
11395         DoSleep( appData.delayAfterQuit );
11396         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11397     }
11398     if (second.pr != NoProc) {
11399         DoSleep( appData.delayBeforeQuit );
11400         SendToProgram("quit\n", &second);
11401         DoSleep( appData.delayAfterQuit );
11402         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11403     }
11404     if (first.isr != NULL) {
11405         RemoveInputSource(first.isr);
11406     }
11407     if (second.isr != NULL) {
11408         RemoveInputSource(second.isr);
11409     }
11410
11411     ShutDownFrontEnd();
11412     exit(status);
11413 }
11414
11415 void
11416 PauseEvent()
11417 {
11418     if (appData.debugMode)
11419         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11420     if (pausing) {
11421         pausing = FALSE;
11422         ModeHighlight();
11423         if (gameMode == MachinePlaysWhite ||
11424             gameMode == MachinePlaysBlack) {
11425             StartClocks();
11426         } else {
11427             DisplayBothClocks();
11428         }
11429         if (gameMode == PlayFromGameFile) {
11430             if (appData.timeDelay >= 0) 
11431                 AutoPlayGameLoop();
11432         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11433             Reset(FALSE, TRUE);
11434             SendToICS(ics_prefix);
11435             SendToICS("refresh\n");
11436         } else if (currentMove < forwardMostMove) {
11437             ForwardInner(forwardMostMove);
11438         }
11439         pauseExamInvalid = FALSE;
11440     } else {
11441         switch (gameMode) {
11442           default:
11443             return;
11444           case IcsExamining:
11445             pauseExamForwardMostMove = forwardMostMove;
11446             pauseExamInvalid = FALSE;
11447             /* fall through */
11448           case IcsObserving:
11449           case IcsPlayingWhite:
11450           case IcsPlayingBlack:
11451             pausing = TRUE;
11452             ModeHighlight();
11453             return;
11454           case PlayFromGameFile:
11455             (void) StopLoadGameTimer();
11456             pausing = TRUE;
11457             ModeHighlight();
11458             break;
11459           case BeginningOfGame:
11460             if (appData.icsActive) return;
11461             /* else fall through */
11462           case MachinePlaysWhite:
11463           case MachinePlaysBlack:
11464           case TwoMachinesPlay:
11465             if (forwardMostMove == 0)
11466               return;           /* don't pause if no one has moved */
11467             if ((gameMode == MachinePlaysWhite &&
11468                  !WhiteOnMove(forwardMostMove)) ||
11469                 (gameMode == MachinePlaysBlack &&
11470                  WhiteOnMove(forwardMostMove))) {
11471                 StopClocks();
11472             }
11473             pausing = TRUE;
11474             ModeHighlight();
11475             break;
11476         }
11477     }
11478 }
11479
11480 void
11481 EditCommentEvent()
11482 {
11483     char title[MSG_SIZ];
11484
11485     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11486         strcpy(title, _("Edit comment"));
11487     } else {
11488         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11489                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11490                 parseList[currentMove - 1]);
11491     }
11492
11493     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11494 }
11495
11496
11497 void
11498 EditTagsEvent()
11499 {
11500     char *tags = PGNTags(&gameInfo);
11501     EditTagsPopUp(tags);
11502     free(tags);
11503 }
11504
11505 void
11506 AnalyzeModeEvent()
11507 {
11508     if (appData.noChessProgram || gameMode == AnalyzeMode)
11509       return;
11510
11511     if (gameMode != AnalyzeFile) {
11512         if (!appData.icsEngineAnalyze) {
11513                EditGameEvent();
11514                if (gameMode != EditGame) return;
11515         }
11516         ResurrectChessProgram();
11517         SendToProgram("analyze\n", &first);
11518         first.analyzing = TRUE;
11519         /*first.maybeThinking = TRUE;*/
11520         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11521         EngineOutputPopUp();
11522     }
11523     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11524     pausing = FALSE;
11525     ModeHighlight();
11526     SetGameInfo();
11527
11528     StartAnalysisClock();
11529     GetTimeMark(&lastNodeCountTime);
11530     lastNodeCount = 0;
11531 }
11532
11533 void
11534 AnalyzeFileEvent()
11535 {
11536     if (appData.noChessProgram || gameMode == AnalyzeFile)
11537       return;
11538
11539     if (gameMode != AnalyzeMode) {
11540         EditGameEvent();
11541         if (gameMode != EditGame) return;
11542         ResurrectChessProgram();
11543         SendToProgram("analyze\n", &first);
11544         first.analyzing = TRUE;
11545         /*first.maybeThinking = TRUE;*/
11546         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11547         EngineOutputPopUp();
11548     }
11549     gameMode = AnalyzeFile;
11550     pausing = FALSE;
11551     ModeHighlight();
11552     SetGameInfo();
11553
11554     StartAnalysisClock();
11555     GetTimeMark(&lastNodeCountTime);
11556     lastNodeCount = 0;
11557 }
11558
11559 void
11560 MachineWhiteEvent()
11561 {
11562     char buf[MSG_SIZ];
11563     char *bookHit = NULL;
11564
11565     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11566       return;
11567
11568
11569     if (gameMode == PlayFromGameFile || 
11570         gameMode == TwoMachinesPlay  || 
11571         gameMode == Training         || 
11572         gameMode == AnalyzeMode      || 
11573         gameMode == EndOfGame)
11574         EditGameEvent();
11575
11576     if (gameMode == EditPosition) 
11577         EditPositionDone(TRUE);
11578
11579     if (!WhiteOnMove(currentMove)) {
11580         DisplayError(_("It is not White's turn"), 0);
11581         return;
11582     }
11583   
11584     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11585       ExitAnalyzeMode();
11586
11587     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11588         gameMode == AnalyzeFile)
11589         TruncateGame();
11590
11591     ResurrectChessProgram();    /* in case it isn't running */
11592     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11593         gameMode = MachinePlaysWhite;
11594         ResetClocks();
11595     } else
11596     gameMode = MachinePlaysWhite;
11597     pausing = FALSE;
11598     ModeHighlight();
11599     SetGameInfo();
11600     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11601     DisplayTitle(buf);
11602     if (first.sendName) {
11603       sprintf(buf, "name %s\n", gameInfo.black);
11604       SendToProgram(buf, &first);
11605     }
11606     if (first.sendTime) {
11607       if (first.useColors) {
11608         SendToProgram("black\n", &first); /*gnu kludge*/
11609       }
11610       SendTimeRemaining(&first, TRUE);
11611     }
11612     if (first.useColors) {
11613       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11614     }
11615     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11616     SetMachineThinkingEnables();
11617     first.maybeThinking = TRUE;
11618     StartClocks();
11619     firstMove = FALSE;
11620
11621     if (appData.autoFlipView && !flipView) {
11622       flipView = !flipView;
11623       DrawPosition(FALSE, NULL);
11624       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11625     }
11626
11627     if(bookHit) { // [HGM] book: simulate book reply
11628         static char bookMove[MSG_SIZ]; // a bit generous?
11629
11630         programStats.nodes = programStats.depth = programStats.time = 
11631         programStats.score = programStats.got_only_move = 0;
11632         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11633
11634         strcpy(bookMove, "move ");
11635         strcat(bookMove, bookHit);
11636         HandleMachineMove(bookMove, &first);
11637     }
11638 }
11639
11640 void
11641 MachineBlackEvent()
11642 {
11643     char buf[MSG_SIZ];
11644    char *bookHit = NULL;
11645
11646     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11647         return;
11648
11649
11650     if (gameMode == PlayFromGameFile || 
11651         gameMode == TwoMachinesPlay  || 
11652         gameMode == Training         || 
11653         gameMode == AnalyzeMode      || 
11654         gameMode == EndOfGame)
11655         EditGameEvent();
11656
11657     if (gameMode == EditPosition) 
11658         EditPositionDone(TRUE);
11659
11660     if (WhiteOnMove(currentMove)) {
11661         DisplayError(_("It is not Black's turn"), 0);
11662         return;
11663     }
11664     
11665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11666       ExitAnalyzeMode();
11667
11668     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11669         gameMode == AnalyzeFile)
11670         TruncateGame();
11671
11672     ResurrectChessProgram();    /* in case it isn't running */
11673     gameMode = MachinePlaysBlack;
11674     pausing = FALSE;
11675     ModeHighlight();
11676     SetGameInfo();
11677     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11678     DisplayTitle(buf);
11679     if (first.sendName) {
11680       sprintf(buf, "name %s\n", gameInfo.white);
11681       SendToProgram(buf, &first);
11682     }
11683     if (first.sendTime) {
11684       if (first.useColors) {
11685         SendToProgram("white\n", &first); /*gnu kludge*/
11686       }
11687       SendTimeRemaining(&first, FALSE);
11688     }
11689     if (first.useColors) {
11690       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11691     }
11692     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11693     SetMachineThinkingEnables();
11694     first.maybeThinking = TRUE;
11695     StartClocks();
11696
11697     if (appData.autoFlipView && flipView) {
11698       flipView = !flipView;
11699       DrawPosition(FALSE, NULL);
11700       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11701     }
11702     if(bookHit) { // [HGM] book: simulate book reply
11703         static char bookMove[MSG_SIZ]; // a bit generous?
11704
11705         programStats.nodes = programStats.depth = programStats.time = 
11706         programStats.score = programStats.got_only_move = 0;
11707         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11708
11709         strcpy(bookMove, "move ");
11710         strcat(bookMove, bookHit);
11711         HandleMachineMove(bookMove, &first);
11712     }
11713 }
11714
11715
11716 void
11717 DisplayTwoMachinesTitle()
11718 {
11719     char buf[MSG_SIZ];
11720     if (appData.matchGames > 0) {
11721         if (first.twoMachinesColor[0] == 'w') {
11722             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11723                     gameInfo.white, gameInfo.black,
11724                     first.matchWins, second.matchWins,
11725                     matchGame - 1 - (first.matchWins + second.matchWins));
11726         } else {
11727             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11728                     gameInfo.white, gameInfo.black,
11729                     second.matchWins, first.matchWins,
11730                     matchGame - 1 - (first.matchWins + second.matchWins));
11731         }
11732     } else {
11733         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11734     }
11735     DisplayTitle(buf);
11736 }
11737
11738 void
11739 TwoMachinesEvent P((void))
11740 {
11741     int i;
11742     char buf[MSG_SIZ];
11743     ChessProgramState *onmove;
11744     char *bookHit = NULL;
11745     
11746     if (appData.noChessProgram) return;
11747
11748     switch (gameMode) {
11749       case TwoMachinesPlay:
11750         return;
11751       case MachinePlaysWhite:
11752       case MachinePlaysBlack:
11753         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11754             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11755             return;
11756         }
11757         /* fall through */
11758       case BeginningOfGame:
11759       case PlayFromGameFile:
11760       case EndOfGame:
11761         EditGameEvent();
11762         if (gameMode != EditGame) return;
11763         break;
11764       case EditPosition:
11765         EditPositionDone(TRUE);
11766         break;
11767       case AnalyzeMode:
11768       case AnalyzeFile:
11769         ExitAnalyzeMode();
11770         break;
11771       case EditGame:
11772       default:
11773         break;
11774     }
11775
11776 //    forwardMostMove = currentMove;
11777     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11778     ResurrectChessProgram();    /* in case first program isn't running */
11779
11780     if (second.pr == NULL) {
11781         StartChessProgram(&second);
11782         if (second.protocolVersion == 1) {
11783           TwoMachinesEventIfReady();
11784         } else {
11785           /* kludge: allow timeout for initial "feature" command */
11786           FreezeUI();
11787           DisplayMessage("", _("Starting second chess program"));
11788           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11789         }
11790         return;
11791     }
11792     DisplayMessage("", "");
11793     InitChessProgram(&second, FALSE);
11794     SendToProgram("force\n", &second);
11795     if (startedFromSetupPosition) {
11796         SendBoard(&second, backwardMostMove);
11797     if (appData.debugMode) {
11798         fprintf(debugFP, "Two Machines\n");
11799     }
11800     }
11801     for (i = backwardMostMove; i < forwardMostMove; i++) {
11802         SendMoveToProgram(i, &second);
11803     }
11804
11805     gameMode = TwoMachinesPlay;
11806     pausing = FALSE;
11807     ModeHighlight();
11808     SetGameInfo();
11809     DisplayTwoMachinesTitle();
11810     firstMove = TRUE;
11811     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11812         onmove = &first;
11813     } else {
11814         onmove = &second;
11815     }
11816
11817     SendToProgram(first.computerString, &first);
11818     if (first.sendName) {
11819       sprintf(buf, "name %s\n", second.tidy);
11820       SendToProgram(buf, &first);
11821     }
11822     SendToProgram(second.computerString, &second);
11823     if (second.sendName) {
11824       sprintf(buf, "name %s\n", first.tidy);
11825       SendToProgram(buf, &second);
11826     }
11827
11828     ResetClocks();
11829     if (!first.sendTime || !second.sendTime) {
11830         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11831         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11832     }
11833     if (onmove->sendTime) {
11834       if (onmove->useColors) {
11835         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11836       }
11837       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11838     }
11839     if (onmove->useColors) {
11840       SendToProgram(onmove->twoMachinesColor, onmove);
11841     }
11842     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11843 //    SendToProgram("go\n", onmove);
11844     onmove->maybeThinking = TRUE;
11845     SetMachineThinkingEnables();
11846
11847     StartClocks();
11848
11849     if(bookHit) { // [HGM] book: simulate book reply
11850         static char bookMove[MSG_SIZ]; // a bit generous?
11851
11852         programStats.nodes = programStats.depth = programStats.time = 
11853         programStats.score = programStats.got_only_move = 0;
11854         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11855
11856         strcpy(bookMove, "move ");
11857         strcat(bookMove, bookHit);
11858         savedMessage = bookMove; // args for deferred call
11859         savedState = onmove;
11860         ScheduleDelayedEvent(DeferredBookMove, 1);
11861     }
11862 }
11863
11864 void
11865 TrainingEvent()
11866 {
11867     if (gameMode == Training) {
11868       SetTrainingModeOff();
11869       gameMode = PlayFromGameFile;
11870       DisplayMessage("", _("Training mode off"));
11871     } else {
11872       gameMode = Training;
11873       animateTraining = appData.animate;
11874
11875       /* make sure we are not already at the end of the game */
11876       if (currentMove < forwardMostMove) {
11877         SetTrainingModeOn();
11878         DisplayMessage("", _("Training mode on"));
11879       } else {
11880         gameMode = PlayFromGameFile;
11881         DisplayError(_("Already at end of game"), 0);
11882       }
11883     }
11884     ModeHighlight();
11885 }
11886
11887 void
11888 IcsClientEvent()
11889 {
11890     if (!appData.icsActive) return;
11891     switch (gameMode) {
11892       case IcsPlayingWhite:
11893       case IcsPlayingBlack:
11894       case IcsObserving:
11895       case IcsIdle:
11896       case BeginningOfGame:
11897       case IcsExamining:
11898         return;
11899
11900       case EditGame:
11901         break;
11902
11903       case EditPosition:
11904         EditPositionDone(TRUE);
11905         break;
11906
11907       case AnalyzeMode:
11908       case AnalyzeFile:
11909         ExitAnalyzeMode();
11910         break;
11911         
11912       default:
11913         EditGameEvent();
11914         break;
11915     }
11916
11917     gameMode = IcsIdle;
11918     ModeHighlight();
11919     return;
11920 }
11921
11922
11923 void
11924 EditGameEvent()
11925 {
11926     int i;
11927
11928     switch (gameMode) {
11929       case Training:
11930         SetTrainingModeOff();
11931         break;
11932       case MachinePlaysWhite:
11933       case MachinePlaysBlack:
11934       case BeginningOfGame:
11935         SendToProgram("force\n", &first);
11936         SetUserThinkingEnables();
11937         break;
11938       case PlayFromGameFile:
11939         (void) StopLoadGameTimer();
11940         if (gameFileFP != NULL) {
11941             gameFileFP = NULL;
11942         }
11943         break;
11944       case EditPosition:
11945         EditPositionDone(TRUE);
11946         break;
11947       case AnalyzeMode:
11948       case AnalyzeFile:
11949         ExitAnalyzeMode();
11950         SendToProgram("force\n", &first);
11951         break;
11952       case TwoMachinesPlay:
11953         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11954         ResurrectChessProgram();
11955         SetUserThinkingEnables();
11956         break;
11957       case EndOfGame:
11958         ResurrectChessProgram();
11959         break;
11960       case IcsPlayingBlack:
11961       case IcsPlayingWhite:
11962         DisplayError(_("Warning: You are still playing a game"), 0);
11963         break;
11964       case IcsObserving:
11965         DisplayError(_("Warning: You are still observing a game"), 0);
11966         break;
11967       case IcsExamining:
11968         DisplayError(_("Warning: You are still examining a game"), 0);
11969         break;
11970       case IcsIdle:
11971         break;
11972       case EditGame:
11973       default:
11974         return;
11975     }
11976     
11977     pausing = FALSE;
11978     StopClocks();
11979     first.offeredDraw = second.offeredDraw = 0;
11980
11981     if (gameMode == PlayFromGameFile) {
11982         whiteTimeRemaining = timeRemaining[0][currentMove];
11983         blackTimeRemaining = timeRemaining[1][currentMove];
11984         DisplayTitle("");
11985     }
11986
11987     if (gameMode == MachinePlaysWhite ||
11988         gameMode == MachinePlaysBlack ||
11989         gameMode == TwoMachinesPlay ||
11990         gameMode == EndOfGame) {
11991         i = forwardMostMove;
11992         while (i > currentMove) {
11993             SendToProgram("undo\n", &first);
11994             i--;
11995         }
11996         whiteTimeRemaining = timeRemaining[0][currentMove];
11997         blackTimeRemaining = timeRemaining[1][currentMove];
11998         DisplayBothClocks();
11999         if (whiteFlag || blackFlag) {
12000             whiteFlag = blackFlag = 0;
12001         }
12002         DisplayTitle("");
12003     }           
12004     
12005     gameMode = EditGame;
12006     ModeHighlight();
12007     SetGameInfo();
12008 }
12009
12010
12011 void
12012 EditPositionEvent()
12013 {
12014     if (gameMode == EditPosition) {
12015         EditGameEvent();
12016         return;
12017     }
12018     
12019     EditGameEvent();
12020     if (gameMode != EditGame) return;
12021     
12022     gameMode = EditPosition;
12023     ModeHighlight();
12024     SetGameInfo();
12025     if (currentMove > 0)
12026       CopyBoard(boards[0], boards[currentMove]);
12027     
12028     blackPlaysFirst = !WhiteOnMove(currentMove);
12029     ResetClocks();
12030     currentMove = forwardMostMove = backwardMostMove = 0;
12031     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12032     DisplayMove(-1);
12033 }
12034
12035 void
12036 ExitAnalyzeMode()
12037 {
12038     /* [DM] icsEngineAnalyze - possible call from other functions */
12039     if (appData.icsEngineAnalyze) {
12040         appData.icsEngineAnalyze = FALSE;
12041
12042         DisplayMessage("",_("Close ICS engine analyze..."));
12043     }
12044     if (first.analysisSupport && first.analyzing) {
12045       SendToProgram("exit\n", &first);
12046       first.analyzing = FALSE;
12047     }
12048     thinkOutput[0] = NULLCHAR;
12049 }
12050
12051 void
12052 EditPositionDone(Boolean fakeRights)
12053 {
12054     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12055
12056     startedFromSetupPosition = TRUE;
12057     InitChessProgram(&first, FALSE);
12058     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12059       boards[0][EP_STATUS] = EP_NONE;
12060       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12061     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12062         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12063         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12064       } else boards[0][CASTLING][2] = NoRights;
12065     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12066         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12067         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12068       } else boards[0][CASTLING][5] = NoRights;
12069     }
12070     SendToProgram("force\n", &first);
12071     if (blackPlaysFirst) {
12072         strcpy(moveList[0], "");
12073         strcpy(parseList[0], "");
12074         currentMove = forwardMostMove = backwardMostMove = 1;
12075         CopyBoard(boards[1], boards[0]);
12076     } else {
12077         currentMove = forwardMostMove = backwardMostMove = 0;
12078     }
12079     SendBoard(&first, forwardMostMove);
12080     if (appData.debugMode) {
12081         fprintf(debugFP, "EditPosDone\n");
12082     }
12083     DisplayTitle("");
12084     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12085     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12086     gameMode = EditGame;
12087     ModeHighlight();
12088     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12089     ClearHighlights(); /* [AS] */
12090 }
12091
12092 /* Pause for `ms' milliseconds */
12093 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12094 void
12095 TimeDelay(ms)
12096      long ms;
12097 {
12098     TimeMark m1, m2;
12099
12100     GetTimeMark(&m1);
12101     do {
12102         GetTimeMark(&m2);
12103     } while (SubtractTimeMarks(&m2, &m1) < ms);
12104 }
12105
12106 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12107 void
12108 SendMultiLineToICS(buf)
12109      char *buf;
12110 {
12111     char temp[MSG_SIZ+1], *p;
12112     int len;
12113
12114     len = strlen(buf);
12115     if (len > MSG_SIZ)
12116       len = MSG_SIZ;
12117   
12118     strncpy(temp, buf, len);
12119     temp[len] = 0;
12120
12121     p = temp;
12122     while (*p) {
12123         if (*p == '\n' || *p == '\r')
12124           *p = ' ';
12125         ++p;
12126     }
12127
12128     strcat(temp, "\n");
12129     SendToICS(temp);
12130     SendToPlayer(temp, strlen(temp));
12131 }
12132
12133 void
12134 SetWhiteToPlayEvent()
12135 {
12136     if (gameMode == EditPosition) {
12137         blackPlaysFirst = FALSE;
12138         DisplayBothClocks();    /* works because currentMove is 0 */
12139     } else if (gameMode == IcsExamining) {
12140         SendToICS(ics_prefix);
12141         SendToICS("tomove white\n");
12142     }
12143 }
12144
12145 void
12146 SetBlackToPlayEvent()
12147 {
12148     if (gameMode == EditPosition) {
12149         blackPlaysFirst = TRUE;
12150         currentMove = 1;        /* kludge */
12151         DisplayBothClocks();
12152         currentMove = 0;
12153     } else if (gameMode == IcsExamining) {
12154         SendToICS(ics_prefix);
12155         SendToICS("tomove black\n");
12156     }
12157 }
12158
12159 void
12160 EditPositionMenuEvent(selection, x, y)
12161      ChessSquare selection;
12162      int x, y;
12163 {
12164     char buf[MSG_SIZ];
12165     ChessSquare piece = boards[0][y][x];
12166
12167     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12168
12169     switch (selection) {
12170       case ClearBoard:
12171         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12172             SendToICS(ics_prefix);
12173             SendToICS("bsetup clear\n");
12174         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12175             SendToICS(ics_prefix);
12176             SendToICS("clearboard\n");
12177         } else {
12178             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12179                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12180                 for (y = 0; y < BOARD_HEIGHT; y++) {
12181                     if (gameMode == IcsExamining) {
12182                         if (boards[currentMove][y][x] != EmptySquare) {
12183                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12184                                     AAA + x, ONE + y);
12185                             SendToICS(buf);
12186                         }
12187                     } else {
12188                         boards[0][y][x] = p;
12189                     }
12190                 }
12191             }
12192         }
12193         if (gameMode == EditPosition) {
12194             DrawPosition(FALSE, boards[0]);
12195         }
12196         break;
12197
12198       case WhitePlay:
12199         SetWhiteToPlayEvent();
12200         break;
12201
12202       case BlackPlay:
12203         SetBlackToPlayEvent();
12204         break;
12205
12206       case EmptySquare:
12207         if (gameMode == IcsExamining) {
12208             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12209             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12210             SendToICS(buf);
12211         } else {
12212             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12213                 if(x == BOARD_LEFT-2) {
12214                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12215                     boards[0][y][1] = 0;
12216                 } else
12217                 if(x == BOARD_RGHT+1) {
12218                     if(y >= gameInfo.holdingsSize) break;
12219                     boards[0][y][BOARD_WIDTH-2] = 0;
12220                 } else break;
12221             }
12222             boards[0][y][x] = EmptySquare;
12223             DrawPosition(FALSE, boards[0]);
12224         }
12225         break;
12226
12227       case PromotePiece:
12228         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12229            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12230             selection = (ChessSquare) (PROMOTED piece);
12231         } else if(piece == EmptySquare) selection = WhiteSilver;
12232         else selection = (ChessSquare)((int)piece - 1);
12233         goto defaultlabel;
12234
12235       case DemotePiece:
12236         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12237            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12238             selection = (ChessSquare) (DEMOTED piece);
12239         } else if(piece == EmptySquare) selection = BlackSilver;
12240         else selection = (ChessSquare)((int)piece + 1);       
12241         goto defaultlabel;
12242
12243       case WhiteQueen:
12244       case BlackQueen:
12245         if(gameInfo.variant == VariantShatranj ||
12246            gameInfo.variant == VariantXiangqi  ||
12247            gameInfo.variant == VariantCourier  ||
12248            gameInfo.variant == VariantMakruk     )
12249             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12250         goto defaultlabel;
12251
12252       case WhiteKing:
12253       case BlackKing:
12254         if(gameInfo.variant == VariantXiangqi)
12255             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12256         if(gameInfo.variant == VariantKnightmate)
12257             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12258       default:
12259         defaultlabel:
12260         if (gameMode == IcsExamining) {
12261             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12262             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12263                     PieceToChar(selection), AAA + x, ONE + y);
12264             SendToICS(buf);
12265         } else {
12266             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12267                 int n;
12268                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12269                     n = PieceToNumber(selection - BlackPawn);
12270                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12271                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12272                     boards[0][BOARD_HEIGHT-1-n][1]++;
12273                 } else
12274                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12275                     n = PieceToNumber(selection);
12276                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12277                     boards[0][n][BOARD_WIDTH-1] = selection;
12278                     boards[0][n][BOARD_WIDTH-2]++;
12279                 }
12280             } else
12281             boards[0][y][x] = selection;
12282             DrawPosition(TRUE, boards[0]);
12283         }
12284         break;
12285     }
12286 }
12287
12288
12289 void
12290 DropMenuEvent(selection, x, y)
12291      ChessSquare selection;
12292      int x, y;
12293 {
12294     ChessMove moveType;
12295
12296     switch (gameMode) {
12297       case IcsPlayingWhite:
12298       case MachinePlaysBlack:
12299         if (!WhiteOnMove(currentMove)) {
12300             DisplayMoveError(_("It is Black's turn"));
12301             return;
12302         }
12303         moveType = WhiteDrop;
12304         break;
12305       case IcsPlayingBlack:
12306       case MachinePlaysWhite:
12307         if (WhiteOnMove(currentMove)) {
12308             DisplayMoveError(_("It is White's turn"));
12309             return;
12310         }
12311         moveType = BlackDrop;
12312         break;
12313       case EditGame:
12314         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12315         break;
12316       default:
12317         return;
12318     }
12319
12320     if (moveType == BlackDrop && selection < BlackPawn) {
12321       selection = (ChessSquare) ((int) selection
12322                                  + (int) BlackPawn - (int) WhitePawn);
12323     }
12324     if (boards[currentMove][y][x] != EmptySquare) {
12325         DisplayMoveError(_("That square is occupied"));
12326         return;
12327     }
12328
12329     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12330 }
12331
12332 void
12333 AcceptEvent()
12334 {
12335     /* Accept a pending offer of any kind from opponent */
12336     
12337     if (appData.icsActive) {
12338         SendToICS(ics_prefix);
12339         SendToICS("accept\n");
12340     } else if (cmailMsgLoaded) {
12341         if (currentMove == cmailOldMove &&
12342             commentList[cmailOldMove] != NULL &&
12343             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12344                    "Black offers a draw" : "White offers a draw")) {
12345             TruncateGame();
12346             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12347             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12348         } else {
12349             DisplayError(_("There is no pending offer on this move"), 0);
12350             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12351         }
12352     } else {
12353         /* Not used for offers from chess program */
12354     }
12355 }
12356
12357 void
12358 DeclineEvent()
12359 {
12360     /* Decline a pending offer of any kind from opponent */
12361     
12362     if (appData.icsActive) {
12363         SendToICS(ics_prefix);
12364         SendToICS("decline\n");
12365     } else if (cmailMsgLoaded) {
12366         if (currentMove == cmailOldMove &&
12367             commentList[cmailOldMove] != NULL &&
12368             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12369                    "Black offers a draw" : "White offers a draw")) {
12370 #ifdef NOTDEF
12371             AppendComment(cmailOldMove, "Draw declined", TRUE);
12372             DisplayComment(cmailOldMove - 1, "Draw declined");
12373 #endif /*NOTDEF*/
12374         } else {
12375             DisplayError(_("There is no pending offer on this move"), 0);
12376         }
12377     } else {
12378         /* Not used for offers from chess program */
12379     }
12380 }
12381
12382 void
12383 RematchEvent()
12384 {
12385     /* Issue ICS rematch command */
12386     if (appData.icsActive) {
12387         SendToICS(ics_prefix);
12388         SendToICS("rematch\n");
12389     }
12390 }
12391
12392 void
12393 CallFlagEvent()
12394 {
12395     /* Call your opponent's flag (claim a win on time) */
12396     if (appData.icsActive) {
12397         SendToICS(ics_prefix);
12398         SendToICS("flag\n");
12399     } else {
12400         switch (gameMode) {
12401           default:
12402             return;
12403           case MachinePlaysWhite:
12404             if (whiteFlag) {
12405                 if (blackFlag)
12406                   GameEnds(GameIsDrawn, "Both players ran out of time",
12407                            GE_PLAYER);
12408                 else
12409                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12410             } else {
12411                 DisplayError(_("Your opponent is not out of time"), 0);
12412             }
12413             break;
12414           case MachinePlaysBlack:
12415             if (blackFlag) {
12416                 if (whiteFlag)
12417                   GameEnds(GameIsDrawn, "Both players ran out of time",
12418                            GE_PLAYER);
12419                 else
12420                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12421             } else {
12422                 DisplayError(_("Your opponent is not out of time"), 0);
12423             }
12424             break;
12425         }
12426     }
12427 }
12428
12429 void
12430 DrawEvent()
12431 {
12432     /* Offer draw or accept pending draw offer from opponent */
12433     
12434     if (appData.icsActive) {
12435         /* Note: tournament rules require draw offers to be
12436            made after you make your move but before you punch
12437            your clock.  Currently ICS doesn't let you do that;
12438            instead, you immediately punch your clock after making
12439            a move, but you can offer a draw at any time. */
12440         
12441         SendToICS(ics_prefix);
12442         SendToICS("draw\n");
12443         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12444     } else if (cmailMsgLoaded) {
12445         if (currentMove == cmailOldMove &&
12446             commentList[cmailOldMove] != NULL &&
12447             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12448                    "Black offers a draw" : "White offers a draw")) {
12449             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12450             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12451         } else if (currentMove == cmailOldMove + 1) {
12452             char *offer = WhiteOnMove(cmailOldMove) ?
12453               "White offers a draw" : "Black offers a draw";
12454             AppendComment(currentMove, offer, TRUE);
12455             DisplayComment(currentMove - 1, offer);
12456             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12457         } else {
12458             DisplayError(_("You must make your move before offering a draw"), 0);
12459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12460         }
12461     } else if (first.offeredDraw) {
12462         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12463     } else {
12464         if (first.sendDrawOffers) {
12465             SendToProgram("draw\n", &first);
12466             userOfferedDraw = TRUE;
12467         }
12468     }
12469 }
12470
12471 void
12472 AdjournEvent()
12473 {
12474     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12475     
12476     if (appData.icsActive) {
12477         SendToICS(ics_prefix);
12478         SendToICS("adjourn\n");
12479     } else {
12480         /* Currently GNU Chess doesn't offer or accept Adjourns */
12481     }
12482 }
12483
12484
12485 void
12486 AbortEvent()
12487 {
12488     /* Offer Abort or accept pending Abort offer from opponent */
12489     
12490     if (appData.icsActive) {
12491         SendToICS(ics_prefix);
12492         SendToICS("abort\n");
12493     } else {
12494         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12495     }
12496 }
12497
12498 void
12499 ResignEvent()
12500 {
12501     /* Resign.  You can do this even if it's not your turn. */
12502     
12503     if (appData.icsActive) {
12504         SendToICS(ics_prefix);
12505         SendToICS("resign\n");
12506     } else {
12507         switch (gameMode) {
12508           case MachinePlaysWhite:
12509             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12510             break;
12511           case MachinePlaysBlack:
12512             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12513             break;
12514           case EditGame:
12515             if (cmailMsgLoaded) {
12516                 TruncateGame();
12517                 if (WhiteOnMove(cmailOldMove)) {
12518                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12519                 } else {
12520                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12521                 }
12522                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12523             }
12524             break;
12525           default:
12526             break;
12527         }
12528     }
12529 }
12530
12531
12532 void
12533 StopObservingEvent()
12534 {
12535     /* Stop observing current games */
12536     SendToICS(ics_prefix);
12537     SendToICS("unobserve\n");
12538 }
12539
12540 void
12541 StopExaminingEvent()
12542 {
12543     /* Stop observing current game */
12544     SendToICS(ics_prefix);
12545     SendToICS("unexamine\n");
12546 }
12547
12548 void
12549 ForwardInner(target)
12550      int target;
12551 {
12552     int limit;
12553
12554     if (appData.debugMode)
12555         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12556                 target, currentMove, forwardMostMove);
12557
12558     if (gameMode == EditPosition)
12559       return;
12560
12561     if (gameMode == PlayFromGameFile && !pausing)
12562       PauseEvent();
12563     
12564     if (gameMode == IcsExamining && pausing)
12565       limit = pauseExamForwardMostMove;
12566     else
12567       limit = forwardMostMove;
12568     
12569     if (target > limit) target = limit;
12570
12571     if (target > 0 && moveList[target - 1][0]) {
12572         int fromX, fromY, toX, toY;
12573         toX = moveList[target - 1][2] - AAA;
12574         toY = moveList[target - 1][3] - ONE;
12575         if (moveList[target - 1][1] == '@') {
12576             if (appData.highlightLastMove) {
12577                 SetHighlights(-1, -1, toX, toY);
12578             }
12579         } else {
12580             fromX = moveList[target - 1][0] - AAA;
12581             fromY = moveList[target - 1][1] - ONE;
12582             if (target == currentMove + 1) {
12583                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12584             }
12585             if (appData.highlightLastMove) {
12586                 SetHighlights(fromX, fromY, toX, toY);
12587             }
12588         }
12589     }
12590     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12591         gameMode == Training || gameMode == PlayFromGameFile || 
12592         gameMode == AnalyzeFile) {
12593         while (currentMove < target) {
12594             SendMoveToProgram(currentMove++, &first);
12595         }
12596     } else {
12597         currentMove = target;
12598     }
12599     
12600     if (gameMode == EditGame || gameMode == EndOfGame) {
12601         whiteTimeRemaining = timeRemaining[0][currentMove];
12602         blackTimeRemaining = timeRemaining[1][currentMove];
12603     }
12604     DisplayBothClocks();
12605     DisplayMove(currentMove - 1);
12606     DrawPosition(FALSE, boards[currentMove]);
12607     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12608     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12609         DisplayComment(currentMove - 1, commentList[currentMove]);
12610     }
12611 }
12612
12613
12614 void
12615 ForwardEvent()
12616 {
12617     if (gameMode == IcsExamining && !pausing) {
12618         SendToICS(ics_prefix);
12619         SendToICS("forward\n");
12620     } else {
12621         ForwardInner(currentMove + 1);
12622     }
12623 }
12624
12625 void
12626 ToEndEvent()
12627 {
12628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12629         /* to optimze, we temporarily turn off analysis mode while we feed
12630          * the remaining moves to the engine. Otherwise we get analysis output
12631          * after each move.
12632          */ 
12633         if (first.analysisSupport) {
12634           SendToProgram("exit\nforce\n", &first);
12635           first.analyzing = FALSE;
12636         }
12637     }
12638         
12639     if (gameMode == IcsExamining && !pausing) {
12640         SendToICS(ics_prefix);
12641         SendToICS("forward 999999\n");
12642     } else {
12643         ForwardInner(forwardMostMove);
12644     }
12645
12646     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12647         /* we have fed all the moves, so reactivate analysis mode */
12648         SendToProgram("analyze\n", &first);
12649         first.analyzing = TRUE;
12650         /*first.maybeThinking = TRUE;*/
12651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12652     }
12653 }
12654
12655 void
12656 BackwardInner(target)
12657      int target;
12658 {
12659     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12660
12661     if (appData.debugMode)
12662         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12663                 target, currentMove, forwardMostMove);
12664
12665     if (gameMode == EditPosition) return;
12666     if (currentMove <= backwardMostMove) {
12667         ClearHighlights();
12668         DrawPosition(full_redraw, boards[currentMove]);
12669         return;
12670     }
12671     if (gameMode == PlayFromGameFile && !pausing)
12672       PauseEvent();
12673     
12674     if (moveList[target][0]) {
12675         int fromX, fromY, toX, toY;
12676         toX = moveList[target][2] - AAA;
12677         toY = moveList[target][3] - ONE;
12678         if (moveList[target][1] == '@') {
12679             if (appData.highlightLastMove) {
12680                 SetHighlights(-1, -1, toX, toY);
12681             }
12682         } else {
12683             fromX = moveList[target][0] - AAA;
12684             fromY = moveList[target][1] - ONE;
12685             if (target == currentMove - 1) {
12686                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12687             }
12688             if (appData.highlightLastMove) {
12689                 SetHighlights(fromX, fromY, toX, toY);
12690             }
12691         }
12692     }
12693     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12694         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12695         while (currentMove > target) {
12696             SendToProgram("undo\n", &first);
12697             currentMove--;
12698         }
12699     } else {
12700         currentMove = target;
12701     }
12702     
12703     if (gameMode == EditGame || gameMode == EndOfGame) {
12704         whiteTimeRemaining = timeRemaining[0][currentMove];
12705         blackTimeRemaining = timeRemaining[1][currentMove];
12706     }
12707     DisplayBothClocks();
12708     DisplayMove(currentMove - 1);
12709     DrawPosition(full_redraw, boards[currentMove]);
12710     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12711     // [HGM] PV info: routine tests if comment empty
12712     DisplayComment(currentMove - 1, commentList[currentMove]);
12713 }
12714
12715 void
12716 BackwardEvent()
12717 {
12718     if (gameMode == IcsExamining && !pausing) {
12719         SendToICS(ics_prefix);
12720         SendToICS("backward\n");
12721     } else {
12722         BackwardInner(currentMove - 1);
12723     }
12724 }
12725
12726 void
12727 ToStartEvent()
12728 {
12729     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12730         /* to optimize, we temporarily turn off analysis mode while we undo
12731          * all the moves. Otherwise we get analysis output after each undo.
12732          */ 
12733         if (first.analysisSupport) {
12734           SendToProgram("exit\nforce\n", &first);
12735           first.analyzing = FALSE;
12736         }
12737     }
12738
12739     if (gameMode == IcsExamining && !pausing) {
12740         SendToICS(ics_prefix);
12741         SendToICS("backward 999999\n");
12742     } else {
12743         BackwardInner(backwardMostMove);
12744     }
12745
12746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12747         /* we have fed all the moves, so reactivate analysis mode */
12748         SendToProgram("analyze\n", &first);
12749         first.analyzing = TRUE;
12750         /*first.maybeThinking = TRUE;*/
12751         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12752     }
12753 }
12754
12755 void
12756 ToNrEvent(int to)
12757 {
12758   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12759   if (to >= forwardMostMove) to = forwardMostMove;
12760   if (to <= backwardMostMove) to = backwardMostMove;
12761   if (to < currentMove) {
12762     BackwardInner(to);
12763   } else {
12764     ForwardInner(to);
12765   }
12766 }
12767
12768 void
12769 RevertEvent(Boolean annotate)
12770 {
12771     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12772         return;
12773     }
12774     if (gameMode != IcsExamining) {
12775         DisplayError(_("You are not examining a game"), 0);
12776         return;
12777     }
12778     if (pausing) {
12779         DisplayError(_("You can't revert while pausing"), 0);
12780         return;
12781     }
12782     SendToICS(ics_prefix);
12783     SendToICS("revert\n");
12784 }
12785
12786 void
12787 RetractMoveEvent()
12788 {
12789     switch (gameMode) {
12790       case MachinePlaysWhite:
12791       case MachinePlaysBlack:
12792         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12793             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12794             return;
12795         }
12796         if (forwardMostMove < 2) return;
12797         currentMove = forwardMostMove = forwardMostMove - 2;
12798         whiteTimeRemaining = timeRemaining[0][currentMove];
12799         blackTimeRemaining = timeRemaining[1][currentMove];
12800         DisplayBothClocks();
12801         DisplayMove(currentMove - 1);
12802         ClearHighlights();/*!! could figure this out*/
12803         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12804         SendToProgram("remove\n", &first);
12805         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12806         break;
12807
12808       case BeginningOfGame:
12809       default:
12810         break;
12811
12812       case IcsPlayingWhite:
12813       case IcsPlayingBlack:
12814         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12815             SendToICS(ics_prefix);
12816             SendToICS("takeback 2\n");
12817         } else {
12818             SendToICS(ics_prefix);
12819             SendToICS("takeback 1\n");
12820         }
12821         break;
12822     }
12823 }
12824
12825 void
12826 MoveNowEvent()
12827 {
12828     ChessProgramState *cps;
12829
12830     switch (gameMode) {
12831       case MachinePlaysWhite:
12832         if (!WhiteOnMove(forwardMostMove)) {
12833             DisplayError(_("It is your turn"), 0);
12834             return;
12835         }
12836         cps = &first;
12837         break;
12838       case MachinePlaysBlack:
12839         if (WhiteOnMove(forwardMostMove)) {
12840             DisplayError(_("It is your turn"), 0);
12841             return;
12842         }
12843         cps = &first;
12844         break;
12845       case TwoMachinesPlay:
12846         if (WhiteOnMove(forwardMostMove) ==
12847             (first.twoMachinesColor[0] == 'w')) {
12848             cps = &first;
12849         } else {
12850             cps = &second;
12851         }
12852         break;
12853       case BeginningOfGame:
12854       default:
12855         return;
12856     }
12857     SendToProgram("?\n", cps);
12858 }
12859
12860 void
12861 TruncateGameEvent()
12862 {
12863     EditGameEvent();
12864     if (gameMode != EditGame) return;
12865     TruncateGame();
12866 }
12867
12868 void
12869 TruncateGame()
12870 {
12871     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12872     if (forwardMostMove > currentMove) {
12873         if (gameInfo.resultDetails != NULL) {
12874             free(gameInfo.resultDetails);
12875             gameInfo.resultDetails = NULL;
12876             gameInfo.result = GameUnfinished;
12877         }
12878         forwardMostMove = currentMove;
12879         HistorySet(parseList, backwardMostMove, forwardMostMove,
12880                    currentMove-1);
12881     }
12882 }
12883
12884 void
12885 HintEvent()
12886 {
12887     if (appData.noChessProgram) return;
12888     switch (gameMode) {
12889       case MachinePlaysWhite:
12890         if (WhiteOnMove(forwardMostMove)) {
12891             DisplayError(_("Wait until your turn"), 0);
12892             return;
12893         }
12894         break;
12895       case BeginningOfGame:
12896       case MachinePlaysBlack:
12897         if (!WhiteOnMove(forwardMostMove)) {
12898             DisplayError(_("Wait until your turn"), 0);
12899             return;
12900         }
12901         break;
12902       default:
12903         DisplayError(_("No hint available"), 0);
12904         return;
12905     }
12906     SendToProgram("hint\n", &first);
12907     hintRequested = TRUE;
12908 }
12909
12910 void
12911 BookEvent()
12912 {
12913     if (appData.noChessProgram) return;
12914     switch (gameMode) {
12915       case MachinePlaysWhite:
12916         if (WhiteOnMove(forwardMostMove)) {
12917             DisplayError(_("Wait until your turn"), 0);
12918             return;
12919         }
12920         break;
12921       case BeginningOfGame:
12922       case MachinePlaysBlack:
12923         if (!WhiteOnMove(forwardMostMove)) {
12924             DisplayError(_("Wait until your turn"), 0);
12925             return;
12926         }
12927         break;
12928       case EditPosition:
12929         EditPositionDone(TRUE);
12930         break;
12931       case TwoMachinesPlay:
12932         return;
12933       default:
12934         break;
12935     }
12936     SendToProgram("bk\n", &first);
12937     bookOutput[0] = NULLCHAR;
12938     bookRequested = TRUE;
12939 }
12940
12941 void
12942 AboutGameEvent()
12943 {
12944     char *tags = PGNTags(&gameInfo);
12945     TagsPopUp(tags, CmailMsg());
12946     free(tags);
12947 }
12948
12949 /* end button procedures */
12950
12951 void
12952 PrintPosition(fp, move)
12953      FILE *fp;
12954      int move;
12955 {
12956     int i, j;
12957     
12958     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12959         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12960             char c = PieceToChar(boards[move][i][j]);
12961             fputc(c == 'x' ? '.' : c, fp);
12962             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12963         }
12964     }
12965     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12966       fprintf(fp, "white to play\n");
12967     else
12968       fprintf(fp, "black to play\n");
12969 }
12970
12971 void
12972 PrintOpponents(fp)
12973      FILE *fp;
12974 {
12975     if (gameInfo.white != NULL) {
12976         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12977     } else {
12978         fprintf(fp, "\n");
12979     }
12980 }
12981
12982 /* Find last component of program's own name, using some heuristics */
12983 void
12984 TidyProgramName(prog, host, buf)
12985      char *prog, *host, buf[MSG_SIZ];
12986 {
12987     char *p, *q;
12988     int local = (strcmp(host, "localhost") == 0);
12989     while (!local && (p = strchr(prog, ';')) != NULL) {
12990         p++;
12991         while (*p == ' ') p++;
12992         prog = p;
12993     }
12994     if (*prog == '"' || *prog == '\'') {
12995         q = strchr(prog + 1, *prog);
12996     } else {
12997         q = strchr(prog, ' ');
12998     }
12999     if (q == NULL) q = prog + strlen(prog);
13000     p = q;
13001     while (p >= prog && *p != '/' && *p != '\\') p--;
13002     p++;
13003     if(p == prog && *p == '"') p++;
13004     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13005     memcpy(buf, p, q - p);
13006     buf[q - p] = NULLCHAR;
13007     if (!local) {
13008         strcat(buf, "@");
13009         strcat(buf, host);
13010     }
13011 }
13012
13013 char *
13014 TimeControlTagValue()
13015 {
13016     char buf[MSG_SIZ];
13017     if (!appData.clockMode) {
13018         strcpy(buf, "-");
13019     } else if (movesPerSession > 0) {
13020         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13021     } else if (timeIncrement == 0) {
13022         sprintf(buf, "%ld", timeControl/1000);
13023     } else {
13024         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13025     }
13026     return StrSave(buf);
13027 }
13028
13029 void
13030 SetGameInfo()
13031 {
13032     /* This routine is used only for certain modes */
13033     VariantClass v = gameInfo.variant;
13034     ChessMove r = GameUnfinished;
13035     char *p = NULL;
13036
13037     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13038         r = gameInfo.result; 
13039         p = gameInfo.resultDetails; 
13040         gameInfo.resultDetails = NULL;
13041     }
13042     ClearGameInfo(&gameInfo);
13043     gameInfo.variant = v;
13044
13045     switch (gameMode) {
13046       case MachinePlaysWhite:
13047         gameInfo.event = StrSave( appData.pgnEventHeader );
13048         gameInfo.site = StrSave(HostName());
13049         gameInfo.date = PGNDate();
13050         gameInfo.round = StrSave("-");
13051         gameInfo.white = StrSave(first.tidy);
13052         gameInfo.black = StrSave(UserName());
13053         gameInfo.timeControl = TimeControlTagValue();
13054         break;
13055
13056       case MachinePlaysBlack:
13057         gameInfo.event = StrSave( appData.pgnEventHeader );
13058         gameInfo.site = StrSave(HostName());
13059         gameInfo.date = PGNDate();
13060         gameInfo.round = StrSave("-");
13061         gameInfo.white = StrSave(UserName());
13062         gameInfo.black = StrSave(first.tidy);
13063         gameInfo.timeControl = TimeControlTagValue();
13064         break;
13065
13066       case TwoMachinesPlay:
13067         gameInfo.event = StrSave( appData.pgnEventHeader );
13068         gameInfo.site = StrSave(HostName());
13069         gameInfo.date = PGNDate();
13070         if (matchGame > 0) {
13071             char buf[MSG_SIZ];
13072             sprintf(buf, "%d", matchGame);
13073             gameInfo.round = StrSave(buf);
13074         } else {
13075             gameInfo.round = StrSave("-");
13076         }
13077         if (first.twoMachinesColor[0] == 'w') {
13078             gameInfo.white = StrSave(first.tidy);
13079             gameInfo.black = StrSave(second.tidy);
13080         } else {
13081             gameInfo.white = StrSave(second.tidy);
13082             gameInfo.black = StrSave(first.tidy);
13083         }
13084         gameInfo.timeControl = TimeControlTagValue();
13085         break;
13086
13087       case EditGame:
13088         gameInfo.event = StrSave("Edited game");
13089         gameInfo.site = StrSave(HostName());
13090         gameInfo.date = PGNDate();
13091         gameInfo.round = StrSave("-");
13092         gameInfo.white = StrSave("-");
13093         gameInfo.black = StrSave("-");
13094         gameInfo.result = r;
13095         gameInfo.resultDetails = p;
13096         break;
13097
13098       case EditPosition:
13099         gameInfo.event = StrSave("Edited position");
13100         gameInfo.site = StrSave(HostName());
13101         gameInfo.date = PGNDate();
13102         gameInfo.round = StrSave("-");
13103         gameInfo.white = StrSave("-");
13104         gameInfo.black = StrSave("-");
13105         break;
13106
13107       case IcsPlayingWhite:
13108       case IcsPlayingBlack:
13109       case IcsObserving:
13110       case IcsExamining:
13111         break;
13112
13113       case PlayFromGameFile:
13114         gameInfo.event = StrSave("Game from non-PGN file");
13115         gameInfo.site = StrSave(HostName());
13116         gameInfo.date = PGNDate();
13117         gameInfo.round = StrSave("-");
13118         gameInfo.white = StrSave("?");
13119         gameInfo.black = StrSave("?");
13120         break;
13121
13122       default:
13123         break;
13124     }
13125 }
13126
13127 void
13128 ReplaceComment(index, text)
13129      int index;
13130      char *text;
13131 {
13132     int len;
13133
13134     while (*text == '\n') text++;
13135     len = strlen(text);
13136     while (len > 0 && text[len - 1] == '\n') len--;
13137
13138     if (commentList[index] != NULL)
13139       free(commentList[index]);
13140
13141     if (len == 0) {
13142         commentList[index] = NULL;
13143         return;
13144     }
13145   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13146       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13147       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13148     commentList[index] = (char *) malloc(len + 2);
13149     strncpy(commentList[index], text, len);
13150     commentList[index][len] = '\n';
13151     commentList[index][len + 1] = NULLCHAR;
13152   } else { 
13153     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13154     char *p;
13155     commentList[index] = (char *) malloc(len + 6);
13156     strcpy(commentList[index], "{\n");
13157     strncpy(commentList[index]+2, text, len);
13158     commentList[index][len+2] = NULLCHAR;
13159     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13160     strcat(commentList[index], "\n}\n");
13161   }
13162 }
13163
13164 void
13165 CrushCRs(text)
13166      char *text;
13167 {
13168   char *p = text;
13169   char *q = text;
13170   char ch;
13171
13172   do {
13173     ch = *p++;
13174     if (ch == '\r') continue;
13175     *q++ = ch;
13176   } while (ch != '\0');
13177 }
13178
13179 void
13180 AppendComment(index, text, addBraces)
13181      int index;
13182      char *text;
13183      Boolean addBraces; // [HGM] braces: tells if we should add {}
13184 {
13185     int oldlen, len;
13186     char *old;
13187
13188 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13189     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13190
13191     CrushCRs(text);
13192     while (*text == '\n') text++;
13193     len = strlen(text);
13194     while (len > 0 && text[len - 1] == '\n') len--;
13195
13196     if (len == 0) return;
13197
13198     if (commentList[index] != NULL) {
13199         old = commentList[index];
13200         oldlen = strlen(old);
13201         while(commentList[index][oldlen-1] ==  '\n')
13202           commentList[index][--oldlen] = NULLCHAR;
13203         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13204         strcpy(commentList[index], old);
13205         free(old);
13206         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13207         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13208           if(addBraces) addBraces = FALSE; else { text++; len--; }
13209           while (*text == '\n') { text++; len--; }
13210           commentList[index][--oldlen] = NULLCHAR;
13211       }
13212         if(addBraces) strcat(commentList[index], "\n{\n");
13213         else          strcat(commentList[index], "\n");
13214         strcat(commentList[index], text);
13215         if(addBraces) strcat(commentList[index], "\n}\n");
13216         else          strcat(commentList[index], "\n");
13217     } else {
13218         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13219         if(addBraces)
13220              strcpy(commentList[index], "{\n");
13221         else commentList[index][0] = NULLCHAR;
13222         strcat(commentList[index], text);
13223         strcat(commentList[index], "\n");
13224         if(addBraces) strcat(commentList[index], "}\n");
13225     }
13226 }
13227
13228 static char * FindStr( char * text, char * sub_text )
13229 {
13230     char * result = strstr( text, sub_text );
13231
13232     if( result != NULL ) {
13233         result += strlen( sub_text );
13234     }
13235
13236     return result;
13237 }
13238
13239 /* [AS] Try to extract PV info from PGN comment */
13240 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13241 char *GetInfoFromComment( int index, char * text )
13242 {
13243     char * sep = text;
13244
13245     if( text != NULL && index > 0 ) {
13246         int score = 0;
13247         int depth = 0;
13248         int time = -1, sec = 0, deci;
13249         char * s_eval = FindStr( text, "[%eval " );
13250         char * s_emt = FindStr( text, "[%emt " );
13251
13252         if( s_eval != NULL || s_emt != NULL ) {
13253             /* New style */
13254             char delim;
13255
13256             if( s_eval != NULL ) {
13257                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13258                     return text;
13259                 }
13260
13261                 if( delim != ']' ) {
13262                     return text;
13263                 }
13264             }
13265
13266             if( s_emt != NULL ) {
13267             }
13268                 return text;
13269         }
13270         else {
13271             /* We expect something like: [+|-]nnn.nn/dd */
13272             int score_lo = 0;
13273
13274             if(*text != '{') return text; // [HGM] braces: must be normal comment
13275
13276             sep = strchr( text, '/' );
13277             if( sep == NULL || sep < (text+4) ) {
13278                 return text;
13279             }
13280
13281             time = -1; sec = -1; deci = -1;
13282             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13283                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13284                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13285                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13286                 return text;
13287             }
13288
13289             if( score_lo < 0 || score_lo >= 100 ) {
13290                 return text;
13291             }
13292
13293             if(sec >= 0) time = 600*time + 10*sec; else
13294             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13295
13296             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13297
13298             /* [HGM] PV time: now locate end of PV info */
13299             while( *++sep >= '0' && *sep <= '9'); // strip depth
13300             if(time >= 0)
13301             while( *++sep >= '0' && *sep <= '9'); // strip time
13302             if(sec >= 0)
13303             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13304             if(deci >= 0)
13305             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13306             while(*sep == ' ') sep++;
13307         }
13308
13309         if( depth <= 0 ) {
13310             return text;
13311         }
13312
13313         if( time < 0 ) {
13314             time = -1;
13315         }
13316
13317         pvInfoList[index-1].depth = depth;
13318         pvInfoList[index-1].score = score;
13319         pvInfoList[index-1].time  = 10*time; // centi-sec
13320         if(*sep == '}') *sep = 0; else *--sep = '{';
13321     }
13322     return sep;
13323 }
13324
13325 void
13326 SendToProgram(message, cps)
13327      char *message;
13328      ChessProgramState *cps;
13329 {
13330     int count, outCount, error;
13331     char buf[MSG_SIZ];
13332
13333     if (cps->pr == NULL) return;
13334     Attention(cps);
13335     
13336     if (appData.debugMode) {
13337         TimeMark now;
13338         GetTimeMark(&now);
13339         fprintf(debugFP, "%ld >%-6s: %s", 
13340                 SubtractTimeMarks(&now, &programStartTime),
13341                 cps->which, message);
13342     }
13343     
13344     count = strlen(message);
13345     outCount = OutputToProcess(cps->pr, message, count, &error);
13346     if (outCount < count && !exiting 
13347                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13348         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13349         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13350             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13351                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13352                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13353             } else {
13354                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13355             }
13356             gameInfo.resultDetails = StrSave(buf);
13357         }
13358         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13359     }
13360 }
13361
13362 void
13363 ReceiveFromProgram(isr, closure, message, count, error)
13364      InputSourceRef isr;
13365      VOIDSTAR closure;
13366      char *message;
13367      int count;
13368      int error;
13369 {
13370     char *end_str;
13371     char buf[MSG_SIZ];
13372     ChessProgramState *cps = (ChessProgramState *)closure;
13373
13374     if (isr != cps->isr) return; /* Killed intentionally */
13375     if (count <= 0) {
13376         if (count == 0) {
13377             sprintf(buf,
13378                     _("Error: %s chess program (%s) exited unexpectedly"),
13379                     cps->which, cps->program);
13380         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13381                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13382                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13383                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13384                 } else {
13385                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13386                 }
13387                 gameInfo.resultDetails = StrSave(buf);
13388             }
13389             RemoveInputSource(cps->isr);
13390             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13391         } else {
13392             sprintf(buf,
13393                     _("Error reading from %s chess program (%s)"),
13394                     cps->which, cps->program);
13395             RemoveInputSource(cps->isr);
13396
13397             /* [AS] Program is misbehaving badly... kill it */
13398             if( count == -2 ) {
13399                 DestroyChildProcess( cps->pr, 9 );
13400                 cps->pr = NoProc;
13401             }
13402
13403             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13404         }
13405         return;
13406     }
13407     
13408     if ((end_str = strchr(message, '\r')) != NULL)
13409       *end_str = NULLCHAR;
13410     if ((end_str = strchr(message, '\n')) != NULL)
13411       *end_str = NULLCHAR;
13412     
13413     if (appData.debugMode) {
13414         TimeMark now; int print = 1;
13415         char *quote = ""; char c; int i;
13416
13417         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13418                 char start = message[0];
13419                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13420                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13421                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13422                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13423                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13424                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13425                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13426                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13427                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13428                     print = (appData.engineComments >= 2);
13429                 }
13430                 message[0] = start; // restore original message
13431         }
13432         if(print) {
13433                 GetTimeMark(&now);
13434                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13435                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13436                         quote,
13437                         message);
13438         }
13439     }
13440
13441     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13442     if (appData.icsEngineAnalyze) {
13443         if (strstr(message, "whisper") != NULL ||
13444              strstr(message, "kibitz") != NULL || 
13445             strstr(message, "tellics") != NULL) return;
13446     }
13447
13448     HandleMachineMove(message, cps);
13449 }
13450
13451
13452 void
13453 SendTimeControl(cps, mps, tc, inc, sd, st)
13454      ChessProgramState *cps;
13455      int mps, inc, sd, st;
13456      long tc;
13457 {
13458     char buf[MSG_SIZ];
13459     int seconds;
13460
13461     if( timeControl_2 > 0 ) {
13462         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13463             tc = timeControl_2;
13464         }
13465     }
13466     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13467     inc /= cps->timeOdds;
13468     st  /= cps->timeOdds;
13469
13470     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13471
13472     if (st > 0) {
13473       /* Set exact time per move, normally using st command */
13474       if (cps->stKludge) {
13475         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13476         seconds = st % 60;
13477         if (seconds == 0) {
13478           sprintf(buf, "level 1 %d\n", st/60);
13479         } else {
13480           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13481         }
13482       } else {
13483         sprintf(buf, "st %d\n", st);
13484       }
13485     } else {
13486       /* Set conventional or incremental time control, using level command */
13487       if (seconds == 0) {
13488         /* Note old gnuchess bug -- minutes:seconds used to not work.
13489            Fixed in later versions, but still avoid :seconds
13490            when seconds is 0. */
13491         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13492       } else {
13493         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13494                 seconds, inc/1000);
13495       }
13496     }
13497     SendToProgram(buf, cps);
13498
13499     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13500     /* Orthogonally, limit search to given depth */
13501     if (sd > 0) {
13502       if (cps->sdKludge) {
13503         sprintf(buf, "depth\n%d\n", sd);
13504       } else {
13505         sprintf(buf, "sd %d\n", sd);
13506       }
13507       SendToProgram(buf, cps);
13508     }
13509
13510     if(cps->nps > 0) { /* [HGM] nps */
13511         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13512         else {
13513                 sprintf(buf, "nps %d\n", cps->nps);
13514               SendToProgram(buf, cps);
13515         }
13516     }
13517 }
13518
13519 ChessProgramState *WhitePlayer()
13520 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13521 {
13522     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13523        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13524         return &second;
13525     return &first;
13526 }
13527
13528 void
13529 SendTimeRemaining(cps, machineWhite)
13530      ChessProgramState *cps;
13531      int /*boolean*/ machineWhite;
13532 {
13533     char message[MSG_SIZ];
13534     long time, otime;
13535
13536     /* Note: this routine must be called when the clocks are stopped
13537        or when they have *just* been set or switched; otherwise
13538        it will be off by the time since the current tick started.
13539     */
13540     if (machineWhite) {
13541         time = whiteTimeRemaining / 10;
13542         otime = blackTimeRemaining / 10;
13543     } else {
13544         time = blackTimeRemaining / 10;
13545         otime = whiteTimeRemaining / 10;
13546     }
13547     /* [HGM] translate opponent's time by time-odds factor */
13548     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13549     if (appData.debugMode) {
13550         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13551     }
13552
13553     if (time <= 0) time = 1;
13554     if (otime <= 0) otime = 1;
13555     
13556     sprintf(message, "time %ld\n", time);
13557     SendToProgram(message, cps);
13558
13559     sprintf(message, "otim %ld\n", otime);
13560     SendToProgram(message, cps);
13561 }
13562
13563 int
13564 BoolFeature(p, name, loc, cps)
13565      char **p;
13566      char *name;
13567      int *loc;
13568      ChessProgramState *cps;
13569 {
13570   char buf[MSG_SIZ];
13571   int len = strlen(name);
13572   int val;
13573   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13574     (*p) += len + 1;
13575     sscanf(*p, "%d", &val);
13576     *loc = (val != 0);
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 IntFeature(p, name, loc, cps)
13587      char **p;
13588      char *name;
13589      int *loc;
13590      ChessProgramState *cps;
13591 {
13592   char buf[MSG_SIZ];
13593   int len = strlen(name);
13594   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13595     (*p) += len + 1;
13596     sscanf(*p, "%d", loc);
13597     while (**p && **p != ' ') (*p)++;
13598     sprintf(buf, "accepted %s\n", name);
13599     SendToProgram(buf, cps);
13600     return TRUE;
13601   }
13602   return FALSE;
13603 }
13604
13605 int
13606 StringFeature(p, name, loc, cps)
13607      char **p;
13608      char *name;
13609      char loc[];
13610      ChessProgramState *cps;
13611 {
13612   char buf[MSG_SIZ];
13613   int len = strlen(name);
13614   if (strncmp((*p), name, len) == 0
13615       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13616     (*p) += len + 2;
13617     sscanf(*p, "%[^\"]", loc);
13618     while (**p && **p != '\"') (*p)++;
13619     if (**p == '\"') (*p)++;
13620     sprintf(buf, "accepted %s\n", name);
13621     SendToProgram(buf, cps);
13622     return TRUE;
13623   }
13624   return FALSE;
13625 }
13626
13627 int 
13628 ParseOption(Option *opt, ChessProgramState *cps)
13629 // [HGM] options: process the string that defines an engine option, and determine
13630 // name, type, default value, and allowed value range
13631 {
13632         char *p, *q, buf[MSG_SIZ];
13633         int n, min = (-1)<<31, max = 1<<31, def;
13634
13635         if(p = strstr(opt->name, " -spin ")) {
13636             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13637             if(max < min) max = min; // enforce consistency
13638             if(def < min) def = min;
13639             if(def > max) def = max;
13640             opt->value = def;
13641             opt->min = min;
13642             opt->max = max;
13643             opt->type = Spin;
13644         } else if((p = strstr(opt->name, " -slider "))) {
13645             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13646             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13647             if(max < min) max = min; // enforce consistency
13648             if(def < min) def = min;
13649             if(def > max) def = max;
13650             opt->value = def;
13651             opt->min = min;
13652             opt->max = max;
13653             opt->type = Spin; // Slider;
13654         } else if((p = strstr(opt->name, " -string "))) {
13655             opt->textValue = p+9;
13656             opt->type = TextBox;
13657         } else if((p = strstr(opt->name, " -file "))) {
13658             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13659             opt->textValue = p+7;
13660             opt->type = TextBox; // FileName;
13661         } else if((p = strstr(opt->name, " -path "))) {
13662             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13663             opt->textValue = p+7;
13664             opt->type = TextBox; // PathName;
13665         } else if(p = strstr(opt->name, " -check ")) {
13666             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13667             opt->value = (def != 0);
13668             opt->type = CheckBox;
13669         } else if(p = strstr(opt->name, " -combo ")) {
13670             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13671             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13672             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13673             opt->value = n = 0;
13674             while(q = StrStr(q, " /// ")) {
13675                 n++; *q = 0;    // count choices, and null-terminate each of them
13676                 q += 5;
13677                 if(*q == '*') { // remember default, which is marked with * prefix
13678                     q++;
13679                     opt->value = n;
13680                 }
13681                 cps->comboList[cps->comboCnt++] = q;
13682             }
13683             cps->comboList[cps->comboCnt++] = NULL;
13684             opt->max = n + 1;
13685             opt->type = ComboBox;
13686         } else if(p = strstr(opt->name, " -button")) {
13687             opt->type = Button;
13688         } else if(p = strstr(opt->name, " -save")) {
13689             opt->type = SaveButton;
13690         } else return FALSE;
13691         *p = 0; // terminate option name
13692         // now look if the command-line options define a setting for this engine option.
13693         if(cps->optionSettings && cps->optionSettings[0])
13694             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13695         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13696                 sprintf(buf, "option %s", p);
13697                 if(p = strstr(buf, ",")) *p = 0;
13698                 strcat(buf, "\n");
13699                 SendToProgram(buf, cps);
13700         }
13701         return TRUE;
13702 }
13703
13704 void
13705 FeatureDone(cps, val)
13706      ChessProgramState* cps;
13707      int val;
13708 {
13709   DelayedEventCallback cb = GetDelayedEvent();
13710   if ((cb == InitBackEnd3 && cps == &first) ||
13711       (cb == TwoMachinesEventIfReady && cps == &second)) {
13712     CancelDelayedEvent();
13713     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13714   }
13715   cps->initDone = val;
13716 }
13717
13718 /* Parse feature command from engine */
13719 void
13720 ParseFeatures(args, cps)
13721      char* args;
13722      ChessProgramState *cps;  
13723 {
13724   char *p = args;
13725   char *q;
13726   int val;
13727   char buf[MSG_SIZ];
13728
13729   for (;;) {
13730     while (*p == ' ') p++;
13731     if (*p == NULLCHAR) return;
13732
13733     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13734     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13735     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13736     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13737     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13738     if (BoolFeature(&p, "reuse", &val, cps)) {
13739       /* Engine can disable reuse, but can't enable it if user said no */
13740       if (!val) cps->reuse = FALSE;
13741       continue;
13742     }
13743     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13744     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13745       if (gameMode == TwoMachinesPlay) {
13746         DisplayTwoMachinesTitle();
13747       } else {
13748         DisplayTitle("");
13749       }
13750       continue;
13751     }
13752     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13753     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13754     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13755     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13756     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13757     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13758     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13759     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13760     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13761     if (IntFeature(&p, "done", &val, cps)) {
13762       FeatureDone(cps, val);
13763       continue;
13764     }
13765     /* Added by Tord: */
13766     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13767     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13768     /* End of additions by Tord */
13769
13770     /* [HGM] added features: */
13771     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13772     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13773     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13774     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13775     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13776     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13777     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13778         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13779             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13780             SendToProgram(buf, cps);
13781             continue;
13782         }
13783         if(cps->nrOptions >= MAX_OPTIONS) {
13784             cps->nrOptions--;
13785             sprintf(buf, "%s engine has too many options\n", cps->which);
13786             DisplayError(buf, 0);
13787         }
13788         continue;
13789     }
13790     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13791     /* End of additions by HGM */
13792
13793     /* unknown feature: complain and skip */
13794     q = p;
13795     while (*q && *q != '=') q++;
13796     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13797     SendToProgram(buf, cps);
13798     p = q;
13799     if (*p == '=') {
13800       p++;
13801       if (*p == '\"') {
13802         p++;
13803         while (*p && *p != '\"') p++;
13804         if (*p == '\"') p++;
13805       } else {
13806         while (*p && *p != ' ') p++;
13807       }
13808     }
13809   }
13810
13811 }
13812
13813 void
13814 PeriodicUpdatesEvent(newState)
13815      int newState;
13816 {
13817     if (newState == appData.periodicUpdates)
13818       return;
13819
13820     appData.periodicUpdates=newState;
13821
13822     /* Display type changes, so update it now */
13823 //    DisplayAnalysis();
13824
13825     /* Get the ball rolling again... */
13826     if (newState) {
13827         AnalysisPeriodicEvent(1);
13828         StartAnalysisClock();
13829     }
13830 }
13831
13832 void
13833 PonderNextMoveEvent(newState)
13834      int newState;
13835 {
13836     if (newState == appData.ponderNextMove) return;
13837     if (gameMode == EditPosition) EditPositionDone(TRUE);
13838     if (newState) {
13839         SendToProgram("hard\n", &first);
13840         if (gameMode == TwoMachinesPlay) {
13841             SendToProgram("hard\n", &second);
13842         }
13843     } else {
13844         SendToProgram("easy\n", &first);
13845         thinkOutput[0] = NULLCHAR;
13846         if (gameMode == TwoMachinesPlay) {
13847             SendToProgram("easy\n", &second);
13848         }
13849     }
13850     appData.ponderNextMove = newState;
13851 }
13852
13853 void
13854 NewSettingEvent(option, command, value)
13855      char *command;
13856      int option, value;
13857 {
13858     char buf[MSG_SIZ];
13859
13860     if (gameMode == EditPosition) EditPositionDone(TRUE);
13861     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13862     SendToProgram(buf, &first);
13863     if (gameMode == TwoMachinesPlay) {
13864         SendToProgram(buf, &second);
13865     }
13866 }
13867
13868 void
13869 ShowThinkingEvent()
13870 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13871 {
13872     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13873     int newState = appData.showThinking
13874         // [HGM] thinking: other features now need thinking output as well
13875         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13876     
13877     if (oldState == newState) return;
13878     oldState = newState;
13879     if (gameMode == EditPosition) EditPositionDone(TRUE);
13880     if (oldState) {
13881         SendToProgram("post\n", &first);
13882         if (gameMode == TwoMachinesPlay) {
13883             SendToProgram("post\n", &second);
13884         }
13885     } else {
13886         SendToProgram("nopost\n", &first);
13887         thinkOutput[0] = NULLCHAR;
13888         if (gameMode == TwoMachinesPlay) {
13889             SendToProgram("nopost\n", &second);
13890         }
13891     }
13892 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13893 }
13894
13895 void
13896 AskQuestionEvent(title, question, replyPrefix, which)
13897      char *title; char *question; char *replyPrefix; char *which;
13898 {
13899   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13900   if (pr == NoProc) return;
13901   AskQuestion(title, question, replyPrefix, pr);
13902 }
13903
13904 void
13905 DisplayMove(moveNumber)
13906      int moveNumber;
13907 {
13908     char message[MSG_SIZ];
13909     char res[MSG_SIZ];
13910     char cpThinkOutput[MSG_SIZ];
13911
13912     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13913     
13914     if (moveNumber == forwardMostMove - 1 || 
13915         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13916
13917         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13918
13919         if (strchr(cpThinkOutput, '\n')) {
13920             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13921         }
13922     } else {
13923         *cpThinkOutput = NULLCHAR;
13924     }
13925
13926     /* [AS] Hide thinking from human user */
13927     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13928         *cpThinkOutput = NULLCHAR;
13929         if( thinkOutput[0] != NULLCHAR ) {
13930             int i;
13931
13932             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13933                 cpThinkOutput[i] = '.';
13934             }
13935             cpThinkOutput[i] = NULLCHAR;
13936             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13937         }
13938     }
13939
13940     if (moveNumber == forwardMostMove - 1 &&
13941         gameInfo.resultDetails != NULL) {
13942         if (gameInfo.resultDetails[0] == NULLCHAR) {
13943             sprintf(res, " %s", PGNResult(gameInfo.result));
13944         } else {
13945             sprintf(res, " {%s} %s",
13946                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13947         }
13948     } else {
13949         res[0] = NULLCHAR;
13950     }
13951
13952     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13953         DisplayMessage(res, cpThinkOutput);
13954     } else {
13955         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13956                 WhiteOnMove(moveNumber) ? " " : ".. ",
13957                 parseList[moveNumber], res);
13958         DisplayMessage(message, cpThinkOutput);
13959     }
13960 }
13961
13962 void
13963 DisplayComment(moveNumber, text)
13964      int moveNumber;
13965      char *text;
13966 {
13967     char title[MSG_SIZ];
13968     char buf[8000]; // comment can be long!
13969     int score, depth;
13970     
13971     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13972       strcpy(title, "Comment");
13973     } else {
13974       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13975               WhiteOnMove(moveNumber) ? " " : ".. ",
13976               parseList[moveNumber]);
13977     }
13978     // [HGM] PV info: display PV info together with (or as) comment
13979     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13980       if(text == NULL) text = "";                                           
13981       score = pvInfoList[moveNumber].score;
13982       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13983               depth, (pvInfoList[moveNumber].time+50)/100, text);
13984       text = buf;
13985     }
13986     if (text != NULL && (appData.autoDisplayComment || commentUp))
13987         CommentPopUp(title, text);
13988 }
13989
13990 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13991  * might be busy thinking or pondering.  It can be omitted if your
13992  * gnuchess is configured to stop thinking immediately on any user
13993  * input.  However, that gnuchess feature depends on the FIONREAD
13994  * ioctl, which does not work properly on some flavors of Unix.
13995  */
13996 void
13997 Attention(cps)
13998      ChessProgramState *cps;
13999 {
14000 #if ATTENTION
14001     if (!cps->useSigint) return;
14002     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14003     switch (gameMode) {
14004       case MachinePlaysWhite:
14005       case MachinePlaysBlack:
14006       case TwoMachinesPlay:
14007       case IcsPlayingWhite:
14008       case IcsPlayingBlack:
14009       case AnalyzeMode:
14010       case AnalyzeFile:
14011         /* Skip if we know it isn't thinking */
14012         if (!cps->maybeThinking) return;
14013         if (appData.debugMode)
14014           fprintf(debugFP, "Interrupting %s\n", cps->which);
14015         InterruptChildProcess(cps->pr);
14016         cps->maybeThinking = FALSE;
14017         break;
14018       default:
14019         break;
14020     }
14021 #endif /*ATTENTION*/
14022 }
14023
14024 int
14025 CheckFlags()
14026 {
14027     if (whiteTimeRemaining <= 0) {
14028         if (!whiteFlag) {
14029             whiteFlag = TRUE;
14030             if (appData.icsActive) {
14031                 if (appData.autoCallFlag &&
14032                     gameMode == IcsPlayingBlack && !blackFlag) {
14033                   SendToICS(ics_prefix);
14034                   SendToICS("flag\n");
14035                 }
14036             } else {
14037                 if (blackFlag) {
14038                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14039                 } else {
14040                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14041                     if (appData.autoCallFlag) {
14042                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14043                         return TRUE;
14044                     }
14045                 }
14046             }
14047         }
14048     }
14049     if (blackTimeRemaining <= 0) {
14050         if (!blackFlag) {
14051             blackFlag = TRUE;
14052             if (appData.icsActive) {
14053                 if (appData.autoCallFlag &&
14054                     gameMode == IcsPlayingWhite && !whiteFlag) {
14055                   SendToICS(ics_prefix);
14056                   SendToICS("flag\n");
14057                 }
14058             } else {
14059                 if (whiteFlag) {
14060                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14061                 } else {
14062                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14063                     if (appData.autoCallFlag) {
14064                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14065                         return TRUE;
14066                     }
14067                 }
14068             }
14069         }
14070     }
14071     return FALSE;
14072 }
14073
14074 void
14075 CheckTimeControl()
14076 {
14077     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14078         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14079
14080     /*
14081      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14082      */
14083     if ( !WhiteOnMove(forwardMostMove) )
14084         /* White made time control */
14085         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14086         /* [HGM] time odds: correct new time quota for time odds! */
14087                                             / WhitePlayer()->timeOdds;
14088       else
14089         /* Black made time control */
14090         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14091                                             / WhitePlayer()->other->timeOdds;
14092 }
14093
14094 void
14095 DisplayBothClocks()
14096 {
14097     int wom = gameMode == EditPosition ?
14098       !blackPlaysFirst : WhiteOnMove(currentMove);
14099     DisplayWhiteClock(whiteTimeRemaining, wom);
14100     DisplayBlackClock(blackTimeRemaining, !wom);
14101 }
14102
14103
14104 /* Timekeeping seems to be a portability nightmare.  I think everyone
14105    has ftime(), but I'm really not sure, so I'm including some ifdefs
14106    to use other calls if you don't.  Clocks will be less accurate if
14107    you have neither ftime nor gettimeofday.
14108 */
14109
14110 /* VS 2008 requires the #include outside of the function */
14111 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14112 #include <sys/timeb.h>
14113 #endif
14114
14115 /* Get the current time as a TimeMark */
14116 void
14117 GetTimeMark(tm)
14118      TimeMark *tm;
14119 {
14120 #if HAVE_GETTIMEOFDAY
14121
14122     struct timeval timeVal;
14123     struct timezone timeZone;
14124
14125     gettimeofday(&timeVal, &timeZone);
14126     tm->sec = (long) timeVal.tv_sec; 
14127     tm->ms = (int) (timeVal.tv_usec / 1000L);
14128
14129 #else /*!HAVE_GETTIMEOFDAY*/
14130 #if HAVE_FTIME
14131
14132 // include <sys/timeb.h> / moved to just above start of function
14133     struct timeb timeB;
14134
14135     ftime(&timeB);
14136     tm->sec = (long) timeB.time;
14137     tm->ms = (int) timeB.millitm;
14138
14139 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14140     tm->sec = (long) time(NULL);
14141     tm->ms = 0;
14142 #endif
14143 #endif
14144 }
14145
14146 /* Return the difference in milliseconds between two
14147    time marks.  We assume the difference will fit in a long!
14148 */
14149 long
14150 SubtractTimeMarks(tm2, tm1)
14151      TimeMark *tm2, *tm1;
14152 {
14153     return 1000L*(tm2->sec - tm1->sec) +
14154            (long) (tm2->ms - tm1->ms);
14155 }
14156
14157
14158 /*
14159  * Code to manage the game clocks.
14160  *
14161  * In tournament play, black starts the clock and then white makes a move.
14162  * We give the human user a slight advantage if he is playing white---the
14163  * clocks don't run until he makes his first move, so it takes zero time.
14164  * Also, we don't account for network lag, so we could get out of sync
14165  * with GNU Chess's clock -- but then, referees are always right.  
14166  */
14167
14168 static TimeMark tickStartTM;
14169 static long intendedTickLength;
14170
14171 long
14172 NextTickLength(timeRemaining)
14173      long timeRemaining;
14174 {
14175     long nominalTickLength, nextTickLength;
14176
14177     if (timeRemaining > 0L && timeRemaining <= 10000L)
14178       nominalTickLength = 100L;
14179     else
14180       nominalTickLength = 1000L;
14181     nextTickLength = timeRemaining % nominalTickLength;
14182     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14183
14184     return nextTickLength;
14185 }
14186
14187 /* Adjust clock one minute up or down */
14188 void
14189 AdjustClock(Boolean which, int dir)
14190 {
14191     if(which) blackTimeRemaining += 60000*dir;
14192     else      whiteTimeRemaining += 60000*dir;
14193     DisplayBothClocks();
14194 }
14195
14196 /* Stop clocks and reset to a fresh time control */
14197 void
14198 ResetClocks() 
14199 {
14200     (void) StopClockTimer();
14201     if (appData.icsActive) {
14202         whiteTimeRemaining = blackTimeRemaining = 0;
14203     } else if (searchTime) {
14204         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14205         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14206     } else { /* [HGM] correct new time quote for time odds */
14207         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14208         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14209     }
14210     if (whiteFlag || blackFlag) {
14211         DisplayTitle("");
14212         whiteFlag = blackFlag = FALSE;
14213     }
14214     DisplayBothClocks();
14215 }
14216
14217 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14218
14219 /* Decrement running clock by amount of time that has passed */
14220 void
14221 DecrementClocks()
14222 {
14223     long timeRemaining;
14224     long lastTickLength, fudge;
14225     TimeMark now;
14226
14227     if (!appData.clockMode) return;
14228     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14229         
14230     GetTimeMark(&now);
14231
14232     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14233
14234     /* Fudge if we woke up a little too soon */
14235     fudge = intendedTickLength - lastTickLength;
14236     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14237
14238     if (WhiteOnMove(forwardMostMove)) {
14239         if(whiteNPS >= 0) lastTickLength = 0;
14240         timeRemaining = whiteTimeRemaining -= lastTickLength;
14241         DisplayWhiteClock(whiteTimeRemaining - fudge,
14242                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14243     } else {
14244         if(blackNPS >= 0) lastTickLength = 0;
14245         timeRemaining = blackTimeRemaining -= lastTickLength;
14246         DisplayBlackClock(blackTimeRemaining - fudge,
14247                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14248     }
14249
14250     if (CheckFlags()) return;
14251         
14252     tickStartTM = now;
14253     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14254     StartClockTimer(intendedTickLength);
14255
14256     /* if the time remaining has fallen below the alarm threshold, sound the
14257      * alarm. if the alarm has sounded and (due to a takeback or time control
14258      * with increment) the time remaining has increased to a level above the
14259      * threshold, reset the alarm so it can sound again. 
14260      */
14261     
14262     if (appData.icsActive && appData.icsAlarm) {
14263
14264         /* make sure we are dealing with the user's clock */
14265         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14266                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14267            )) return;
14268
14269         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14270             alarmSounded = FALSE;
14271         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14272             PlayAlarmSound();
14273             alarmSounded = TRUE;
14274         }
14275     }
14276 }
14277
14278
14279 /* A player has just moved, so stop the previously running
14280    clock and (if in clock mode) start the other one.
14281    We redisplay both clocks in case we're in ICS mode, because
14282    ICS gives us an update to both clocks after every move.
14283    Note that this routine is called *after* forwardMostMove
14284    is updated, so the last fractional tick must be subtracted
14285    from the color that is *not* on move now.
14286 */
14287 void
14288 SwitchClocks(int newMoveNr)
14289 {
14290     long lastTickLength;
14291     TimeMark now;
14292     int flagged = FALSE;
14293
14294     GetTimeMark(&now);
14295
14296     if (StopClockTimer() && appData.clockMode) {
14297         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14298         if (!WhiteOnMove(forwardMostMove)) {
14299             if(blackNPS >= 0) lastTickLength = 0;
14300             blackTimeRemaining -= lastTickLength;
14301            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14302 //         if(pvInfoList[forwardMostMove-1].time == -1)
14303                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14304                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14305         } else {
14306            if(whiteNPS >= 0) lastTickLength = 0;
14307            whiteTimeRemaining -= lastTickLength;
14308            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14309 //         if(pvInfoList[forwardMostMove-1].time == -1)
14310                  pvInfoList[forwardMostMove-1].time = 
14311                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14312         }
14313         flagged = CheckFlags();
14314     }
14315     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14316     CheckTimeControl();
14317
14318     if (flagged || !appData.clockMode) return;
14319
14320     switch (gameMode) {
14321       case MachinePlaysBlack:
14322       case MachinePlaysWhite:
14323       case BeginningOfGame:
14324         if (pausing) return;
14325         break;
14326
14327       case EditGame:
14328       case PlayFromGameFile:
14329       case IcsExamining:
14330         return;
14331
14332       default:
14333         break;
14334     }
14335
14336     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14337         if(WhiteOnMove(forwardMostMove))
14338              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14339         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14340     }
14341
14342     tickStartTM = now;
14343     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14344       whiteTimeRemaining : blackTimeRemaining);
14345     StartClockTimer(intendedTickLength);
14346 }
14347         
14348
14349 /* Stop both clocks */
14350 void
14351 StopClocks()
14352 {       
14353     long lastTickLength;
14354     TimeMark now;
14355
14356     if (!StopClockTimer()) return;
14357     if (!appData.clockMode) return;
14358
14359     GetTimeMark(&now);
14360
14361     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14362     if (WhiteOnMove(forwardMostMove)) {
14363         if(whiteNPS >= 0) lastTickLength = 0;
14364         whiteTimeRemaining -= lastTickLength;
14365         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14366     } else {
14367         if(blackNPS >= 0) lastTickLength = 0;
14368         blackTimeRemaining -= lastTickLength;
14369         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14370     }
14371     CheckFlags();
14372 }
14373         
14374 /* Start clock of player on move.  Time may have been reset, so
14375    if clock is already running, stop and restart it. */
14376 void
14377 StartClocks()
14378 {
14379     (void) StopClockTimer(); /* in case it was running already */
14380     DisplayBothClocks();
14381     if (CheckFlags()) return;
14382
14383     if (!appData.clockMode) return;
14384     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14385
14386     GetTimeMark(&tickStartTM);
14387     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14388       whiteTimeRemaining : blackTimeRemaining);
14389
14390    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14391     whiteNPS = blackNPS = -1; 
14392     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14393        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14394         whiteNPS = first.nps;
14395     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14396        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14397         blackNPS = first.nps;
14398     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14399         whiteNPS = second.nps;
14400     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14401         blackNPS = second.nps;
14402     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14403
14404     StartClockTimer(intendedTickLength);
14405 }
14406
14407 char *
14408 TimeString(ms)
14409      long ms;
14410 {
14411     long second, minute, hour, day;
14412     char *sign = "";
14413     static char buf[32];
14414     
14415     if (ms > 0 && ms <= 9900) {
14416       /* convert milliseconds to tenths, rounding up */
14417       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14418
14419       sprintf(buf, " %03.1f ", tenths/10.0);
14420       return buf;
14421     }
14422
14423     /* convert milliseconds to seconds, rounding up */
14424     /* use floating point to avoid strangeness of integer division
14425        with negative dividends on many machines */
14426     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14427
14428     if (second < 0) {
14429         sign = "-";
14430         second = -second;
14431     }
14432     
14433     day = second / (60 * 60 * 24);
14434     second = second % (60 * 60 * 24);
14435     hour = second / (60 * 60);
14436     second = second % (60 * 60);
14437     minute = second / 60;
14438     second = second % 60;
14439     
14440     if (day > 0)
14441       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14442               sign, day, hour, minute, second);
14443     else if (hour > 0)
14444       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14445     else
14446       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14447     
14448     return buf;
14449 }
14450
14451
14452 /*
14453  * This is necessary because some C libraries aren't ANSI C compliant yet.
14454  */
14455 char *
14456 StrStr(string, match)
14457      char *string, *match;
14458 {
14459     int i, length;
14460     
14461     length = strlen(match);
14462     
14463     for (i = strlen(string) - length; i >= 0; i--, string++)
14464       if (!strncmp(match, string, length))
14465         return string;
14466     
14467     return NULL;
14468 }
14469
14470 char *
14471 StrCaseStr(string, match)
14472      char *string, *match;
14473 {
14474     int i, j, length;
14475     
14476     length = strlen(match);
14477     
14478     for (i = strlen(string) - length; i >= 0; i--, string++) {
14479         for (j = 0; j < length; j++) {
14480             if (ToLower(match[j]) != ToLower(string[j]))
14481               break;
14482         }
14483         if (j == length) return string;
14484     }
14485
14486     return NULL;
14487 }
14488
14489 #ifndef _amigados
14490 int
14491 StrCaseCmp(s1, s2)
14492      char *s1, *s2;
14493 {
14494     char c1, c2;
14495     
14496     for (;;) {
14497         c1 = ToLower(*s1++);
14498         c2 = ToLower(*s2++);
14499         if (c1 > c2) return 1;
14500         if (c1 < c2) return -1;
14501         if (c1 == NULLCHAR) return 0;
14502     }
14503 }
14504
14505
14506 int
14507 ToLower(c)
14508      int c;
14509 {
14510     return isupper(c) ? tolower(c) : c;
14511 }
14512
14513
14514 int
14515 ToUpper(c)
14516      int c;
14517 {
14518     return islower(c) ? toupper(c) : c;
14519 }
14520 #endif /* !_amigados    */
14521
14522 char *
14523 StrSave(s)
14524      char *s;
14525 {
14526     char *ret;
14527
14528     if ((ret = (char *) malloc(strlen(s) + 1))) {
14529         strcpy(ret, s);
14530     }
14531     return ret;
14532 }
14533
14534 char *
14535 StrSavePtr(s, savePtr)
14536      char *s, **savePtr;
14537 {
14538     if (*savePtr) {
14539         free(*savePtr);
14540     }
14541     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14542         strcpy(*savePtr, s);
14543     }
14544     return(*savePtr);
14545 }
14546
14547 char *
14548 PGNDate()
14549 {
14550     time_t clock;
14551     struct tm *tm;
14552     char buf[MSG_SIZ];
14553
14554     clock = time((time_t *)NULL);
14555     tm = localtime(&clock);
14556     sprintf(buf, "%04d.%02d.%02d",
14557             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14558     return StrSave(buf);
14559 }
14560
14561
14562 char *
14563 PositionToFEN(move, overrideCastling)
14564      int move;
14565      char *overrideCastling;
14566 {
14567     int i, j, fromX, fromY, toX, toY;
14568     int whiteToPlay;
14569     char buf[128];
14570     char *p, *q;
14571     int emptycount;
14572     ChessSquare piece;
14573
14574     whiteToPlay = (gameMode == EditPosition) ?
14575       !blackPlaysFirst : (move % 2 == 0);
14576     p = buf;
14577
14578     /* Piece placement data */
14579     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14580         emptycount = 0;
14581         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14582             if (boards[move][i][j] == EmptySquare) {
14583                 emptycount++;
14584             } else { ChessSquare piece = boards[move][i][j];
14585                 if (emptycount > 0) {
14586                     if(emptycount<10) /* [HGM] can be >= 10 */
14587                         *p++ = '0' + emptycount;
14588                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14589                     emptycount = 0;
14590                 }
14591                 if(PieceToChar(piece) == '+') {
14592                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14593                     *p++ = '+';
14594                     piece = (ChessSquare)(DEMOTED piece);
14595                 } 
14596                 *p++ = PieceToChar(piece);
14597                 if(p[-1] == '~') {
14598                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14599                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14600                     *p++ = '~';
14601                 }
14602             }
14603         }
14604         if (emptycount > 0) {
14605             if(emptycount<10) /* [HGM] can be >= 10 */
14606                 *p++ = '0' + emptycount;
14607             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14608             emptycount = 0;
14609         }
14610         *p++ = '/';
14611     }
14612     *(p - 1) = ' ';
14613
14614     /* [HGM] print Crazyhouse or Shogi holdings */
14615     if( gameInfo.holdingsWidth ) {
14616         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14617         q = p;
14618         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14619             piece = boards[move][i][BOARD_WIDTH-1];
14620             if( piece != EmptySquare )
14621               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14622                   *p++ = PieceToChar(piece);
14623         }
14624         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14625             piece = boards[move][BOARD_HEIGHT-i-1][0];
14626             if( piece != EmptySquare )
14627               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14628                   *p++ = PieceToChar(piece);
14629         }
14630
14631         if( q == p ) *p++ = '-';
14632         *p++ = ']';
14633         *p++ = ' ';
14634     }
14635
14636     /* Active color */
14637     *p++ = whiteToPlay ? 'w' : 'b';
14638     *p++ = ' ';
14639
14640   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14641     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14642   } else {
14643   if(nrCastlingRights) {
14644      q = p;
14645      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14646        /* [HGM] write directly from rights */
14647            if(boards[move][CASTLING][2] != NoRights &&
14648               boards[move][CASTLING][0] != NoRights   )
14649                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14650            if(boards[move][CASTLING][2] != NoRights &&
14651               boards[move][CASTLING][1] != NoRights   )
14652                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14653            if(boards[move][CASTLING][5] != NoRights &&
14654               boards[move][CASTLING][3] != NoRights   )
14655                 *p++ = boards[move][CASTLING][3] + AAA;
14656            if(boards[move][CASTLING][5] != NoRights &&
14657               boards[move][CASTLING][4] != NoRights   )
14658                 *p++ = boards[move][CASTLING][4] + AAA;
14659      } else {
14660
14661         /* [HGM] write true castling rights */
14662         if( nrCastlingRights == 6 ) {
14663             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14664                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14665             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14666                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14667             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14668                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14669             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14670                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14671         }
14672      }
14673      if (q == p) *p++ = '-'; /* No castling rights */
14674      *p++ = ' ';
14675   }
14676
14677   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14678      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14679     /* En passant target square */
14680     if (move > backwardMostMove) {
14681         fromX = moveList[move - 1][0] - AAA;
14682         fromY = moveList[move - 1][1] - ONE;
14683         toX = moveList[move - 1][2] - AAA;
14684         toY = moveList[move - 1][3] - ONE;
14685         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14686             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14687             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14688             fromX == toX) {
14689             /* 2-square pawn move just happened */
14690             *p++ = toX + AAA;
14691             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14692         } else {
14693             *p++ = '-';
14694         }
14695     } else if(move == backwardMostMove) {
14696         // [HGM] perhaps we should always do it like this, and forget the above?
14697         if((signed char)boards[move][EP_STATUS] >= 0) {
14698             *p++ = boards[move][EP_STATUS] + AAA;
14699             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14700         } else {
14701             *p++ = '-';
14702         }
14703     } else {
14704         *p++ = '-';
14705     }
14706     *p++ = ' ';
14707   }
14708   }
14709
14710     /* [HGM] find reversible plies */
14711     {   int i = 0, j=move;
14712
14713         if (appData.debugMode) { int k;
14714             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14715             for(k=backwardMostMove; k<=forwardMostMove; k++)
14716                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14717
14718         }
14719
14720         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14721         if( j == backwardMostMove ) i += initialRulePlies;
14722         sprintf(p, "%d ", i);
14723         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14724     }
14725     /* Fullmove number */
14726     sprintf(p, "%d", (move / 2) + 1);
14727     
14728     return StrSave(buf);
14729 }
14730
14731 Boolean
14732 ParseFEN(board, blackPlaysFirst, fen)
14733     Board board;
14734      int *blackPlaysFirst;
14735      char *fen;
14736 {
14737     int i, j;
14738     char *p;
14739     int emptycount;
14740     ChessSquare piece;
14741
14742     p = fen;
14743
14744     /* [HGM] by default clear Crazyhouse holdings, if present */
14745     if(gameInfo.holdingsWidth) {
14746        for(i=0; i<BOARD_HEIGHT; i++) {
14747            board[i][0]             = EmptySquare; /* black holdings */
14748            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14749            board[i][1]             = (ChessSquare) 0; /* black counts */
14750            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14751        }
14752     }
14753
14754     /* Piece placement data */
14755     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14756         j = 0;
14757         for (;;) {
14758             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14759                 if (*p == '/') p++;
14760                 emptycount = gameInfo.boardWidth - j;
14761                 while (emptycount--)
14762                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14763                 break;
14764 #if(BOARD_FILES >= 10)
14765             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14766                 p++; emptycount=10;
14767                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14768                 while (emptycount--)
14769                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14770 #endif
14771             } else if (isdigit(*p)) {
14772                 emptycount = *p++ - '0';
14773                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14774                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14775                 while (emptycount--)
14776                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14777             } else if (*p == '+' || isalpha(*p)) {
14778                 if (j >= gameInfo.boardWidth) return FALSE;
14779                 if(*p=='+') {
14780                     piece = CharToPiece(*++p);
14781                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14782                     piece = (ChessSquare) (PROMOTED piece ); p++;
14783                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14784                 } else piece = CharToPiece(*p++);
14785
14786                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14787                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14788                     piece = (ChessSquare) (PROMOTED piece);
14789                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14790                     p++;
14791                 }
14792                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14793             } else {
14794                 return FALSE;
14795             }
14796         }
14797     }
14798     while (*p == '/' || *p == ' ') p++;
14799
14800     /* [HGM] look for Crazyhouse holdings here */
14801     while(*p==' ') p++;
14802     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14803         if(*p == '[') p++;
14804         if(*p == '-' ) *p++; /* empty holdings */ else {
14805             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14806             /* if we would allow FEN reading to set board size, we would   */
14807             /* have to add holdings and shift the board read so far here   */
14808             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14809                 *p++;
14810                 if((int) piece >= (int) BlackPawn ) {
14811                     i = (int)piece - (int)BlackPawn;
14812                     i = PieceToNumber((ChessSquare)i);
14813                     if( i >= gameInfo.holdingsSize ) return FALSE;
14814                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14815                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14816                 } else {
14817                     i = (int)piece - (int)WhitePawn;
14818                     i = PieceToNumber((ChessSquare)i);
14819                     if( i >= gameInfo.holdingsSize ) return FALSE;
14820                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14821                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14822                 }
14823             }
14824         }
14825         if(*p == ']') *p++;
14826     }
14827
14828     while(*p == ' ') p++;
14829
14830     /* Active color */
14831     switch (*p++) {
14832       case 'w':
14833         *blackPlaysFirst = FALSE;
14834         break;
14835       case 'b': 
14836         *blackPlaysFirst = TRUE;
14837         break;
14838       default:
14839         return FALSE;
14840     }
14841
14842     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14843     /* return the extra info in global variiables             */
14844
14845     /* set defaults in case FEN is incomplete */
14846     board[EP_STATUS] = EP_UNKNOWN;
14847     for(i=0; i<nrCastlingRights; i++ ) {
14848         board[CASTLING][i] =
14849             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14850     }   /* assume possible unless obviously impossible */
14851     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14852     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14853     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14854                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14855     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14856     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14857     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14858                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14859     FENrulePlies = 0;
14860
14861     while(*p==' ') p++;
14862     if(nrCastlingRights) {
14863       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14864           /* castling indicator present, so default becomes no castlings */
14865           for(i=0; i<nrCastlingRights; i++ ) {
14866                  board[CASTLING][i] = NoRights;
14867           }
14868       }
14869       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14870              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14871              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14872              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14873         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14874
14875         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14876             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14877             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14878         }
14879         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14880             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14881         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14882                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14883         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14884                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14885         switch(c) {
14886           case'K':
14887               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14888               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14889               board[CASTLING][2] = whiteKingFile;
14890               break;
14891           case'Q':
14892               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14893               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14894               board[CASTLING][2] = whiteKingFile;
14895               break;
14896           case'k':
14897               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14898               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14899               board[CASTLING][5] = blackKingFile;
14900               break;
14901           case'q':
14902               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14903               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14904               board[CASTLING][5] = blackKingFile;
14905           case '-':
14906               break;
14907           default: /* FRC castlings */
14908               if(c >= 'a') { /* black rights */
14909                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14910                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14911                   if(i == BOARD_RGHT) break;
14912                   board[CASTLING][5] = i;
14913                   c -= AAA;
14914                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14915                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14916                   if(c > i)
14917                       board[CASTLING][3] = c;
14918                   else
14919                       board[CASTLING][4] = c;
14920               } else { /* white rights */
14921                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14922                     if(board[0][i] == WhiteKing) break;
14923                   if(i == BOARD_RGHT) break;
14924                   board[CASTLING][2] = i;
14925                   c -= AAA - 'a' + 'A';
14926                   if(board[0][c] >= WhiteKing) break;
14927                   if(c > i)
14928                       board[CASTLING][0] = c;
14929                   else
14930                       board[CASTLING][1] = c;
14931               }
14932         }
14933       }
14934       for(i=0; i<nrCastlingRights; i++)
14935         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14936     if (appData.debugMode) {
14937         fprintf(debugFP, "FEN castling rights:");
14938         for(i=0; i<nrCastlingRights; i++)
14939         fprintf(debugFP, " %d", board[CASTLING][i]);
14940         fprintf(debugFP, "\n");
14941     }
14942
14943       while(*p==' ') p++;
14944     }
14945
14946     /* read e.p. field in games that know e.p. capture */
14947     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14948        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14949       if(*p=='-') {
14950         p++; board[EP_STATUS] = EP_NONE;
14951       } else {
14952          char c = *p++ - AAA;
14953
14954          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14955          if(*p >= '0' && *p <='9') *p++;
14956          board[EP_STATUS] = c;
14957       }
14958     }
14959
14960
14961     if(sscanf(p, "%d", &i) == 1) {
14962         FENrulePlies = i; /* 50-move ply counter */
14963         /* (The move number is still ignored)    */
14964     }
14965
14966     return TRUE;
14967 }
14968       
14969 void
14970 EditPositionPasteFEN(char *fen)
14971 {
14972   if (fen != NULL) {
14973     Board initial_position;
14974
14975     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14976       DisplayError(_("Bad FEN position in clipboard"), 0);
14977       return ;
14978     } else {
14979       int savedBlackPlaysFirst = blackPlaysFirst;
14980       EditPositionEvent();
14981       blackPlaysFirst = savedBlackPlaysFirst;
14982       CopyBoard(boards[0], initial_position);
14983       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14984       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14985       DisplayBothClocks();
14986       DrawPosition(FALSE, boards[currentMove]);
14987     }
14988   }
14989 }
14990
14991 static char cseq[12] = "\\   ";
14992
14993 Boolean set_cont_sequence(char *new_seq)
14994 {
14995     int len;
14996     Boolean ret;
14997
14998     // handle bad attempts to set the sequence
14999         if (!new_seq)
15000                 return 0; // acceptable error - no debug
15001
15002     len = strlen(new_seq);
15003     ret = (len > 0) && (len < sizeof(cseq));
15004     if (ret)
15005         strcpy(cseq, new_seq);
15006     else if (appData.debugMode)
15007         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15008     return ret;
15009 }
15010
15011 /*
15012     reformat a source message so words don't cross the width boundary.  internal
15013     newlines are not removed.  returns the wrapped size (no null character unless
15014     included in source message).  If dest is NULL, only calculate the size required
15015     for the dest buffer.  lp argument indicats line position upon entry, and it's
15016     passed back upon exit.
15017 */
15018 int wrap(char *dest, char *src, int count, int width, int *lp)
15019 {
15020     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15021
15022     cseq_len = strlen(cseq);
15023     old_line = line = *lp;
15024     ansi = len = clen = 0;
15025
15026     for (i=0; i < count; i++)
15027     {
15028         if (src[i] == '\033')
15029             ansi = 1;
15030
15031         // if we hit the width, back up
15032         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15033         {
15034             // store i & len in case the word is too long
15035             old_i = i, old_len = len;
15036
15037             // find the end of the last word
15038             while (i && src[i] != ' ' && src[i] != '\n')
15039             {
15040                 i--;
15041                 len--;
15042             }
15043
15044             // word too long?  restore i & len before splitting it
15045             if ((old_i-i+clen) >= width)
15046             {
15047                 i = old_i;
15048                 len = old_len;
15049             }
15050
15051             // extra space?
15052             if (i && src[i-1] == ' ')
15053                 len--;
15054
15055             if (src[i] != ' ' && src[i] != '\n')
15056             {
15057                 i--;
15058                 if (len)
15059                     len--;
15060             }
15061
15062             // now append the newline and continuation sequence
15063             if (dest)
15064                 dest[len] = '\n';
15065             len++;
15066             if (dest)
15067                 strncpy(dest+len, cseq, cseq_len);
15068             len += cseq_len;
15069             line = cseq_len;
15070             clen = cseq_len;
15071             continue;
15072         }
15073
15074         if (dest)
15075             dest[len] = src[i];
15076         len++;
15077         if (!ansi)
15078             line++;
15079         if (src[i] == '\n')
15080             line = 0;
15081         if (src[i] == 'm')
15082             ansi = 0;
15083     }
15084     if (dest && appData.debugMode)
15085     {
15086         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15087             count, width, line, len, *lp);
15088         show_bytes(debugFP, src, count);
15089         fprintf(debugFP, "\ndest: ");
15090         show_bytes(debugFP, dest, len);
15091         fprintf(debugFP, "\n");
15092     }
15093     *lp = dest ? line : old_line;
15094
15095     return len;
15096 }
15097
15098 // [HGM] vari: routines for shelving variations
15099
15100 void 
15101 PushTail(int firstMove, int lastMove)
15102 {
15103         int i, j, nrMoves = lastMove - firstMove;
15104
15105         if(appData.icsActive) { // only in local mode
15106                 forwardMostMove = currentMove; // mimic old ICS behavior
15107                 return;
15108         }
15109         if(storedGames >= MAX_VARIATIONS-1) return;
15110
15111         // push current tail of game on stack
15112         savedResult[storedGames] = gameInfo.result;
15113         savedDetails[storedGames] = gameInfo.resultDetails;
15114         gameInfo.resultDetails = NULL;
15115         savedFirst[storedGames] = firstMove;
15116         savedLast [storedGames] = lastMove;
15117         savedFramePtr[storedGames] = framePtr;
15118         framePtr -= nrMoves; // reserve space for the boards
15119         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15120             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15121             for(j=0; j<MOVE_LEN; j++)
15122                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15123             for(j=0; j<2*MOVE_LEN; j++)
15124                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15125             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15126             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15127             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15128             pvInfoList[firstMove+i-1].depth = 0;
15129             commentList[framePtr+i] = commentList[firstMove+i];
15130             commentList[firstMove+i] = NULL;
15131         }
15132
15133         storedGames++;
15134         forwardMostMove = firstMove; // truncate game so we can start variation
15135         if(storedGames == 1) GreyRevert(FALSE);
15136 }
15137
15138 Boolean
15139 PopTail(Boolean annotate)
15140 {
15141         int i, j, nrMoves;
15142         char buf[8000], moveBuf[20];
15143
15144         if(appData.icsActive) return FALSE; // only in local mode
15145         if(!storedGames) return FALSE; // sanity
15146         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15147
15148         storedGames--;
15149         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15150         nrMoves = savedLast[storedGames] - currentMove;
15151         if(annotate) {
15152                 int cnt = 10;
15153                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15154                 else strcpy(buf, "(");
15155                 for(i=currentMove; i<forwardMostMove; i++) {
15156                         if(WhiteOnMove(i))
15157                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15158                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15159                         strcat(buf, moveBuf);
15160                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15161                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15162                 }
15163                 strcat(buf, ")");
15164         }
15165         for(i=1; i<=nrMoves; i++) { // copy last variation back
15166             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15167             for(j=0; j<MOVE_LEN; j++)
15168                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15169             for(j=0; j<2*MOVE_LEN; j++)
15170                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15171             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15172             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15173             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15174             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15175             commentList[currentMove+i] = commentList[framePtr+i];
15176             commentList[framePtr+i] = NULL;
15177         }
15178         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15179         framePtr = savedFramePtr[storedGames];
15180         gameInfo.result = savedResult[storedGames];
15181         if(gameInfo.resultDetails != NULL) {
15182             free(gameInfo.resultDetails);
15183       }
15184         gameInfo.resultDetails = savedDetails[storedGames];
15185         forwardMostMove = currentMove + nrMoves;
15186         if(storedGames == 0) GreyRevert(TRUE);
15187         return TRUE;
15188 }
15189
15190 void 
15191 CleanupTail()
15192 {       // remove all shelved variations
15193         int i;
15194         for(i=0; i<storedGames; i++) {
15195             if(savedDetails[i])
15196                 free(savedDetails[i]);
15197             savedDetails[i] = NULL;
15198         }
15199         for(i=framePtr; i<MAX_MOVES; i++) {
15200                 if(commentList[i]) free(commentList[i]);
15201                 commentList[i] = NULL;
15202         }
15203         framePtr = MAX_MOVES-1;
15204         storedGames = 0;
15205 }
15206
15207 void
15208 LoadVariation(int index, char *text)
15209 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15210         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15211         int level = 0, move;
15212
15213         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15214         // first find outermost bracketing variation
15215         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15216             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15217                 if(*p == '{') wait = '}'; else
15218                 if(*p == '[') wait = ']'; else
15219                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15220                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15221             }
15222             if(*p == wait) wait = NULLCHAR; // closing ]} found
15223             p++;
15224         }
15225         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15226         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15227         end[1] = NULLCHAR; // clip off comment beyond variation
15228         ToNrEvent(currentMove-1);
15229         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15230         // kludge: use ParsePV() to append variation to game
15231         move = currentMove;
15232         ParsePV(start, TRUE);
15233         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15234         ClearPremoveHighlights();
15235         CommentPopDown();
15236         ToNrEvent(currentMove+1);
15237 }
15238