Refactoring of adjudication code
[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 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 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 int endPV = -1;
241 static int exiting = 0; /* [HGM] moved to top */
242 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
243 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
244 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
245 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
246 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
247 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
248 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
249 int opponentKibitzes;
250 int lastSavedGame; /* [HGM] save: ID of game */
251 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
252 extern int chatCount;
253 int chattingPartner;
254 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
255
256 /* States for ics_getting_history */
257 #define H_FALSE 0
258 #define H_REQUESTED 1
259 #define H_GOT_REQ_HEADER 2
260 #define H_GOT_UNREQ_HEADER 3
261 #define H_GETTING_MOVES 4
262 #define H_GOT_UNWANTED_HEADER 5
263
264 /* whosays values for GameEnds */
265 #define GE_ICS 0
266 #define GE_ENGINE 1
267 #define GE_PLAYER 2
268 #define GE_FILE 3
269 #define GE_XBOARD 4
270 #define GE_ENGINE1 5
271 #define GE_ENGINE2 6
272
273 /* Maximum number of games in a cmail message */
274 #define CMAIL_MAX_GAMES 20
275
276 /* Different types of move when calling RegisterMove */
277 #define CMAIL_MOVE   0
278 #define CMAIL_RESIGN 1
279 #define CMAIL_DRAW   2
280 #define CMAIL_ACCEPT 3
281
282 /* Different types of result to remember for each game */
283 #define CMAIL_NOT_RESULT 0
284 #define CMAIL_OLD_RESULT 1
285 #define CMAIL_NEW_RESULT 2
286
287 /* Telnet protocol constants */
288 #define TN_WILL 0373
289 #define TN_WONT 0374
290 #define TN_DO   0375
291 #define TN_DONT 0376
292 #define TN_IAC  0377
293 #define TN_ECHO 0001
294 #define TN_SGA  0003
295 #define TN_PORT 23
296
297 /* [AS] */
298 static char * safeStrCpy( char * dst, const char * src, size_t count )
299 {
300     assert( dst != NULL );
301     assert( src != NULL );
302     assert( count > 0 );
303
304     strncpy( dst, src, count );
305     dst[ count-1 ] = '\0';
306     return dst;
307 }
308
309 /* Some compiler can't cast u64 to double
310  * This function do the job for us:
311
312  * We use the highest bit for cast, this only
313  * works if the highest bit is not
314  * in use (This should not happen)
315  *
316  * We used this for all compiler
317  */
318 double
319 u64ToDouble(u64 value)
320 {
321   double r;
322   u64 tmp = value & u64Const(0x7fffffffffffffff);
323   r = (double)(s64)tmp;
324   if (value & u64Const(0x8000000000000000))
325        r +=  9.2233720368547758080e18; /* 2^63 */
326  return r;
327 }
328
329 /* Fake up flags for now, as we aren't keeping track of castling
330    availability yet. [HGM] Change of logic: the flag now only
331    indicates the type of castlings allowed by the rule of the game.
332    The actual rights themselves are maintained in the array
333    castlingRights, as part of the game history, and are not probed
334    by this function.
335  */
336 int
337 PosFlags(index)
338 {
339   int flags = F_ALL_CASTLE_OK;
340   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
341   switch (gameInfo.variant) {
342   case VariantSuicide:
343     flags &= ~F_ALL_CASTLE_OK;
344   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
345     flags |= F_IGNORE_CHECK;
346   case VariantLosers:
347     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348     break;
349   case VariantAtomic:
350     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
351     break;
352   case VariantKriegspiel:
353     flags |= F_KRIEGSPIEL_CAPTURE;
354     break;
355   case VariantCapaRandom: 
356   case VariantFischeRandom:
357     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
358   case VariantNoCastle:
359   case VariantShatranj:
360   case VariantCourier:
361   case VariantMakruk:
362     flags &= ~F_ALL_CASTLE_OK;
363     break;
364   default:
365     break;
366   }
367   return flags;
368 }
369
370 FILE *gameFileFP, *debugFP;
371
372 /* 
373     [AS] Note: sometimes, the sscanf() function is used to parse the input
374     into a fixed-size buffer. Because of this, we must be prepared to
375     receive strings as long as the size of the input buffer, which is currently
376     set to 4K for Windows and 8K for the rest.
377     So, we must either allocate sufficiently large buffers here, or
378     reduce the size of the input buffer in the input reading part.
379 */
380
381 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
382 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
383 char thinkOutput1[MSG_SIZ*10];
384
385 ChessProgramState first, second;
386
387 /* premove variables */
388 int premoveToX = 0;
389 int premoveToY = 0;
390 int premoveFromX = 0;
391 int premoveFromY = 0;
392 int premovePromoChar = 0;
393 int gotPremove = 0;
394 Boolean alarmSounded;
395 /* end premove variables */
396
397 char *ics_prefix = "$";
398 int ics_type = ICS_GENERIC;
399
400 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
401 int pauseExamForwardMostMove = 0;
402 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
403 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
404 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
405 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
406 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
407 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
408 int whiteFlag = FALSE, blackFlag = FALSE;
409 int userOfferedDraw = FALSE;
410 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
411 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
412 int cmailMoveType[CMAIL_MAX_GAMES];
413 long ics_clock_paused = 0;
414 ProcRef icsPR = NoProc, cmailPR = NoProc;
415 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
416 GameMode gameMode = BeginningOfGame;
417 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
418 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
419 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
420 int hiddenThinkOutputState = 0; /* [AS] */
421 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
422 int adjudicateLossPlies = 6;
423 char white_holding[64], black_holding[64];
424 TimeMark lastNodeCountTime;
425 long lastNodeCount=0;
426 int have_sent_ICS_logon = 0;
427 int movesPerSession;
428 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
429 long timeControl_2; /* [AS] Allow separate time controls */
430 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
431 long timeRemaining[2][MAX_MOVES];
432 int matchGame = 0;
433 TimeMark programStartTime;
434 char ics_handle[MSG_SIZ];
435 int have_set_title = 0;
436
437 /* animateTraining preserves the state of appData.animate
438  * when Training mode is activated. This allows the
439  * response to be animated when appData.animate == TRUE and
440  * appData.animateDragging == TRUE.
441  */
442 Boolean animateTraining;
443
444 GameInfo gameInfo;
445
446 AppData appData;
447
448 Board boards[MAX_MOVES];
449 /* [HGM] Following 7 needed for accurate legality tests: */
450 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
451 signed char  initialRights[BOARD_FILES];
452 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
453 int   initialRulePlies, FENrulePlies;
454 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int loadFlag = 0; 
456 int shuffleOpenings;
457 int mute; // mute all sounds
458
459 // [HGM] vari: next 12 to save and restore variations
460 #define MAX_VARIATIONS 10
461 int framePtr = MAX_MOVES-1; // points to free stack entry
462 int storedGames = 0;
463 int savedFirst[MAX_VARIATIONS];
464 int savedLast[MAX_VARIATIONS];
465 int savedFramePtr[MAX_VARIATIONS];
466 char *savedDetails[MAX_VARIATIONS];
467 ChessMove savedResult[MAX_VARIATIONS];
468
469 void PushTail P((int firstMove, int lastMove));
470 Boolean PopTail P((Boolean annotate));
471 void CleanupTail P((void));
472
473 ChessSquare  FIDEArray[2][BOARD_FILES] = {
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477         BlackKing, BlackBishop, BlackKnight, BlackRook }
478 };
479
480 ChessSquare twoKingsArray[2][BOARD_FILES] = {
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484         BlackKing, BlackKing, BlackKnight, BlackRook }
485 };
486
487 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
488     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490     { BlackRook, BlackMan, BlackBishop, BlackQueen,
491         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 };
493
494 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
495     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
496         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
498         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
499 };
500
501 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
510         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackMan, BlackFerz,
512         BlackKing, BlackMan, BlackKnight, BlackRook }
513 };
514
515
516 #if (BOARD_FILES>=10)
517 ChessSquare ShogiArray[2][BOARD_FILES] = {
518     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
519         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
520     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
521         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
522 };
523
524 ChessSquare XiangqiArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
526         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
528         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
529 };
530
531 ChessSquare CapablancaArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
533         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
534     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
535         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
536 };
537
538 ChessSquare GreatArray[2][BOARD_FILES] = {
539     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
540         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
541     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
542         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
543 };
544
545 ChessSquare JanusArray[2][BOARD_FILES] = {
546     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
547         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
548     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
549         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
550 };
551
552 #ifdef GOTHIC
553 ChessSquare GothicArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
555         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
557         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
558 };
559 #else // !GOTHIC
560 #define GothicArray CapablancaArray
561 #endif // !GOTHIC
562
563 #ifdef FALCON
564 ChessSquare FalconArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
566         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
568         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
569 };
570 #else // !FALCON
571 #define FalconArray CapablancaArray
572 #endif // !FALCON
573
574 #else // !(BOARD_FILES>=10)
575 #define XiangqiPosition FIDEArray
576 #define CapablancaArray FIDEArray
577 #define GothicArray FIDEArray
578 #define GreatArray FIDEArray
579 #endif // !(BOARD_FILES>=10)
580
581 #if (BOARD_FILES>=12)
582 ChessSquare CourierArray[2][BOARD_FILES] = {
583     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
586         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
587 };
588 #else // !(BOARD_FILES>=12)
589 #define CourierArray CapablancaArray
590 #endif // !(BOARD_FILES>=12)
591
592
593 Board initialPosition;
594
595
596 /* Convert str to a rating. Checks for special cases of "----",
597
598    "++++", etc. Also strips ()'s */
599 int
600 string_to_rating(str)
601   char *str;
602 {
603   while(*str && !isdigit(*str)) ++str;
604   if (!*str)
605     return 0;   /* One of the special "no rating" cases */
606   else
607     return atoi(str);
608 }
609
610 void
611 ClearProgramStats()
612 {
613     /* Init programStats */
614     programStats.movelist[0] = 0;
615     programStats.depth = 0;
616     programStats.nr_moves = 0;
617     programStats.moves_left = 0;
618     programStats.nodes = 0;
619     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
620     programStats.score = 0;
621     programStats.got_only_move = 0;
622     programStats.got_fail = 0;
623     programStats.line_is_book = 0;
624 }
625
626 void
627 InitBackEnd1()
628 {
629     int matched, min, sec;
630
631     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
632
633     GetTimeMark(&programStartTime);
634     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
635
636     ClearProgramStats();
637     programStats.ok_to_send = 1;
638     programStats.seen_stat = 0;
639
640     /*
641      * Initialize game list
642      */
643     ListNew(&gameList);
644
645
646     /*
647      * Internet chess server status
648      */
649     if (appData.icsActive) {
650         appData.matchMode = FALSE;
651         appData.matchGames = 0;
652 #if ZIPPY       
653         appData.noChessProgram = !appData.zippyPlay;
654 #else
655         appData.zippyPlay = FALSE;
656         appData.zippyTalk = FALSE;
657         appData.noChessProgram = TRUE;
658 #endif
659         if (*appData.icsHelper != NULLCHAR) {
660             appData.useTelnet = TRUE;
661             appData.telnetProgram = appData.icsHelper;
662         }
663     } else {
664         appData.zippyTalk = appData.zippyPlay = FALSE;
665     }
666
667     /* [AS] Initialize pv info list [HGM] and game state */
668     {
669         int i, j;
670
671         for( i=0; i<=framePtr; i++ ) {
672             pvInfoList[i].depth = -1;
673             boards[i][EP_STATUS] = EP_NONE;
674             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
675         }
676     }
677
678     /*
679      * Parse timeControl resource
680      */
681     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
682                           appData.movesPerSession)) {
683         char buf[MSG_SIZ];
684         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
685         DisplayFatalError(buf, 0, 2);
686     }
687
688     /*
689      * Parse searchTime resource
690      */
691     if (*appData.searchTime != NULLCHAR) {
692         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
693         if (matched == 1) {
694             searchTime = min * 60;
695         } else if (matched == 2) {
696             searchTime = min * 60 + sec;
697         } else {
698             char buf[MSG_SIZ];
699             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
700             DisplayFatalError(buf, 0, 2);
701         }
702     }
703
704     /* [AS] Adjudication threshold */
705     adjudicateLossThreshold = appData.adjudicateLossThreshold;
706     
707     first.which = "first";
708     second.which = "second";
709     first.maybeThinking = second.maybeThinking = FALSE;
710     first.pr = second.pr = NoProc;
711     first.isr = second.isr = NULL;
712     first.sendTime = second.sendTime = 2;
713     first.sendDrawOffers = 1;
714     if (appData.firstPlaysBlack) {
715         first.twoMachinesColor = "black\n";
716         second.twoMachinesColor = "white\n";
717     } else {
718         first.twoMachinesColor = "white\n";
719         second.twoMachinesColor = "black\n";
720     }
721     first.program = appData.firstChessProgram;
722     second.program = appData.secondChessProgram;
723     first.host = appData.firstHost;
724     second.host = appData.secondHost;
725     first.dir = appData.firstDirectory;
726     second.dir = appData.secondDirectory;
727     first.other = &second;
728     second.other = &first;
729     first.initString = appData.initString;
730     second.initString = appData.secondInitString;
731     first.computerString = appData.firstComputerString;
732     second.computerString = appData.secondComputerString;
733     first.useSigint = second.useSigint = TRUE;
734     first.useSigterm = second.useSigterm = TRUE;
735     first.reuse = appData.reuseFirst;
736     second.reuse = appData.reuseSecond;
737     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
738     second.nps = appData.secondNPS;
739     first.useSetboard = second.useSetboard = FALSE;
740     first.useSAN = second.useSAN = FALSE;
741     first.usePing = second.usePing = FALSE;
742     first.lastPing = second.lastPing = 0;
743     first.lastPong = second.lastPong = 0;
744     first.usePlayother = second.usePlayother = FALSE;
745     first.useColors = second.useColors = TRUE;
746     first.useUsermove = second.useUsermove = FALSE;
747     first.sendICS = second.sendICS = FALSE;
748     first.sendName = second.sendName = appData.icsActive;
749     first.sdKludge = second.sdKludge = FALSE;
750     first.stKludge = second.stKludge = FALSE;
751     TidyProgramName(first.program, first.host, first.tidy);
752     TidyProgramName(second.program, second.host, second.tidy);
753     first.matchWins = second.matchWins = 0;
754     strcpy(first.variants, appData.variant);
755     strcpy(second.variants, appData.variant);
756     first.analysisSupport = second.analysisSupport = 2; /* detect */
757     first.analyzing = second.analyzing = FALSE;
758     first.initDone = second.initDone = FALSE;
759
760     /* New features added by Tord: */
761     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
762     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
763     /* End of new features added by Tord. */
764     first.fenOverride  = appData.fenOverride1;
765     second.fenOverride = appData.fenOverride2;
766
767     /* [HGM] time odds: set factor for each machine */
768     first.timeOdds  = appData.firstTimeOdds;
769     second.timeOdds = appData.secondTimeOdds;
770     { float norm = 1;
771         if(appData.timeOddsMode) {
772             norm = first.timeOdds;
773             if(norm > second.timeOdds) norm = second.timeOdds;
774         }
775         first.timeOdds /= norm;
776         second.timeOdds /= norm;
777     }
778
779     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
780     first.accumulateTC = appData.firstAccumulateTC;
781     second.accumulateTC = appData.secondAccumulateTC;
782     first.maxNrOfSessions = second.maxNrOfSessions = 1;
783
784     /* [HGM] debug */
785     first.debug = second.debug = FALSE;
786     first.supportsNPS = second.supportsNPS = UNKNOWN;
787
788     /* [HGM] options */
789     first.optionSettings  = appData.firstOptions;
790     second.optionSettings = appData.secondOptions;
791
792     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
793     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
794     first.isUCI = appData.firstIsUCI; /* [AS] */
795     second.isUCI = appData.secondIsUCI; /* [AS] */
796     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
797     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
798
799     if (appData.firstProtocolVersion > PROTOVER ||
800         appData.firstProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.firstProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       first.protocolVersion = appData.firstProtocolVersion;
807     }
808
809     if (appData.secondProtocolVersion > PROTOVER ||
810         appData.secondProtocolVersion < 1) {
811       char buf[MSG_SIZ];
812       sprintf(buf, _("protocol version %d not supported"),
813               appData.secondProtocolVersion);
814       DisplayFatalError(buf, 0, 2);
815     } else {
816       second.protocolVersion = appData.secondProtocolVersion;
817     }
818
819     if (appData.icsActive) {
820         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
821 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
822     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
823         appData.clockMode = FALSE;
824         first.sendTime = second.sendTime = 0;
825     }
826     
827 #if ZIPPY
828     /* Override some settings from environment variables, for backward
829        compatibility.  Unfortunately it's not feasible to have the env
830        vars just set defaults, at least in xboard.  Ugh.
831     */
832     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
833       ZippyInit();
834     }
835 #endif
836     
837     if (appData.noChessProgram) {
838         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
839         sprintf(programVersion, "%s", PACKAGE_STRING);
840     } else {
841       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantMakruk:     /* should work except for daw countdown */
902       case VariantBerolina:   /* might work if TestLegality is off */
903       case VariantCapaRandom: /* should work */
904       case VariantJanus:      /* should work */
905       case VariantSuper:      /* experimental */
906       case VariantGreat:      /* experimental, requires legality testing to be off */
907         break;
908       }
909     }
910
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
912     InitEngineUCI( installDir, &second );
913 }
914
915 int NextIntegerFromString( char ** str, long * value )
916 {
917     int result = -1;
918     char * s = *str;
919
920     while( *s == ' ' || *s == '\t' ) {
921         s++;
922     }
923
924     *value = 0;
925
926     if( *s >= '0' && *s <= '9' ) {
927         while( *s >= '0' && *s <= '9' ) {
928             *value = *value * 10 + (*s - '0');
929             s++;
930         }
931
932         result = 0;
933     }
934
935     *str = s;
936
937     return result;
938 }
939
940 int NextTimeControlFromString( char ** str, long * value )
941 {
942     long temp;
943     int result = NextIntegerFromString( str, &temp );
944
945     if( result == 0 ) {
946         *value = temp * 60; /* Minutes */
947         if( **str == ':' ) {
948             (*str)++;
949             result = NextIntegerFromString( str, &temp );
950             *value += temp; /* Seconds */
951         }
952     }
953
954     return result;
955 }
956
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
959     int result = -1; long temp, temp2;
960
961     if(**str != '+') return -1; // old params remain in force!
962     (*str)++;
963     if( NextTimeControlFromString( str, &temp ) ) return -1;
964
965     if(**str != '/') {
966         /* time only: incremental or sudden-death time control */
967         if(**str == '+') { /* increment follows; read it */
968             (*str)++;
969             if(result = NextIntegerFromString( str, &temp2)) return -1;
970             *inc = temp2 * 1000;
971         } else *inc = 0;
972         *moves = 0; *tc = temp * 1000; 
973         return 0;
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
975
976     (*str)++; /* classical time control */
977     result = NextTimeControlFromString( str, &temp2);
978     if(result == 0) {
979         *moves = temp/60;
980         *tc    = temp2 * 1000;
981         *inc   = 0;
982     }
983     return result;
984 }
985
986 int GetTimeQuota(int movenr)
987 {   /* [HGM] get time to add from the multi-session time-control string */
988     int moves=1; /* kludge to force reading of first session */
989     long time, increment;
990     char *s = fullTimeControlString;
991
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993     do {
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996         if(movenr == -1) return time;    /* last move before new session     */
997         if(!moves) return increment;     /* current session is incremental   */
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */
999     } while(movenr >= -1);               /* try again for next session       */
1000
1001     return 0; // no new time quota on this move
1002 }
1003
1004 int
1005 ParseTimeControl(tc, ti, mps)
1006      char *tc;
1007      int ti;
1008      int mps;
1009 {
1010   long tc1;
1011   long tc2;
1012   char buf[MSG_SIZ];
1013   
1014   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1015   if(ti > 0) {
1016     if(mps)
1017       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1018     else sprintf(buf, "+%s+%d", tc, ti);
1019   } else {
1020     if(mps)
1021              sprintf(buf, "+%d/%s", mps, tc);
1022     else sprintf(buf, "+%s", tc);
1023   }
1024   fullTimeControlString = StrSave(buf);
1025   
1026   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1027     return FALSE;
1028   }
1029   
1030   if( *tc == '/' ) {
1031     /* Parse second time control */
1032     tc++;
1033     
1034     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1035       return FALSE;
1036     }
1037     
1038     if( tc2 == 0 ) {
1039       return FALSE;
1040     }
1041     
1042     timeControl_2 = tc2 * 1000;
1043   }
1044   else {
1045     timeControl_2 = 0;
1046   }
1047   
1048   if( tc1 == 0 ) {
1049     return FALSE;
1050   }
1051   
1052   timeControl = tc1 * 1000;
1053   
1054   if (ti >= 0) {
1055     timeIncrement = ti * 1000;  /* convert to ms */
1056     movesPerSession = 0;
1057   } else {
1058     timeIncrement = 0;
1059     movesPerSession = mps;
1060   }
1061   return TRUE;
1062 }
1063
1064 void
1065 InitBackEnd2()
1066 {
1067     if (appData.debugMode) {
1068         fprintf(debugFP, "%s\n", programVersion);
1069     }
1070
1071     set_cont_sequence(appData.wrapContSeq);
1072     if (appData.matchGames > 0) {
1073         appData.matchMode = TRUE;
1074     } else if (appData.matchMode) {
1075         appData.matchGames = 1;
1076     }
1077     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1078         appData.matchGames = appData.sameColorGames;
1079     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1080         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1081         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1082     }
1083     Reset(TRUE, FALSE);
1084     if (appData.noChessProgram || first.protocolVersion == 1) {
1085       InitBackEnd3();
1086     } else {
1087       /* kludge: allow timeout for initial "feature" commands */
1088       FreezeUI();
1089       DisplayMessage("", _("Starting chess program"));
1090       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1091     }
1092 }
1093
1094 void
1095 InitBackEnd3 P((void))
1096 {
1097     GameMode initialMode;
1098     char buf[MSG_SIZ];
1099     int err;
1100
1101     InitChessProgram(&first, startedFromSetupPosition);
1102
1103
1104     if (appData.icsActive) {
1105 #ifdef WIN32
1106         /* [DM] Make a console window if needed [HGM] merged ifs */
1107         ConsoleCreate(); 
1108 #endif
1109         err = establish();
1110         if (err != 0) {
1111             if (*appData.icsCommPort != NULLCHAR) {
1112                 sprintf(buf, _("Could not open comm port %s"),  
1113                         appData.icsCommPort);
1114             } else {
1115                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1116                         appData.icsHost, appData.icsPort);
1117             }
1118             DisplayFatalError(buf, err, 1);
1119             return;
1120         }
1121         SetICSMode();
1122         telnetISR =
1123           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1124         fromUserISR =
1125           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1126         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1127             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1128     } else if (appData.noChessProgram) {
1129         SetNCPMode();
1130     } else {
1131         SetGNUMode();
1132     }
1133
1134     if (*appData.cmailGameName != NULLCHAR) {
1135         SetCmailMode();
1136         OpenLoopback(&cmailPR);
1137         cmailISR =
1138           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1139     }
1140     
1141     ThawUI();
1142     DisplayMessage("", "");
1143     if (StrCaseCmp(appData.initialMode, "") == 0) {
1144       initialMode = BeginningOfGame;
1145     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1146       initialMode = TwoMachinesPlay;
1147     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1148       initialMode = AnalyzeFile; 
1149     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1150       initialMode = AnalyzeMode;
1151     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1152       initialMode = MachinePlaysWhite;
1153     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1154       initialMode = MachinePlaysBlack;
1155     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1156       initialMode = EditGame;
1157     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1158       initialMode = EditPosition;
1159     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1160       initialMode = Training;
1161     } else {
1162       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1163       DisplayFatalError(buf, 0, 2);
1164       return;
1165     }
1166
1167     if (appData.matchMode) {
1168         /* Set up machine vs. machine match */
1169         if (appData.noChessProgram) {
1170             DisplayFatalError(_("Can't have a match with no chess programs"),
1171                               0, 2);
1172             return;
1173         }
1174         matchMode = TRUE;
1175         matchGame = 1;
1176         if (*appData.loadGameFile != NULLCHAR) {
1177             int index = appData.loadGameIndex; // [HGM] autoinc
1178             if(index<0) lastIndex = index = 1;
1179             if (!LoadGameFromFile(appData.loadGameFile,
1180                                   index,
1181                                   appData.loadGameFile, FALSE)) {
1182                 DisplayFatalError(_("Bad game file"), 0, 1);
1183                 return;
1184             }
1185         } else if (*appData.loadPositionFile != NULLCHAR) {
1186             int index = appData.loadPositionIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadPositionFromFile(appData.loadPositionFile,
1189                                       index,
1190                                       appData.loadPositionFile)) {
1191                 DisplayFatalError(_("Bad position file"), 0, 1);
1192                 return;
1193             }
1194         }
1195         TwoMachinesEvent();
1196     } else if (*appData.cmailGameName != NULLCHAR) {
1197         /* Set up cmail mode */
1198         ReloadCmailMsgEvent(TRUE);
1199     } else {
1200         /* Set up other modes */
1201         if (initialMode == AnalyzeFile) {
1202           if (*appData.loadGameFile == NULLCHAR) {
1203             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1204             return;
1205           }
1206         }
1207         if (*appData.loadGameFile != NULLCHAR) {
1208             (void) LoadGameFromFile(appData.loadGameFile,
1209                                     appData.loadGameIndex,
1210                                     appData.loadGameFile, TRUE);
1211         } else if (*appData.loadPositionFile != NULLCHAR) {
1212             (void) LoadPositionFromFile(appData.loadPositionFile,
1213                                         appData.loadPositionIndex,
1214                                         appData.loadPositionFile);
1215             /* [HGM] try to make self-starting even after FEN load */
1216             /* to allow automatic setup of fairy variants with wtm */
1217             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1218                 gameMode = BeginningOfGame;
1219                 setboardSpoiledMachineBlack = 1;
1220             }
1221             /* [HGM] loadPos: make that every new game uses the setup */
1222             /* from file as long as we do not switch variant          */
1223             if(!blackPlaysFirst) {
1224                 startedFromPositionFile = TRUE;
1225                 CopyBoard(filePosition, boards[0]);
1226             }
1227         }
1228         if (initialMode == AnalyzeMode) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1235             return;
1236           }
1237           AnalyzeModeEvent();
1238         } else if (initialMode == AnalyzeFile) {
1239           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1240           ShowThinkingEvent();
1241           AnalyzeFileEvent();
1242           AnalysisPeriodicEvent(1);
1243         } else if (initialMode == MachinePlaysWhite) {
1244           if (appData.noChessProgram) {
1245             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1246                               0, 2);
1247             return;
1248           }
1249           if (appData.icsActive) {
1250             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1251                               0, 2);
1252             return;
1253           }
1254           MachineWhiteEvent();
1255         } else if (initialMode == MachinePlaysBlack) {
1256           if (appData.noChessProgram) {
1257             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1258                               0, 2);
1259             return;
1260           }
1261           if (appData.icsActive) {
1262             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1263                               0, 2);
1264             return;
1265           }
1266           MachineBlackEvent();
1267         } else if (initialMode == TwoMachinesPlay) {
1268           if (appData.noChessProgram) {
1269             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1270                               0, 2);
1271             return;
1272           }
1273           if (appData.icsActive) {
1274             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1275                               0, 2);
1276             return;
1277           }
1278           TwoMachinesEvent();
1279         } else if (initialMode == EditGame) {
1280           EditGameEvent();
1281         } else if (initialMode == EditPosition) {
1282           EditPositionEvent();
1283         } else if (initialMode == Training) {
1284           if (*appData.loadGameFile == NULLCHAR) {
1285             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1286             return;
1287           }
1288           TrainingEvent();
1289         }
1290     }
1291 }
1292
1293 /*
1294  * Establish will establish a contact to a remote host.port.
1295  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1296  *  used to talk to the host.
1297  * Returns 0 if okay, error code if not.
1298  */
1299 int
1300 establish()
1301 {
1302     char buf[MSG_SIZ];
1303
1304     if (*appData.icsCommPort != NULLCHAR) {
1305         /* Talk to the host through a serial comm port */
1306         return OpenCommPort(appData.icsCommPort, &icsPR);
1307
1308     } else if (*appData.gateway != NULLCHAR) {
1309         if (*appData.remoteShell == NULLCHAR) {
1310             /* Use the rcmd protocol to run telnet program on a gateway host */
1311             snprintf(buf, sizeof(buf), "%s %s %s",
1312                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1313             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1314
1315         } else {
1316             /* Use the rsh program to run telnet program on a gateway host */
1317             if (*appData.remoteUser == NULLCHAR) {
1318                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1319                         appData.gateway, appData.telnetProgram,
1320                         appData.icsHost, appData.icsPort);
1321             } else {
1322                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1323                         appData.remoteShell, appData.gateway, 
1324                         appData.remoteUser, appData.telnetProgram,
1325                         appData.icsHost, appData.icsPort);
1326             }
1327             return StartChildProcess(buf, "", &icsPR);
1328
1329         }
1330     } else if (appData.useTelnet) {
1331         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1332
1333     } else {
1334         /* TCP socket interface differs somewhat between
1335            Unix and NT; handle details in the front end.
1336            */
1337         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1338     }
1339 }
1340
1341 void
1342 show_bytes(fp, buf, count)
1343      FILE *fp;
1344      char *buf;
1345      int count;
1346 {
1347     while (count--) {
1348         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1349             fprintf(fp, "\\%03o", *buf & 0xff);
1350         } else {
1351             putc(*buf, fp);
1352         }
1353         buf++;
1354     }
1355     fflush(fp);
1356 }
1357
1358 /* Returns an errno value */
1359 int
1360 OutputMaybeTelnet(pr, message, count, outError)
1361      ProcRef pr;
1362      char *message;
1363      int count;
1364      int *outError;
1365 {
1366     char buf[8192], *p, *q, *buflim;
1367     int left, newcount, outcount;
1368
1369     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1370         *appData.gateway != NULLCHAR) {
1371         if (appData.debugMode) {
1372             fprintf(debugFP, ">ICS: ");
1373             show_bytes(debugFP, message, count);
1374             fprintf(debugFP, "\n");
1375         }
1376         return OutputToProcess(pr, message, count, outError);
1377     }
1378
1379     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1380     p = message;
1381     q = buf;
1382     left = count;
1383     newcount = 0;
1384     while (left) {
1385         if (q >= buflim) {
1386             if (appData.debugMode) {
1387                 fprintf(debugFP, ">ICS: ");
1388                 show_bytes(debugFP, buf, newcount);
1389                 fprintf(debugFP, "\n");
1390             }
1391             outcount = OutputToProcess(pr, buf, newcount, outError);
1392             if (outcount < newcount) return -1; /* to be sure */
1393             q = buf;
1394             newcount = 0;
1395         }
1396         if (*p == '\n') {
1397             *q++ = '\r';
1398             newcount++;
1399         } else if (((unsigned char) *p) == TN_IAC) {
1400             *q++ = (char) TN_IAC;
1401             newcount ++;
1402         }
1403         *q++ = *p++;
1404         newcount++;
1405         left--;
1406     }
1407     if (appData.debugMode) {
1408         fprintf(debugFP, ">ICS: ");
1409         show_bytes(debugFP, buf, newcount);
1410         fprintf(debugFP, "\n");
1411     }
1412     outcount = OutputToProcess(pr, buf, newcount, outError);
1413     if (outcount < newcount) return -1; /* to be sure */
1414     return count;
1415 }
1416
1417 void
1418 read_from_player(isr, closure, message, count, error)
1419      InputSourceRef isr;
1420      VOIDSTAR closure;
1421      char *message;
1422      int count;
1423      int error;
1424 {
1425     int outError, outCount;
1426     static int gotEof = 0;
1427
1428     /* Pass data read from player on to ICS */
1429     if (count > 0) {
1430         gotEof = 0;
1431         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1432         if (outCount < count) {
1433             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1434         }
1435     } else if (count < 0) {
1436         RemoveInputSource(isr);
1437         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1438     } else if (gotEof++ > 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1441     }
1442 }
1443
1444 void
1445 KeepAlive()
1446 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1447     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1448     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1449     SendToICS("date\n");
1450     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1451 }
1452
1453 /* added routine for printf style output to ics */
1454 void ics_printf(char *format, ...)
1455 {
1456     char buffer[MSG_SIZ];
1457     va_list args;
1458
1459     va_start(args, format);
1460     vsnprintf(buffer, sizeof(buffer), format, args);
1461     buffer[sizeof(buffer)-1] = '\0';
1462     SendToICS(buffer);
1463     va_end(args);
1464 }
1465
1466 void
1467 SendToICS(s)
1468      char *s;
1469 {
1470     int count, outCount, outError;
1471
1472     if (icsPR == NULL) return;
1473
1474     count = strlen(s);
1475     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1476     if (outCount < count) {
1477         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478     }
1479 }
1480
1481 /* This is used for sending logon scripts to the ICS. Sending
1482    without a delay causes problems when using timestamp on ICC
1483    (at least on my machine). */
1484 void
1485 SendToICSDelayed(s,msdelay)
1486      char *s;
1487      long msdelay;
1488 {
1489     int count, outCount, outError;
1490
1491     if (icsPR == NULL) return;
1492
1493     count = strlen(s);
1494     if (appData.debugMode) {
1495         fprintf(debugFP, ">ICS: ");
1496         show_bytes(debugFP, s, count);
1497         fprintf(debugFP, "\n");
1498     }
1499     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500                                       msdelay);
1501     if (outCount < count) {
1502         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1503     }
1504 }
1505
1506
1507 /* Remove all highlighting escape sequences in s
1508    Also deletes any suffix starting with '(' 
1509    */
1510 char *
1511 StripHighlightAndTitle(s)
1512      char *s;
1513 {
1514     static char retbuf[MSG_SIZ];
1515     char *p = retbuf;
1516
1517     while (*s != NULLCHAR) {
1518         while (*s == '\033') {
1519             while (*s != NULLCHAR && !isalpha(*s)) s++;
1520             if (*s != NULLCHAR) s++;
1521         }
1522         while (*s != NULLCHAR && *s != '\033') {
1523             if (*s == '(' || *s == '[') {
1524                 *p = NULLCHAR;
1525                 return retbuf;
1526             }
1527             *p++ = *s++;
1528         }
1529     }
1530     *p = NULLCHAR;
1531     return retbuf;
1532 }
1533
1534 /* Remove all highlighting escape sequences in s */
1535 char *
1536 StripHighlight(s)
1537      char *s;
1538 {
1539     static char retbuf[MSG_SIZ];
1540     char *p = retbuf;
1541
1542     while (*s != NULLCHAR) {
1543         while (*s == '\033') {
1544             while (*s != NULLCHAR && !isalpha(*s)) s++;
1545             if (*s != NULLCHAR) s++;
1546         }
1547         while (*s != NULLCHAR && *s != '\033') {
1548             *p++ = *s++;
1549         }
1550     }
1551     *p = NULLCHAR;
1552     return retbuf;
1553 }
1554
1555 char *variantNames[] = VARIANT_NAMES;
1556 char *
1557 VariantName(v)
1558      VariantClass v;
1559 {
1560     return variantNames[v];
1561 }
1562
1563
1564 /* Identify a variant from the strings the chess servers use or the
1565    PGN Variant tag names we use. */
1566 VariantClass
1567 StringToVariant(e)
1568      char *e;
1569 {
1570     char *p;
1571     int wnum = -1;
1572     VariantClass v = VariantNormal;
1573     int i, found = FALSE;
1574     char buf[MSG_SIZ];
1575
1576     if (!e) return v;
1577
1578     /* [HGM] skip over optional board-size prefixes */
1579     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1580         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1581         while( *e++ != '_');
1582     }
1583
1584     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1585         v = VariantNormal;
1586         found = TRUE;
1587     } else
1588     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1589       if (StrCaseStr(e, variantNames[i])) {
1590         v = (VariantClass) i;
1591         found = TRUE;
1592         break;
1593       }
1594     }
1595
1596     if (!found) {
1597       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1598           || StrCaseStr(e, "wild/fr") 
1599           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1600         v = VariantFischeRandom;
1601       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1602                  (i = 1, p = StrCaseStr(e, "w"))) {
1603         p += i;
1604         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1605         if (isdigit(*p)) {
1606           wnum = atoi(p);
1607         } else {
1608           wnum = -1;
1609         }
1610         switch (wnum) {
1611         case 0: /* FICS only, actually */
1612         case 1:
1613           /* Castling legal even if K starts on d-file */
1614           v = VariantWildCastle;
1615           break;
1616         case 2:
1617         case 3:
1618         case 4:
1619           /* Castling illegal even if K & R happen to start in
1620              normal positions. */
1621           v = VariantNoCastle;
1622           break;
1623         case 5:
1624         case 7:
1625         case 8:
1626         case 10:
1627         case 11:
1628         case 12:
1629         case 13:
1630         case 14:
1631         case 15:
1632         case 18:
1633         case 19:
1634           /* Castling legal iff K & R start in normal positions */
1635           v = VariantNormal;
1636           break;
1637         case 6:
1638         case 20:
1639         case 21:
1640           /* Special wilds for position setup; unclear what to do here */
1641           v = VariantLoadable;
1642           break;
1643         case 9:
1644           /* Bizarre ICC game */
1645           v = VariantTwoKings;
1646           break;
1647         case 16:
1648           v = VariantKriegspiel;
1649           break;
1650         case 17:
1651           v = VariantLosers;
1652           break;
1653         case 22:
1654           v = VariantFischeRandom;
1655           break;
1656         case 23:
1657           v = VariantCrazyhouse;
1658           break;
1659         case 24:
1660           v = VariantBughouse;
1661           break;
1662         case 25:
1663           v = Variant3Check;
1664           break;
1665         case 26:
1666           /* Not quite the same as FICS suicide! */
1667           v = VariantGiveaway;
1668           break;
1669         case 27:
1670           v = VariantAtomic;
1671           break;
1672         case 28:
1673           v = VariantShatranj;
1674           break;
1675
1676         /* Temporary names for future ICC types.  The name *will* change in 
1677            the next xboard/WinBoard release after ICC defines it. */
1678         case 29:
1679           v = Variant29;
1680           break;
1681         case 30:
1682           v = Variant30;
1683           break;
1684         case 31:
1685           v = Variant31;
1686           break;
1687         case 32:
1688           v = Variant32;
1689           break;
1690         case 33:
1691           v = Variant33;
1692           break;
1693         case 34:
1694           v = Variant34;
1695           break;
1696         case 35:
1697           v = Variant35;
1698           break;
1699         case 36:
1700           v = Variant36;
1701           break;
1702         case 37:
1703           v = VariantShogi;
1704           break;
1705         case 38:
1706           v = VariantXiangqi;
1707           break;
1708         case 39:
1709           v = VariantCourier;
1710           break;
1711         case 40:
1712           v = VariantGothic;
1713           break;
1714         case 41:
1715           v = VariantCapablanca;
1716           break;
1717         case 42:
1718           v = VariantKnightmate;
1719           break;
1720         case 43:
1721           v = VariantFairy;
1722           break;
1723         case 44:
1724           v = VariantCylinder;
1725           break;
1726         case 45:
1727           v = VariantFalcon;
1728           break;
1729         case 46:
1730           v = VariantCapaRandom;
1731           break;
1732         case 47:
1733           v = VariantBerolina;
1734           break;
1735         case 48:
1736           v = VariantJanus;
1737           break;
1738         case 49:
1739           v = VariantSuper;
1740           break;
1741         case 50:
1742           v = VariantGreat;
1743           break;
1744         case -1:
1745           /* Found "wild" or "w" in the string but no number;
1746              must assume it's normal chess. */
1747           v = VariantNormal;
1748           break;
1749         default:
1750           sprintf(buf, _("Unknown wild type %d"), wnum);
1751           DisplayError(buf, 0);
1752           v = VariantUnknown;
1753           break;
1754         }
1755       }
1756     }
1757     if (appData.debugMode) {
1758       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1759               e, wnum, VariantName(v));
1760     }
1761     return v;
1762 }
1763
1764 static int leftover_start = 0, leftover_len = 0;
1765 char star_match[STAR_MATCH_N][MSG_SIZ];
1766
1767 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1768    advance *index beyond it, and set leftover_start to the new value of
1769    *index; else return FALSE.  If pattern contains the character '*', it
1770    matches any sequence of characters not containing '\r', '\n', or the
1771    character following the '*' (if any), and the matched sequence(s) are
1772    copied into star_match.
1773    */
1774 int
1775 looking_at(buf, index, pattern)
1776      char *buf;
1777      int *index;
1778      char *pattern;
1779 {
1780     char *bufp = &buf[*index], *patternp = pattern;
1781     int star_count = 0;
1782     char *matchp = star_match[0];
1783     
1784     for (;;) {
1785         if (*patternp == NULLCHAR) {
1786             *index = leftover_start = bufp - buf;
1787             *matchp = NULLCHAR;
1788             return TRUE;
1789         }
1790         if (*bufp == NULLCHAR) return FALSE;
1791         if (*patternp == '*') {
1792             if (*bufp == *(patternp + 1)) {
1793                 *matchp = NULLCHAR;
1794                 matchp = star_match[++star_count];
1795                 patternp += 2;
1796                 bufp++;
1797                 continue;
1798             } else if (*bufp == '\n' || *bufp == '\r') {
1799                 patternp++;
1800                 if (*patternp == NULLCHAR)
1801                   continue;
1802                 else
1803                   return FALSE;
1804             } else {
1805                 *matchp++ = *bufp++;
1806                 continue;
1807             }
1808         }
1809         if (*patternp != *bufp) return FALSE;
1810         patternp++;
1811         bufp++;
1812     }
1813 }
1814
1815 void
1816 SendToPlayer(data, length)
1817      char *data;
1818      int length;
1819 {
1820     int error, outCount;
1821     outCount = OutputToProcess(NoProc, data, length, &error);
1822     if (outCount < length) {
1823         DisplayFatalError(_("Error writing to display"), error, 1);
1824     }
1825 }
1826
1827 void
1828 PackHolding(packed, holding)
1829      char packed[];
1830      char *holding;
1831 {
1832     char *p = holding;
1833     char *q = packed;
1834     int runlength = 0;
1835     int curr = 9999;
1836     do {
1837         if (*p == curr) {
1838             runlength++;
1839         } else {
1840             switch (runlength) {
1841               case 0:
1842                 break;
1843               case 1:
1844                 *q++ = curr;
1845                 break;
1846               case 2:
1847                 *q++ = curr;
1848                 *q++ = curr;
1849                 break;
1850               default:
1851                 sprintf(q, "%d", runlength);
1852                 while (*q) q++;
1853                 *q++ = curr;
1854                 break;
1855             }
1856             runlength = 1;
1857             curr = *p;
1858         }
1859     } while (*p++);
1860     *q = NULLCHAR;
1861 }
1862
1863 /* Telnet protocol requests from the front end */
1864 void
1865 TelnetRequest(ddww, option)
1866      unsigned char ddww, option;
1867 {
1868     unsigned char msg[3];
1869     int outCount, outError;
1870
1871     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1872
1873     if (appData.debugMode) {
1874         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875         switch (ddww) {
1876           case TN_DO:
1877             ddwwStr = "DO";
1878             break;
1879           case TN_DONT:
1880             ddwwStr = "DONT";
1881             break;
1882           case TN_WILL:
1883             ddwwStr = "WILL";
1884             break;
1885           case TN_WONT:
1886             ddwwStr = "WONT";
1887             break;
1888           default:
1889             ddwwStr = buf1;
1890             sprintf(buf1, "%d", ddww);
1891             break;
1892         }
1893         switch (option) {
1894           case TN_ECHO:
1895             optionStr = "ECHO";
1896             break;
1897           default:
1898             optionStr = buf2;
1899             sprintf(buf2, "%d", option);
1900             break;
1901         }
1902         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1903     }
1904     msg[0] = TN_IAC;
1905     msg[1] = ddww;
1906     msg[2] = option;
1907     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1908     if (outCount < 3) {
1909         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910     }
1911 }
1912
1913 void
1914 DoEcho()
1915 {
1916     if (!appData.icsActive) return;
1917     TelnetRequest(TN_DO, TN_ECHO);
1918 }
1919
1920 void
1921 DontEcho()
1922 {
1923     if (!appData.icsActive) return;
1924     TelnetRequest(TN_DONT, TN_ECHO);
1925 }
1926
1927 void
1928 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1929 {
1930     /* put the holdings sent to us by the server on the board holdings area */
1931     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1932     char p;
1933     ChessSquare piece;
1934
1935     if(gameInfo.holdingsWidth < 2)  return;
1936     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1937         return; // prevent overwriting by pre-board holdings
1938
1939     if( (int)lowestPiece >= BlackPawn ) {
1940         holdingsColumn = 0;
1941         countsColumn = 1;
1942         holdingsStartRow = BOARD_HEIGHT-1;
1943         direction = -1;
1944     } else {
1945         holdingsColumn = BOARD_WIDTH-1;
1946         countsColumn = BOARD_WIDTH-2;
1947         holdingsStartRow = 0;
1948         direction = 1;
1949     }
1950
1951     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1952         board[i][holdingsColumn] = EmptySquare;
1953         board[i][countsColumn]   = (ChessSquare) 0;
1954     }
1955     while( (p=*holdings++) != NULLCHAR ) {
1956         piece = CharToPiece( ToUpper(p) );
1957         if(piece == EmptySquare) continue;
1958         /*j = (int) piece - (int) WhitePawn;*/
1959         j = PieceToNumber(piece);
1960         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1961         if(j < 0) continue;               /* should not happen */
1962         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1963         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1964         board[holdingsStartRow+j*direction][countsColumn]++;
1965     }
1966 }
1967
1968
1969 void
1970 VariantSwitch(Board board, VariantClass newVariant)
1971 {
1972    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1973    Board oldBoard;
1974
1975    startedFromPositionFile = FALSE;
1976    if(gameInfo.variant == newVariant) return;
1977
1978    /* [HGM] This routine is called each time an assignment is made to
1979     * gameInfo.variant during a game, to make sure the board sizes
1980     * are set to match the new variant. If that means adding or deleting
1981     * holdings, we shift the playing board accordingly
1982     * This kludge is needed because in ICS observe mode, we get boards
1983     * of an ongoing game without knowing the variant, and learn about the
1984     * latter only later. This can be because of the move list we requested,
1985     * in which case the game history is refilled from the beginning anyway,
1986     * but also when receiving holdings of a crazyhouse game. In the latter
1987     * case we want to add those holdings to the already received position.
1988     */
1989
1990    
1991    if (appData.debugMode) {
1992      fprintf(debugFP, "Switch board from %s to %s\n",
1993              VariantName(gameInfo.variant), VariantName(newVariant));
1994      setbuf(debugFP, NULL);
1995    }
1996    shuffleOpenings = 0;       /* [HGM] shuffle */
1997    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1998    switch(newVariant) 
1999      {
2000      case VariantShogi:
2001        newWidth = 9;  newHeight = 9;
2002        gameInfo.holdingsSize = 7;
2003      case VariantBughouse:
2004      case VariantCrazyhouse:
2005        newHoldingsWidth = 2; break;
2006      case VariantGreat:
2007        newWidth = 10;
2008      case VariantSuper:
2009        newHoldingsWidth = 2;
2010        gameInfo.holdingsSize = 8;
2011        break;
2012      case VariantGothic:
2013      case VariantCapablanca:
2014      case VariantCapaRandom:
2015        newWidth = 10;
2016      default:
2017        newHoldingsWidth = gameInfo.holdingsSize = 0;
2018      };
2019    
2020    if(newWidth  != gameInfo.boardWidth  ||
2021       newHeight != gameInfo.boardHeight ||
2022       newHoldingsWidth != gameInfo.holdingsWidth ) {
2023      
2024      /* shift position to new playing area, if needed */
2025      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2026        for(i=0; i<BOARD_HEIGHT; i++) 
2027          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2028            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2029              board[i][j];
2030        for(i=0; i<newHeight; i++) {
2031          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2032          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2033        }
2034      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2035        for(i=0; i<BOARD_HEIGHT; i++)
2036          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2037            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2038              board[i][j];
2039      }
2040      gameInfo.boardWidth  = newWidth;
2041      gameInfo.boardHeight = newHeight;
2042      gameInfo.holdingsWidth = newHoldingsWidth;
2043      gameInfo.variant = newVariant;
2044      InitDrawingSizes(-2, 0);
2045    } else gameInfo.variant = newVariant;
2046    CopyBoard(oldBoard, board);   // remember correctly formatted board
2047      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2048    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2049 }
2050
2051 static int loggedOn = FALSE;
2052
2053 /*-- Game start info cache: --*/
2054 int gs_gamenum;
2055 char gs_kind[MSG_SIZ];
2056 static char player1Name[128] = "";
2057 static char player2Name[128] = "";
2058 static char cont_seq[] = "\n\\   ";
2059 static int player1Rating = -1;
2060 static int player2Rating = -1;
2061 /*----------------------------*/
2062
2063 ColorClass curColor = ColorNormal;
2064 int suppressKibitz = 0;
2065
2066 void
2067 read_from_ics(isr, closure, data, count, error)
2068      InputSourceRef isr;
2069      VOIDSTAR closure;
2070      char *data;
2071      int count;
2072      int error;
2073 {
2074 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2075 #define STARTED_NONE 0
2076 #define STARTED_MOVES 1
2077 #define STARTED_BOARD 2
2078 #define STARTED_OBSERVE 3
2079 #define STARTED_HOLDINGS 4
2080 #define STARTED_CHATTER 5
2081 #define STARTED_COMMENT 6
2082 #define STARTED_MOVES_NOHIDE 7
2083     
2084     static int started = STARTED_NONE;
2085     static char parse[20000];
2086     static int parse_pos = 0;
2087     static char buf[BUF_SIZE + 1];
2088     static int firstTime = TRUE, intfSet = FALSE;
2089     static ColorClass prevColor = ColorNormal;
2090     static int savingComment = FALSE;
2091     static int cmatch = 0; // continuation sequence match
2092     char *bp;
2093     char str[500];
2094     int i, oldi;
2095     int buf_len;
2096     int next_out;
2097     int tkind;
2098     int backup;    /* [DM] For zippy color lines */
2099     char *p;
2100     char talker[MSG_SIZ]; // [HGM] chat
2101     int channel;
2102
2103     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2104
2105     if (appData.debugMode) {
2106       if (!error) {
2107         fprintf(debugFP, "<ICS: ");
2108         show_bytes(debugFP, data, count);
2109         fprintf(debugFP, "\n");
2110       }
2111     }
2112
2113     if (appData.debugMode) { int f = forwardMostMove;
2114         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2115                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2116                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2117     }
2118     if (count > 0) {
2119         /* If last read ended with a partial line that we couldn't parse,
2120            prepend it to the new read and try again. */
2121         if (leftover_len > 0) {
2122             for (i=0; i<leftover_len; i++)
2123               buf[i] = buf[leftover_start + i];
2124         }
2125
2126     /* copy new characters into the buffer */
2127     bp = buf + leftover_len;
2128     buf_len=leftover_len;
2129     for (i=0; i<count; i++)
2130     {
2131         // ignore these
2132         if (data[i] == '\r')
2133             continue;
2134
2135         // join lines split by ICS?
2136         if (!appData.noJoin)
2137         {
2138             /*
2139                 Joining just consists of finding matches against the
2140                 continuation sequence, and discarding that sequence
2141                 if found instead of copying it.  So, until a match
2142                 fails, there's nothing to do since it might be the
2143                 complete sequence, and thus, something we don't want
2144                 copied.
2145             */
2146             if (data[i] == cont_seq[cmatch])
2147             {
2148                 cmatch++;
2149                 if (cmatch == strlen(cont_seq))
2150                 {
2151                     cmatch = 0; // complete match.  just reset the counter
2152
2153                     /*
2154                         it's possible for the ICS to not include the space
2155                         at the end of the last word, making our [correct]
2156                         join operation fuse two separate words.  the server
2157                         does this when the space occurs at the width setting.
2158                     */
2159                     if (!buf_len || buf[buf_len-1] != ' ')
2160                     {
2161                         *bp++ = ' ';
2162                         buf_len++;
2163                     }
2164                 }
2165                 continue;
2166             }
2167             else if (cmatch)
2168             {
2169                 /*
2170                     match failed, so we have to copy what matched before
2171                     falling through and copying this character.  In reality,
2172                     this will only ever be just the newline character, but
2173                     it doesn't hurt to be precise.
2174                 */
2175                 strncpy(bp, cont_seq, cmatch);
2176                 bp += cmatch;
2177                 buf_len += cmatch;
2178                 cmatch = 0;
2179             }
2180         }
2181
2182         // copy this char
2183         *bp++ = data[i];
2184         buf_len++;
2185     }
2186
2187         buf[buf_len] = NULLCHAR;
2188 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2189         next_out = 0;
2190         leftover_start = 0;
2191         
2192         i = 0;
2193         while (i < buf_len) {
2194             /* Deal with part of the TELNET option negotiation
2195                protocol.  We refuse to do anything beyond the
2196                defaults, except that we allow the WILL ECHO option,
2197                which ICS uses to turn off password echoing when we are
2198                directly connected to it.  We reject this option
2199                if localLineEditing mode is on (always on in xboard)
2200                and we are talking to port 23, which might be a real
2201                telnet server that will try to keep WILL ECHO on permanently.
2202              */
2203             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2204                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2205                 unsigned char option;
2206                 oldi = i;
2207                 switch ((unsigned char) buf[++i]) {
2208                   case TN_WILL:
2209                     if (appData.debugMode)
2210                       fprintf(debugFP, "\n<WILL ");
2211                     switch (option = (unsigned char) buf[++i]) {
2212                       case TN_ECHO:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "ECHO ");
2215                         /* Reply only if this is a change, according
2216                            to the protocol rules. */
2217                         if (remoteEchoOption) break;
2218                         if (appData.localLineEditing &&
2219                             atoi(appData.icsPort) == TN_PORT) {
2220                             TelnetRequest(TN_DONT, TN_ECHO);
2221                         } else {
2222                             EchoOff();
2223                             TelnetRequest(TN_DO, TN_ECHO);
2224                             remoteEchoOption = TRUE;
2225                         }
2226                         break;
2227                       default:
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", option);
2230                         /* Whatever this is, we don't want it. */
2231                         TelnetRequest(TN_DONT, option);
2232                         break;
2233                     }
2234                     break;
2235                   case TN_WONT:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<WONT ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       case TN_ECHO:
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "ECHO ");
2242                         /* Reply only if this is a change, according
2243                            to the protocol rules. */
2244                         if (!remoteEchoOption) break;
2245                         EchoOn();
2246                         TelnetRequest(TN_DONT, TN_ECHO);
2247                         remoteEchoOption = FALSE;
2248                         break;
2249                       default:
2250                         if (appData.debugMode)
2251                           fprintf(debugFP, "%d ", (unsigned char) option);
2252                         /* Whatever this is, it must already be turned
2253                            off, because we never agree to turn on
2254                            anything non-default, so according to the
2255                            protocol rules, we don't reply. */
2256                         break;
2257                     }
2258                     break;
2259                   case TN_DO:
2260                     if (appData.debugMode)
2261                       fprintf(debugFP, "\n<DO ");
2262                     switch (option = (unsigned char) buf[++i]) {
2263                       default:
2264                         /* Whatever this is, we refuse to do it. */
2265                         if (appData.debugMode)
2266                           fprintf(debugFP, "%d ", option);
2267                         TelnetRequest(TN_WONT, option);
2268                         break;
2269                     }
2270                     break;
2271                   case TN_DONT:
2272                     if (appData.debugMode)
2273                       fprintf(debugFP, "\n<DONT ");
2274                     switch (option = (unsigned char) buf[++i]) {
2275                       default:
2276                         if (appData.debugMode)
2277                           fprintf(debugFP, "%d ", option);
2278                         /* Whatever this is, we are already not doing
2279                            it, because we never agree to do anything
2280                            non-default, so according to the protocol
2281                            rules, we don't reply. */
2282                         break;
2283                     }
2284                     break;
2285                   case TN_IAC:
2286                     if (appData.debugMode)
2287                       fprintf(debugFP, "\n<IAC ");
2288                     /* Doubled IAC; pass it through */
2289                     i--;
2290                     break;
2291                   default:
2292                     if (appData.debugMode)
2293                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2294                     /* Drop all other telnet commands on the floor */
2295                     break;
2296                 }
2297                 if (oldi > next_out)
2298                   SendToPlayer(&buf[next_out], oldi - next_out);
2299                 if (++i > next_out)
2300                   next_out = i;
2301                 continue;
2302             }
2303                 
2304             /* OK, this at least will *usually* work */
2305             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2306                 loggedOn = TRUE;
2307             }
2308             
2309             if (loggedOn && !intfSet) {
2310                 if (ics_type == ICS_ICC) {
2311                   sprintf(str,
2312                           "/set-quietly interface %s\n/set-quietly style 12\n",
2313                           programVersion);
2314                 } else if (ics_type == ICS_CHESSNET) {
2315                   sprintf(str, "/style 12\n");
2316                 } else {
2317                   strcpy(str, "alias $ @\n$set interface ");
2318                   strcat(str, programVersion);
2319                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2320 #ifdef WIN32
2321                   strcat(str, "$iset nohighlight 1\n");
2322 #endif
2323                   strcat(str, "$iset lock 1\n$style 12\n");
2324                 }
2325                 SendToICS(str);
2326                 NotifyFrontendLogin();
2327                 intfSet = TRUE;
2328             }
2329
2330             if (started == STARTED_COMMENT) {
2331                 /* Accumulate characters in comment */
2332                 parse[parse_pos++] = buf[i];
2333                 if (buf[i] == '\n') {
2334                     parse[parse_pos] = NULLCHAR;
2335                     if(chattingPartner>=0) {
2336                         char mess[MSG_SIZ];
2337                         sprintf(mess, "%s%s", talker, parse);
2338                         OutputChatMessage(chattingPartner, mess);
2339                         chattingPartner = -1;
2340                     } else
2341                     if(!suppressKibitz) // [HGM] kibitz
2342                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2343                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2344                         int nrDigit = 0, nrAlph = 0, j;
2345                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2346                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2347                         parse[parse_pos] = NULLCHAR;
2348                         // try to be smart: if it does not look like search info, it should go to
2349                         // ICS interaction window after all, not to engine-output window.
2350                         for(j=0; j<parse_pos; j++) { // count letters and digits
2351                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2352                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2353                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2354                         }
2355                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2356                             int depth=0; float score;
2357                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2358                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2359                                 pvInfoList[forwardMostMove-1].depth = depth;
2360                                 pvInfoList[forwardMostMove-1].score = 100*score;
2361                             }
2362                             OutputKibitz(suppressKibitz, parse);
2363                             next_out = i+1; // [HGM] suppress printing in ICS window
2364                         } else {
2365                             char tmp[MSG_SIZ];
2366                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2367                             SendToPlayer(tmp, strlen(tmp));
2368                         }
2369                     }
2370                     started = STARTED_NONE;
2371                 } else {
2372                     /* Don't match patterns against characters in comment */
2373                     i++;
2374                     continue;
2375                 }
2376             }
2377             if (started == STARTED_CHATTER) {
2378                 if (buf[i] != '\n') {
2379                     /* Don't match patterns against characters in chatter */
2380                     i++;
2381                     continue;
2382                 }
2383                 started = STARTED_NONE;
2384             }
2385
2386             /* Kludge to deal with rcmd protocol */
2387             if (firstTime && looking_at(buf, &i, "\001*")) {
2388                 DisplayFatalError(&buf[1], 0, 1);
2389                 continue;
2390             } else {
2391                 firstTime = FALSE;
2392             }
2393
2394             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2395                 ics_type = ICS_ICC;
2396                 ics_prefix = "/";
2397                 if (appData.debugMode)
2398                   fprintf(debugFP, "ics_type %d\n", ics_type);
2399                 continue;
2400             }
2401             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2402                 ics_type = ICS_FICS;
2403                 ics_prefix = "$";
2404                 if (appData.debugMode)
2405                   fprintf(debugFP, "ics_type %d\n", ics_type);
2406                 continue;
2407             }
2408             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2409                 ics_type = ICS_CHESSNET;
2410                 ics_prefix = "/";
2411                 if (appData.debugMode)
2412                   fprintf(debugFP, "ics_type %d\n", ics_type);
2413                 continue;
2414             }
2415
2416             if (!loggedOn &&
2417                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2418                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2419                  looking_at(buf, &i, "will be \"*\""))) {
2420               strcpy(ics_handle, star_match[0]);
2421               continue;
2422             }
2423
2424             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2425               char buf[MSG_SIZ];
2426               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2427               DisplayIcsInteractionTitle(buf);
2428               have_set_title = TRUE;
2429             }
2430
2431             /* skip finger notes */
2432             if (started == STARTED_NONE &&
2433                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2434                  (buf[i] == '1' && buf[i+1] == '0')) &&
2435                 buf[i+2] == ':' && buf[i+3] == ' ') {
2436               started = STARTED_CHATTER;
2437               i += 3;
2438               continue;
2439             }
2440
2441             /* skip formula vars */
2442             if (started == STARTED_NONE &&
2443                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2444               started = STARTED_CHATTER;
2445               i += 3;
2446               continue;
2447             }
2448
2449             oldi = i;
2450             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2451             if (appData.autoKibitz && started == STARTED_NONE && 
2452                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2453                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2454                 if(looking_at(buf, &i, "* kibitzes: ") &&
2455                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2456                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2457                         suppressKibitz = TRUE;
2458                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2459                                 && (gameMode == IcsPlayingWhite)) ||
2460                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2461                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2462                             started = STARTED_CHATTER; // own kibitz we simply discard
2463                         else {
2464                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2465                             parse_pos = 0; parse[0] = NULLCHAR;
2466                             savingComment = TRUE;
2467                             suppressKibitz = gameMode != IcsObserving ? 2 :
2468                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2469                         } 
2470                         continue;
2471                 } else
2472                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2473                     // suppress the acknowledgements of our own autoKibitz
2474                     SendToPlayer(star_match[0], strlen(star_match[0]));
2475                     looking_at(buf, &i, "*% "); // eat prompt
2476                     next_out = i;
2477                 }
2478             } // [HGM] kibitz: end of patch
2479
2480 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2481
2482             // [HGM] chat: intercept tells by users for which we have an open chat window
2483             channel = -1;
2484             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2485                                            looking_at(buf, &i, "* whispers:") ||
2486                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2487                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2488                 int p;
2489                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2490                 chattingPartner = -1;
2491
2492                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2493                 for(p=0; p<MAX_CHAT; p++) {
2494                     if(channel == atoi(chatPartner[p])) {
2495                     talker[0] = '['; strcat(talker, "]");
2496                     chattingPartner = p; break;
2497                     }
2498                 } else
2499                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2500                 for(p=0; p<MAX_CHAT; p++) {
2501                     if(!strcmp("WHISPER", chatPartner[p])) {
2502                         talker[0] = '['; strcat(talker, "]");
2503                         chattingPartner = p; break;
2504                     }
2505                 }
2506                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2507                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2508                     talker[0] = 0;
2509                     chattingPartner = p; break;
2510                 }
2511                 if(chattingPartner<0) i = oldi; else {
2512                     started = STARTED_COMMENT;
2513                     parse_pos = 0; parse[0] = NULLCHAR;
2514                     savingComment = TRUE;
2515                     suppressKibitz = TRUE;
2516                 }
2517             } // [HGM] chat: end of patch
2518
2519             if (appData.zippyTalk || appData.zippyPlay) {
2520                 /* [DM] Backup address for color zippy lines */
2521                 backup = i;
2522 #if ZIPPY
2523        #ifdef WIN32
2524                if (loggedOn == TRUE)
2525                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2526                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2527        #else
2528                 if (ZippyControl(buf, &i) ||
2529                     ZippyConverse(buf, &i) ||
2530                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2531                       loggedOn = TRUE;
2532                       if (!appData.colorize) continue;
2533                 }
2534        #endif
2535 #endif
2536             } // [DM] 'else { ' deleted
2537                 if (
2538                     /* Regular tells and says */
2539                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2540                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2541                     looking_at(buf, &i, "* says: ") ||
2542                     /* Don't color "message" or "messages" output */
2543                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2544                     looking_at(buf, &i, "*. * at *:*: ") ||
2545                     looking_at(buf, &i, "--* (*:*): ") ||
2546                     /* Message notifications (same color as tells) */
2547                     looking_at(buf, &i, "* has left a message ") ||
2548                     looking_at(buf, &i, "* just sent you a message:\n") ||
2549                     /* Whispers and kibitzes */
2550                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2551                     looking_at(buf, &i, "* kibitzes: ") ||
2552                     /* Channel tells */
2553                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2554
2555                   if (tkind == 1 && strchr(star_match[0], ':')) {
2556                       /* Avoid "tells you:" spoofs in channels */
2557                      tkind = 3;
2558                   }
2559                   if (star_match[0][0] == NULLCHAR ||
2560                       strchr(star_match[0], ' ') ||
2561                       (tkind == 3 && strchr(star_match[1], ' '))) {
2562                     /* Reject bogus matches */
2563                     i = oldi;
2564                   } else {
2565                     if (appData.colorize) {
2566                       if (oldi > next_out) {
2567                         SendToPlayer(&buf[next_out], oldi - next_out);
2568                         next_out = oldi;
2569                       }
2570                       switch (tkind) {
2571                       case 1:
2572                         Colorize(ColorTell, FALSE);
2573                         curColor = ColorTell;
2574                         break;
2575                       case 2:
2576                         Colorize(ColorKibitz, FALSE);
2577                         curColor = ColorKibitz;
2578                         break;
2579                       case 3:
2580                         p = strrchr(star_match[1], '(');
2581                         if (p == NULL) {
2582                           p = star_match[1];
2583                         } else {
2584                           p++;
2585                         }
2586                         if (atoi(p) == 1) {
2587                           Colorize(ColorChannel1, FALSE);
2588                           curColor = ColorChannel1;
2589                         } else {
2590                           Colorize(ColorChannel, FALSE);
2591                           curColor = ColorChannel;
2592                         }
2593                         break;
2594                       case 5:
2595                         curColor = ColorNormal;
2596                         break;
2597                       }
2598                     }
2599                     if (started == STARTED_NONE && appData.autoComment &&
2600                         (gameMode == IcsObserving ||
2601                          gameMode == IcsPlayingWhite ||
2602                          gameMode == IcsPlayingBlack)) {
2603                       parse_pos = i - oldi;
2604                       memcpy(parse, &buf[oldi], parse_pos);
2605                       parse[parse_pos] = NULLCHAR;
2606                       started = STARTED_COMMENT;
2607                       savingComment = TRUE;
2608                     } else {
2609                       started = STARTED_CHATTER;
2610                       savingComment = FALSE;
2611                     }
2612                     loggedOn = TRUE;
2613                     continue;
2614                   }
2615                 }
2616
2617                 if (looking_at(buf, &i, "* s-shouts: ") ||
2618                     looking_at(buf, &i, "* c-shouts: ")) {
2619                     if (appData.colorize) {
2620                         if (oldi > next_out) {
2621                             SendToPlayer(&buf[next_out], oldi - next_out);
2622                             next_out = oldi;
2623                         }
2624                         Colorize(ColorSShout, FALSE);
2625                         curColor = ColorSShout;
2626                     }
2627                     loggedOn = TRUE;
2628                     started = STARTED_CHATTER;
2629                     continue;
2630                 }
2631
2632                 if (looking_at(buf, &i, "--->")) {
2633                     loggedOn = TRUE;
2634                     continue;
2635                 }
2636
2637                 if (looking_at(buf, &i, "* shouts: ") ||
2638                     looking_at(buf, &i, "--> ")) {
2639                     if (appData.colorize) {
2640                         if (oldi > next_out) {
2641                             SendToPlayer(&buf[next_out], oldi - next_out);
2642                             next_out = oldi;
2643                         }
2644                         Colorize(ColorShout, FALSE);
2645                         curColor = ColorShout;
2646                     }
2647                     loggedOn = TRUE;
2648                     started = STARTED_CHATTER;
2649                     continue;
2650                 }
2651
2652                 if (looking_at( buf, &i, "Challenge:")) {
2653                     if (appData.colorize) {
2654                         if (oldi > next_out) {
2655                             SendToPlayer(&buf[next_out], oldi - next_out);
2656                             next_out = oldi;
2657                         }
2658                         Colorize(ColorChallenge, FALSE);
2659                         curColor = ColorChallenge;
2660                     }
2661                     loggedOn = TRUE;
2662                     continue;
2663                 }
2664
2665                 if (looking_at(buf, &i, "* offers you") ||
2666                     looking_at(buf, &i, "* offers to be") ||
2667                     looking_at(buf, &i, "* would like to") ||
2668                     looking_at(buf, &i, "* requests to") ||
2669                     looking_at(buf, &i, "Your opponent offers") ||
2670                     looking_at(buf, &i, "Your opponent requests")) {
2671
2672                     if (appData.colorize) {
2673                         if (oldi > next_out) {
2674                             SendToPlayer(&buf[next_out], oldi - next_out);
2675                             next_out = oldi;
2676                         }
2677                         Colorize(ColorRequest, FALSE);
2678                         curColor = ColorRequest;
2679                     }
2680                     continue;
2681                 }
2682
2683                 if (looking_at(buf, &i, "* (*) seeking")) {
2684                     if (appData.colorize) {
2685                         if (oldi > next_out) {
2686                             SendToPlayer(&buf[next_out], oldi - next_out);
2687                             next_out = oldi;
2688                         }
2689                         Colorize(ColorSeek, FALSE);
2690                         curColor = ColorSeek;
2691                     }
2692                     continue;
2693             }
2694
2695             if (looking_at(buf, &i, "\\   ")) {
2696                 if (prevColor != ColorNormal) {
2697                     if (oldi > next_out) {
2698                         SendToPlayer(&buf[next_out], oldi - next_out);
2699                         next_out = oldi;
2700                     }
2701                     Colorize(prevColor, TRUE);
2702                     curColor = prevColor;
2703                 }
2704                 if (savingComment) {
2705                     parse_pos = i - oldi;
2706                     memcpy(parse, &buf[oldi], parse_pos);
2707                     parse[parse_pos] = NULLCHAR;
2708                     started = STARTED_COMMENT;
2709                 } else {
2710                     started = STARTED_CHATTER;
2711                 }
2712                 continue;
2713             }
2714
2715             if (looking_at(buf, &i, "Black Strength :") ||
2716                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2717                 looking_at(buf, &i, "<10>") ||
2718                 looking_at(buf, &i, "#@#")) {
2719                 /* Wrong board style */
2720                 loggedOn = TRUE;
2721                 SendToICS(ics_prefix);
2722                 SendToICS("set style 12\n");
2723                 SendToICS(ics_prefix);
2724                 SendToICS("refresh\n");
2725                 continue;
2726             }
2727             
2728             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2729                 ICSInitScript();
2730                 have_sent_ICS_logon = 1;
2731                 continue;
2732             }
2733               
2734             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2735                 (looking_at(buf, &i, "\n<12> ") ||
2736                  looking_at(buf, &i, "<12> "))) {
2737                 loggedOn = TRUE;
2738                 if (oldi > next_out) {
2739                     SendToPlayer(&buf[next_out], oldi - next_out);
2740                 }
2741                 next_out = i;
2742                 started = STARTED_BOARD;
2743                 parse_pos = 0;
2744                 continue;
2745             }
2746
2747             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2748                 looking_at(buf, &i, "<b1> ")) {
2749                 if (oldi > next_out) {
2750                     SendToPlayer(&buf[next_out], oldi - next_out);
2751                 }
2752                 next_out = i;
2753                 started = STARTED_HOLDINGS;
2754                 parse_pos = 0;
2755                 continue;
2756             }
2757
2758             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2759                 loggedOn = TRUE;
2760                 /* Header for a move list -- first line */
2761
2762                 switch (ics_getting_history) {
2763                   case H_FALSE:
2764                     switch (gameMode) {
2765                       case IcsIdle:
2766                       case BeginningOfGame:
2767                         /* User typed "moves" or "oldmoves" while we
2768                            were idle.  Pretend we asked for these
2769                            moves and soak them up so user can step
2770                            through them and/or save them.
2771                            */
2772                         Reset(FALSE, TRUE);
2773                         gameMode = IcsObserving;
2774                         ModeHighlight();
2775                         ics_gamenum = -1;
2776                         ics_getting_history = H_GOT_UNREQ_HEADER;
2777                         break;
2778                       case EditGame: /*?*/
2779                       case EditPosition: /*?*/
2780                         /* Should above feature work in these modes too? */
2781                         /* For now it doesn't */
2782                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2783                         break;
2784                       default:
2785                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2786                         break;
2787                     }
2788                     break;
2789                   case H_REQUESTED:
2790                     /* Is this the right one? */
2791                     if (gameInfo.white && gameInfo.black &&
2792                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2793                         strcmp(gameInfo.black, star_match[2]) == 0) {
2794                         /* All is well */
2795                         ics_getting_history = H_GOT_REQ_HEADER;
2796                     }
2797                     break;
2798                   case H_GOT_REQ_HEADER:
2799                   case H_GOT_UNREQ_HEADER:
2800                   case H_GOT_UNWANTED_HEADER:
2801                   case H_GETTING_MOVES:
2802                     /* Should not happen */
2803                     DisplayError(_("Error gathering move list: two headers"), 0);
2804                     ics_getting_history = H_FALSE;
2805                     break;
2806                 }
2807
2808                 /* Save player ratings into gameInfo if needed */
2809                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2810                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2811                     (gameInfo.whiteRating == -1 ||
2812                      gameInfo.blackRating == -1)) {
2813
2814                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2815                     gameInfo.blackRating = string_to_rating(star_match[3]);
2816                     if (appData.debugMode)
2817                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2818                               gameInfo.whiteRating, gameInfo.blackRating);
2819                 }
2820                 continue;
2821             }
2822
2823             if (looking_at(buf, &i,
2824               "* * match, initial time: * minute*, increment: * second")) {
2825                 /* Header for a move list -- second line */
2826                 /* Initial board will follow if this is a wild game */
2827                 if (gameInfo.event != NULL) free(gameInfo.event);
2828                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2829                 gameInfo.event = StrSave(str);
2830                 /* [HGM] we switched variant. Translate boards if needed. */
2831                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2832                 continue;
2833             }
2834
2835             if (looking_at(buf, &i, "Move  ")) {
2836                 /* Beginning of a move list */
2837                 switch (ics_getting_history) {
2838                   case H_FALSE:
2839                     /* Normally should not happen */
2840                     /* Maybe user hit reset while we were parsing */
2841                     break;
2842                   case H_REQUESTED:
2843                     /* Happens if we are ignoring a move list that is not
2844                      * the one we just requested.  Common if the user
2845                      * tries to observe two games without turning off
2846                      * getMoveList */
2847                     break;
2848                   case H_GETTING_MOVES:
2849                     /* Should not happen */
2850                     DisplayError(_("Error gathering move list: nested"), 0);
2851                     ics_getting_history = H_FALSE;
2852                     break;
2853                   case H_GOT_REQ_HEADER:
2854                     ics_getting_history = H_GETTING_MOVES;
2855                     started = STARTED_MOVES;
2856                     parse_pos = 0;
2857                     if (oldi > next_out) {
2858                         SendToPlayer(&buf[next_out], oldi - next_out);
2859                     }
2860                     break;
2861                   case H_GOT_UNREQ_HEADER:
2862                     ics_getting_history = H_GETTING_MOVES;
2863                     started = STARTED_MOVES_NOHIDE;
2864                     parse_pos = 0;
2865                     break;
2866                   case H_GOT_UNWANTED_HEADER:
2867                     ics_getting_history = H_FALSE;
2868                     break;
2869                 }
2870                 continue;
2871             }                           
2872             
2873             if (looking_at(buf, &i, "% ") ||
2874                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2875                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2876                 if(suppressKibitz) next_out = i;
2877                 savingComment = FALSE;
2878                 suppressKibitz = 0;
2879                 switch (started) {
2880                   case STARTED_MOVES:
2881                   case STARTED_MOVES_NOHIDE:
2882                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2883                     parse[parse_pos + i - oldi] = NULLCHAR;
2884                     ParseGameHistory(parse);
2885 #if ZIPPY
2886                     if (appData.zippyPlay && first.initDone) {
2887                         FeedMovesToProgram(&first, forwardMostMove);
2888                         if (gameMode == IcsPlayingWhite) {
2889                             if (WhiteOnMove(forwardMostMove)) {
2890                                 if (first.sendTime) {
2891                                   if (first.useColors) {
2892                                     SendToProgram("black\n", &first); 
2893                                   }
2894                                   SendTimeRemaining(&first, TRUE);
2895                                 }
2896                                 if (first.useColors) {
2897                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2898                                 }
2899                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2900                                 first.maybeThinking = TRUE;
2901                             } else {
2902                                 if (first.usePlayother) {
2903                                   if (first.sendTime) {
2904                                     SendTimeRemaining(&first, TRUE);
2905                                   }
2906                                   SendToProgram("playother\n", &first);
2907                                   firstMove = FALSE;
2908                                 } else {
2909                                   firstMove = TRUE;
2910                                 }
2911                             }
2912                         } else if (gameMode == IcsPlayingBlack) {
2913                             if (!WhiteOnMove(forwardMostMove)) {
2914                                 if (first.sendTime) {
2915                                   if (first.useColors) {
2916                                     SendToProgram("white\n", &first);
2917                                   }
2918                                   SendTimeRemaining(&first, FALSE);
2919                                 }
2920                                 if (first.useColors) {
2921                                   SendToProgram("black\n", &first);
2922                                 }
2923                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2924                                 first.maybeThinking = TRUE;
2925                             } else {
2926                                 if (first.usePlayother) {
2927                                   if (first.sendTime) {
2928                                     SendTimeRemaining(&first, FALSE);
2929                                   }
2930                                   SendToProgram("playother\n", &first);
2931                                   firstMove = FALSE;
2932                                 } else {
2933                                   firstMove = TRUE;
2934                                 }
2935                             }
2936                         }                       
2937                     }
2938 #endif
2939                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2940                         /* Moves came from oldmoves or moves command
2941                            while we weren't doing anything else.
2942                            */
2943                         currentMove = forwardMostMove;
2944                         ClearHighlights();/*!!could figure this out*/
2945                         flipView = appData.flipView;
2946                         DrawPosition(TRUE, boards[currentMove]);
2947                         DisplayBothClocks();
2948                         sprintf(str, "%s vs. %s",
2949                                 gameInfo.white, gameInfo.black);
2950                         DisplayTitle(str);
2951                         gameMode = IcsIdle;
2952                     } else {
2953                         /* Moves were history of an active game */
2954                         if (gameInfo.resultDetails != NULL) {
2955                             free(gameInfo.resultDetails);
2956                             gameInfo.resultDetails = NULL;
2957                         }
2958                     }
2959                     HistorySet(parseList, backwardMostMove,
2960                                forwardMostMove, currentMove-1);
2961                     DisplayMove(currentMove - 1);
2962                     if (started == STARTED_MOVES) next_out = i;
2963                     started = STARTED_NONE;
2964                     ics_getting_history = H_FALSE;
2965                     break;
2966
2967                   case STARTED_OBSERVE:
2968                     started = STARTED_NONE;
2969                     SendToICS(ics_prefix);
2970                     SendToICS("refresh\n");
2971                     break;
2972
2973                   default:
2974                     break;
2975                 }
2976                 if(bookHit) { // [HGM] book: simulate book reply
2977                     static char bookMove[MSG_SIZ]; // a bit generous?
2978
2979                     programStats.nodes = programStats.depth = programStats.time = 
2980                     programStats.score = programStats.got_only_move = 0;
2981                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2982
2983                     strcpy(bookMove, "move ");
2984                     strcat(bookMove, bookHit);
2985                     HandleMachineMove(bookMove, &first);
2986                 }
2987                 continue;
2988             }
2989             
2990             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2991                  started == STARTED_HOLDINGS ||
2992                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2993                 /* Accumulate characters in move list or board */
2994                 parse[parse_pos++] = buf[i];
2995             }
2996             
2997             /* Start of game messages.  Mostly we detect start of game
2998                when the first board image arrives.  On some versions
2999                of the ICS, though, we need to do a "refresh" after starting
3000                to observe in order to get the current board right away. */
3001             if (looking_at(buf, &i, "Adding game * to observation list")) {
3002                 started = STARTED_OBSERVE;
3003                 continue;
3004             }
3005
3006             /* Handle auto-observe */
3007             if (appData.autoObserve &&
3008                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3009                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3010                 char *player;
3011                 /* Choose the player that was highlighted, if any. */
3012                 if (star_match[0][0] == '\033' ||
3013                     star_match[1][0] != '\033') {
3014                     player = star_match[0];
3015                 } else {
3016                     player = star_match[2];
3017                 }
3018                 sprintf(str, "%sobserve %s\n",
3019                         ics_prefix, StripHighlightAndTitle(player));
3020                 SendToICS(str);
3021
3022                 /* Save ratings from notify string */
3023                 strcpy(player1Name, star_match[0]);
3024                 player1Rating = string_to_rating(star_match[1]);
3025                 strcpy(player2Name, star_match[2]);
3026                 player2Rating = string_to_rating(star_match[3]);
3027
3028                 if (appData.debugMode)
3029                   fprintf(debugFP, 
3030                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3031                           player1Name, player1Rating,
3032                           player2Name, player2Rating);
3033
3034                 continue;
3035             }
3036
3037             /* Deal with automatic examine mode after a game,
3038                and with IcsObserving -> IcsExamining transition */
3039             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3040                 looking_at(buf, &i, "has made you an examiner of game *")) {
3041
3042                 int gamenum = atoi(star_match[0]);
3043                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3044                     gamenum == ics_gamenum) {
3045                     /* We were already playing or observing this game;
3046                        no need to refetch history */
3047                     gameMode = IcsExamining;
3048                     if (pausing) {
3049                         pauseExamForwardMostMove = forwardMostMove;
3050                     } else if (currentMove < forwardMostMove) {
3051                         ForwardInner(forwardMostMove);
3052                     }
3053                 } else {
3054                     /* I don't think this case really can happen */
3055                     SendToICS(ics_prefix);
3056                     SendToICS("refresh\n");
3057                 }
3058                 continue;
3059             }    
3060             
3061             /* Error messages */
3062 //          if (ics_user_moved) {
3063             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3064                 if (looking_at(buf, &i, "Illegal move") ||
3065                     looking_at(buf, &i, "Not a legal move") ||
3066                     looking_at(buf, &i, "Your king is in check") ||
3067                     looking_at(buf, &i, "It isn't your turn") ||
3068                     looking_at(buf, &i, "It is not your move")) {
3069                     /* Illegal move */
3070                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3071                         currentMove = --forwardMostMove;
3072                         DisplayMove(currentMove - 1); /* before DMError */
3073                         DrawPosition(FALSE, boards[currentMove]);
3074                         SwitchClocks();
3075                         DisplayBothClocks();
3076                     }
3077                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3078                     ics_user_moved = 0;
3079                     continue;
3080                 }
3081             }
3082
3083             if (looking_at(buf, &i, "still have time") ||
3084                 looking_at(buf, &i, "not out of time") ||
3085                 looking_at(buf, &i, "either player is out of time") ||
3086                 looking_at(buf, &i, "has timeseal; checking")) {
3087                 /* We must have called his flag a little too soon */
3088                 whiteFlag = blackFlag = FALSE;
3089                 continue;
3090             }
3091
3092             if (looking_at(buf, &i, "added * seconds to") ||
3093                 looking_at(buf, &i, "seconds were added to")) {
3094                 /* Update the clocks */
3095                 SendToICS(ics_prefix);
3096                 SendToICS("refresh\n");
3097                 continue;
3098             }
3099
3100             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3101                 ics_clock_paused = TRUE;
3102                 StopClocks();
3103                 continue;
3104             }
3105
3106             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3107                 ics_clock_paused = FALSE;
3108                 StartClocks();
3109                 continue;
3110             }
3111
3112             /* Grab player ratings from the Creating: message.
3113                Note we have to check for the special case when
3114                the ICS inserts things like [white] or [black]. */
3115             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3116                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3117                 /* star_matches:
3118                    0    player 1 name (not necessarily white)
3119                    1    player 1 rating
3120                    2    empty, white, or black (IGNORED)
3121                    3    player 2 name (not necessarily black)
3122                    4    player 2 rating
3123                    
3124                    The names/ratings are sorted out when the game
3125                    actually starts (below).
3126                 */
3127                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3128                 player1Rating = string_to_rating(star_match[1]);
3129                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3130                 player2Rating = string_to_rating(star_match[4]);
3131
3132                 if (appData.debugMode)
3133                   fprintf(debugFP, 
3134                           "Ratings from 'Creating:' %s %d, %s %d\n",
3135                           player1Name, player1Rating,
3136                           player2Name, player2Rating);
3137
3138                 continue;
3139             }
3140             
3141             /* Improved generic start/end-of-game messages */
3142             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3143                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3144                 /* If tkind == 0: */
3145                 /* star_match[0] is the game number */
3146                 /*           [1] is the white player's name */
3147                 /*           [2] is the black player's name */
3148                 /* For end-of-game: */
3149                 /*           [3] is the reason for the game end */
3150                 /*           [4] is a PGN end game-token, preceded by " " */
3151                 /* For start-of-game: */
3152                 /*           [3] begins with "Creating" or "Continuing" */
3153                 /*           [4] is " *" or empty (don't care). */
3154                 int gamenum = atoi(star_match[0]);
3155                 char *whitename, *blackname, *why, *endtoken;
3156                 ChessMove endtype = (ChessMove) 0;
3157
3158                 if (tkind == 0) {
3159                   whitename = star_match[1];
3160                   blackname = star_match[2];
3161                   why = star_match[3];
3162                   endtoken = star_match[4];
3163                 } else {
3164                   whitename = star_match[1];
3165                   blackname = star_match[3];
3166                   why = star_match[5];
3167                   endtoken = star_match[6];
3168                 }
3169
3170                 /* Game start messages */
3171                 if (strncmp(why, "Creating ", 9) == 0 ||
3172                     strncmp(why, "Continuing ", 11) == 0) {
3173                     gs_gamenum = gamenum;
3174                     strcpy(gs_kind, strchr(why, ' ') + 1);
3175                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3176 #if ZIPPY
3177                     if (appData.zippyPlay) {
3178                         ZippyGameStart(whitename, blackname);
3179                     }
3180 #endif /*ZIPPY*/
3181                     continue;
3182                 }
3183
3184                 /* Game end messages */
3185                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3186                     ics_gamenum != gamenum) {
3187                     continue;
3188                 }
3189                 while (endtoken[0] == ' ') endtoken++;
3190                 switch (endtoken[0]) {
3191                   case '*':
3192                   default:
3193                     endtype = GameUnfinished;
3194                     break;
3195                   case '0':
3196                     endtype = BlackWins;
3197                     break;
3198                   case '1':
3199                     if (endtoken[1] == '/')
3200                       endtype = GameIsDrawn;
3201                     else
3202                       endtype = WhiteWins;
3203                     break;
3204                 }
3205                 GameEnds(endtype, why, GE_ICS);
3206 #if ZIPPY
3207                 if (appData.zippyPlay && first.initDone) {
3208                     ZippyGameEnd(endtype, why);
3209                     if (first.pr == NULL) {
3210                       /* Start the next process early so that we'll
3211                          be ready for the next challenge */
3212                       StartChessProgram(&first);
3213                     }
3214                     /* Send "new" early, in case this command takes
3215                        a long time to finish, so that we'll be ready
3216                        for the next challenge. */
3217                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3218                     Reset(TRUE, TRUE);
3219                 }
3220 #endif /*ZIPPY*/
3221                 continue;
3222             }
3223
3224             if (looking_at(buf, &i, "Removing game * from observation") ||
3225                 looking_at(buf, &i, "no longer observing game *") ||
3226                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3227                 if (gameMode == IcsObserving &&
3228                     atoi(star_match[0]) == ics_gamenum)
3229                   {
3230                       /* icsEngineAnalyze */
3231                       if (appData.icsEngineAnalyze) {
3232                             ExitAnalyzeMode();
3233                             ModeHighlight();
3234                       }
3235                       StopClocks();
3236                       gameMode = IcsIdle;
3237                       ics_gamenum = -1;
3238                       ics_user_moved = FALSE;
3239                   }
3240                 continue;
3241             }
3242
3243             if (looking_at(buf, &i, "no longer examining game *")) {
3244                 if (gameMode == IcsExamining &&
3245                     atoi(star_match[0]) == ics_gamenum)
3246                   {
3247                       gameMode = IcsIdle;
3248                       ics_gamenum = -1;
3249                       ics_user_moved = FALSE;
3250                   }
3251                 continue;
3252             }
3253
3254             /* Advance leftover_start past any newlines we find,
3255                so only partial lines can get reparsed */
3256             if (looking_at(buf, &i, "\n")) {
3257                 prevColor = curColor;
3258                 if (curColor != ColorNormal) {
3259                     if (oldi > next_out) {
3260                         SendToPlayer(&buf[next_out], oldi - next_out);
3261                         next_out = oldi;
3262                     }
3263                     Colorize(ColorNormal, FALSE);
3264                     curColor = ColorNormal;
3265                 }
3266                 if (started == STARTED_BOARD) {
3267                     started = STARTED_NONE;
3268                     parse[parse_pos] = NULLCHAR;
3269                     ParseBoard12(parse);
3270                     ics_user_moved = 0;
3271
3272                     /* Send premove here */
3273                     if (appData.premove) {
3274                       char str[MSG_SIZ];
3275                       if (currentMove == 0 &&
3276                           gameMode == IcsPlayingWhite &&
3277                           appData.premoveWhite) {
3278                         sprintf(str, "%s\n", appData.premoveWhiteText);
3279                         if (appData.debugMode)
3280                           fprintf(debugFP, "Sending premove:\n");
3281                         SendToICS(str);
3282                       } else if (currentMove == 1 &&
3283                                  gameMode == IcsPlayingBlack &&
3284                                  appData.premoveBlack) {
3285                         sprintf(str, "%s\n", appData.premoveBlackText);
3286                         if (appData.debugMode)
3287                           fprintf(debugFP, "Sending premove:\n");
3288                         SendToICS(str);
3289                       } else if (gotPremove) {
3290                         gotPremove = 0;
3291                         ClearPremoveHighlights();
3292                         if (appData.debugMode)
3293                           fprintf(debugFP, "Sending premove:\n");
3294                           UserMoveEvent(premoveFromX, premoveFromY, 
3295                                         premoveToX, premoveToY, 
3296                                         premovePromoChar);
3297                       }
3298                     }
3299
3300                     /* Usually suppress following prompt */
3301                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3302                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3303                         if (looking_at(buf, &i, "*% ")) {
3304                             savingComment = FALSE;
3305                             suppressKibitz = 0;
3306                         }
3307                     }
3308                     next_out = i;
3309                 } else if (started == STARTED_HOLDINGS) {
3310                     int gamenum;
3311                     char new_piece[MSG_SIZ];
3312                     started = STARTED_NONE;
3313                     parse[parse_pos] = NULLCHAR;
3314                     if (appData.debugMode)
3315                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3316                                                         parse, currentMove);
3317                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3318                         gamenum == ics_gamenum) {
3319                         if (gameInfo.variant == VariantNormal) {
3320                           /* [HGM] We seem to switch variant during a game!
3321                            * Presumably no holdings were displayed, so we have
3322                            * to move the position two files to the right to
3323                            * create room for them!
3324                            */
3325                           VariantClass newVariant;
3326                           switch(gameInfo.boardWidth) { // base guess on board width
3327                                 case 9:  newVariant = VariantShogi; break;
3328                                 case 10: newVariant = VariantGreat; break;
3329                                 default: newVariant = VariantCrazyhouse; break;
3330                           }
3331                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3332                           /* Get a move list just to see the header, which
3333                              will tell us whether this is really bug or zh */
3334                           if (ics_getting_history == H_FALSE) {
3335                             ics_getting_history = H_REQUESTED;
3336                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3337                             SendToICS(str);
3338                           }
3339                         }
3340                         new_piece[0] = NULLCHAR;
3341                         sscanf(parse, "game %d white [%s black [%s <- %s",
3342                                &gamenum, white_holding, black_holding,
3343                                new_piece);
3344                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3345                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3346                         /* [HGM] copy holdings to board holdings area */
3347                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3348                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3349                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3350 #if ZIPPY
3351                         if (appData.zippyPlay && first.initDone) {
3352                             ZippyHoldings(white_holding, black_holding,
3353                                           new_piece);
3354                         }
3355 #endif /*ZIPPY*/
3356                         if (tinyLayout || smallLayout) {
3357                             char wh[16], bh[16];
3358                             PackHolding(wh, white_holding);
3359                             PackHolding(bh, black_holding);
3360                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3361                                     gameInfo.white, gameInfo.black);
3362                         } else {
3363                             sprintf(str, "%s [%s] vs. %s [%s]",
3364                                     gameInfo.white, white_holding,
3365                                     gameInfo.black, black_holding);
3366                         }
3367
3368                         DrawPosition(FALSE, boards[currentMove]);
3369                         DisplayTitle(str);
3370                     }
3371                     /* Suppress following prompt */
3372                     if (looking_at(buf, &i, "*% ")) {
3373                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3374                         savingComment = FALSE;
3375                         suppressKibitz = 0;
3376                     }
3377                     next_out = i;
3378                 }
3379                 continue;
3380             }
3381
3382             i++;                /* skip unparsed character and loop back */
3383         }
3384         
3385         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3386 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3387 //          SendToPlayer(&buf[next_out], i - next_out);
3388             started != STARTED_HOLDINGS && leftover_start > next_out) {
3389             SendToPlayer(&buf[next_out], leftover_start - next_out);
3390             next_out = i;
3391         }
3392         
3393         leftover_len = buf_len - leftover_start;
3394         /* if buffer ends with something we couldn't parse,
3395            reparse it after appending the next read */
3396         
3397     } else if (count == 0) {
3398         RemoveInputSource(isr);
3399         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3400     } else {
3401         DisplayFatalError(_("Error reading from ICS"), error, 1);
3402     }
3403 }
3404
3405
3406 /* Board style 12 looks like this:
3407    
3408    <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
3409    
3410  * The "<12> " is stripped before it gets to this routine.  The two
3411  * trailing 0's (flip state and clock ticking) are later addition, and
3412  * some chess servers may not have them, or may have only the first.
3413  * Additional trailing fields may be added in the future.  
3414  */
3415
3416 #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"
3417
3418 #define RELATION_OBSERVING_PLAYED    0
3419 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3420 #define RELATION_PLAYING_MYMOVE      1
3421 #define RELATION_PLAYING_NOTMYMOVE  -1
3422 #define RELATION_EXAMINING           2
3423 #define RELATION_ISOLATED_BOARD     -3
3424 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3425
3426 void
3427 ParseBoard12(string)
3428      char *string;
3429
3430     GameMode newGameMode;
3431     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3432     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3433     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3434     char to_play, board_chars[200];
3435     char move_str[500], str[500], elapsed_time[500];
3436     char black[32], white[32];
3437     Board board;
3438     int prevMove = currentMove;
3439     int ticking = 2;
3440     ChessMove moveType;
3441     int fromX, fromY, toX, toY;
3442     char promoChar;
3443     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3444     char *bookHit = NULL; // [HGM] book
3445     Boolean weird = FALSE, reqFlag = FALSE;
3446
3447     fromX = fromY = toX = toY = -1;
3448     
3449     newGame = FALSE;
3450
3451     if (appData.debugMode)
3452       fprintf(debugFP, _("Parsing board: %s\n"), string);
3453
3454     move_str[0] = NULLCHAR;
3455     elapsed_time[0] = NULLCHAR;
3456     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3457         int  i = 0, j;
3458         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3459             if(string[i] == ' ') { ranks++; files = 0; }
3460             else files++;
3461             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3462             i++;
3463         }
3464         for(j = 0; j <i; j++) board_chars[j] = string[j];
3465         board_chars[i] = '\0';
3466         string += i + 1;
3467     }
3468     n = sscanf(string, PATTERN, &to_play, &double_push,
3469                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3470                &gamenum, white, black, &relation, &basetime, &increment,
3471                &white_stren, &black_stren, &white_time, &black_time,
3472                &moveNum, str, elapsed_time, move_str, &ics_flip,
3473                &ticking);
3474
3475     if (n < 21) {
3476         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3477         DisplayError(str, 0);
3478         return;
3479     }
3480
3481     /* Convert the move number to internal form */
3482     moveNum = (moveNum - 1) * 2;
3483     if (to_play == 'B') moveNum++;
3484     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3485       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3486                         0, 1);
3487       return;
3488     }
3489     
3490     switch (relation) {
3491       case RELATION_OBSERVING_PLAYED:
3492       case RELATION_OBSERVING_STATIC:
3493         if (gamenum == -1) {
3494             /* Old ICC buglet */
3495             relation = RELATION_OBSERVING_STATIC;
3496         }
3497         newGameMode = IcsObserving;
3498         break;
3499       case RELATION_PLAYING_MYMOVE:
3500       case RELATION_PLAYING_NOTMYMOVE:
3501         newGameMode =
3502           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3503             IcsPlayingWhite : IcsPlayingBlack;
3504         break;
3505       case RELATION_EXAMINING:
3506         newGameMode = IcsExamining;
3507         break;
3508       case RELATION_ISOLATED_BOARD:
3509       default:
3510         /* Just display this board.  If user was doing something else,
3511            we will forget about it until the next board comes. */ 
3512         newGameMode = IcsIdle;
3513         break;
3514       case RELATION_STARTING_POSITION:
3515         newGameMode = gameMode;
3516         break;
3517     }
3518     
3519     /* Modify behavior for initial board display on move listing
3520        of wild games.
3521        */
3522     switch (ics_getting_history) {
3523       case H_FALSE:
3524       case H_REQUESTED:
3525         break;
3526       case H_GOT_REQ_HEADER:
3527       case H_GOT_UNREQ_HEADER:
3528         /* This is the initial position of the current game */
3529         gamenum = ics_gamenum;
3530         moveNum = 0;            /* old ICS bug workaround */
3531         if (to_play == 'B') {
3532           startedFromSetupPosition = TRUE;
3533           blackPlaysFirst = TRUE;
3534           moveNum = 1;
3535           if (forwardMostMove == 0) forwardMostMove = 1;
3536           if (backwardMostMove == 0) backwardMostMove = 1;
3537           if (currentMove == 0) currentMove = 1;
3538         }
3539         newGameMode = gameMode;
3540         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3541         break;
3542       case H_GOT_UNWANTED_HEADER:
3543         /* This is an initial board that we don't want */
3544         return;
3545       case H_GETTING_MOVES:
3546         /* Should not happen */
3547         DisplayError(_("Error gathering move list: extra board"), 0);
3548         ics_getting_history = H_FALSE;
3549         return;
3550     }
3551
3552    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3553                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3554      /* [HGM] We seem to have switched variant unexpectedly
3555       * Try to guess new variant from board size
3556       */
3557           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3558           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3559           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3560           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3561           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3562           if(!weird) newVariant = VariantNormal;
3563           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3564           /* Get a move list just to see the header, which
3565              will tell us whether this is really bug or zh */
3566           if (ics_getting_history == H_FALSE) {
3567             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3568             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3569             SendToICS(str);
3570           }
3571     }
3572     
3573     /* Take action if this is the first board of a new game, or of a
3574        different game than is currently being displayed.  */
3575     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3576         relation == RELATION_ISOLATED_BOARD) {
3577         
3578         /* Forget the old game and get the history (if any) of the new one */
3579         if (gameMode != BeginningOfGame) {
3580           Reset(TRUE, TRUE);
3581         }
3582         newGame = TRUE;
3583         if (appData.autoRaiseBoard) BoardToTop();
3584         prevMove = -3;
3585         if (gamenum == -1) {
3586             newGameMode = IcsIdle;
3587         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3588                    appData.getMoveList && !reqFlag) {
3589             /* Need to get game history */
3590             ics_getting_history = H_REQUESTED;
3591             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3592             SendToICS(str);
3593         }
3594         
3595         /* Initially flip the board to have black on the bottom if playing
3596            black or if the ICS flip flag is set, but let the user change
3597            it with the Flip View button. */
3598         flipView = appData.autoFlipView ? 
3599           (newGameMode == IcsPlayingBlack) || ics_flip :
3600           appData.flipView;
3601         
3602         /* Done with values from previous mode; copy in new ones */
3603         gameMode = newGameMode;
3604         ModeHighlight();
3605         ics_gamenum = gamenum;
3606         if (gamenum == gs_gamenum) {
3607             int klen = strlen(gs_kind);
3608             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3609             sprintf(str, "ICS %s", gs_kind);
3610             gameInfo.event = StrSave(str);
3611         } else {
3612             gameInfo.event = StrSave("ICS game");
3613         }
3614         gameInfo.site = StrSave(appData.icsHost);
3615         gameInfo.date = PGNDate();
3616         gameInfo.round = StrSave("-");
3617         gameInfo.white = StrSave(white);
3618         gameInfo.black = StrSave(black);
3619         timeControl = basetime * 60 * 1000;
3620         timeControl_2 = 0;
3621         timeIncrement = increment * 1000;
3622         movesPerSession = 0;
3623         gameInfo.timeControl = TimeControlTagValue();
3624         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3625   if (appData.debugMode) {
3626     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3627     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3628     setbuf(debugFP, NULL);
3629   }
3630
3631         gameInfo.outOfBook = NULL;
3632         
3633         /* Do we have the ratings? */
3634         if (strcmp(player1Name, white) == 0 &&
3635             strcmp(player2Name, black) == 0) {
3636             if (appData.debugMode)
3637               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3638                       player1Rating, player2Rating);
3639             gameInfo.whiteRating = player1Rating;
3640             gameInfo.blackRating = player2Rating;
3641         } else if (strcmp(player2Name, white) == 0 &&
3642                    strcmp(player1Name, black) == 0) {
3643             if (appData.debugMode)
3644               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3645                       player2Rating, player1Rating);
3646             gameInfo.whiteRating = player2Rating;
3647             gameInfo.blackRating = player1Rating;
3648         }
3649         player1Name[0] = player2Name[0] = NULLCHAR;
3650
3651         /* Silence shouts if requested */
3652         if (appData.quietPlay &&
3653             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3654             SendToICS(ics_prefix);
3655             SendToICS("set shout 0\n");
3656         }
3657     }
3658     
3659     /* Deal with midgame name changes */
3660     if (!newGame) {
3661         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3662             if (gameInfo.white) free(gameInfo.white);
3663             gameInfo.white = StrSave(white);
3664         }
3665         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3666             if (gameInfo.black) free(gameInfo.black);
3667             gameInfo.black = StrSave(black);
3668         }
3669     }
3670     
3671     /* Throw away game result if anything actually changes in examine mode */
3672     if (gameMode == IcsExamining && !newGame) {
3673         gameInfo.result = GameUnfinished;
3674         if (gameInfo.resultDetails != NULL) {
3675             free(gameInfo.resultDetails);
3676             gameInfo.resultDetails = NULL;
3677         }
3678     }
3679     
3680     /* In pausing && IcsExamining mode, we ignore boards coming
3681        in if they are in a different variation than we are. */
3682     if (pauseExamInvalid) return;
3683     if (pausing && gameMode == IcsExamining) {
3684         if (moveNum <= pauseExamForwardMostMove) {
3685             pauseExamInvalid = TRUE;
3686             forwardMostMove = pauseExamForwardMostMove;
3687             return;
3688         }
3689     }
3690     
3691   if (appData.debugMode) {
3692     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3693   }
3694     /* Parse the board */
3695     for (k = 0; k < ranks; k++) {
3696       for (j = 0; j < files; j++)
3697         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3698       if(gameInfo.holdingsWidth > 1) {
3699            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3700            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3701       }
3702     }
3703     CopyBoard(boards[moveNum], board);
3704     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3705     if (moveNum == 0) {
3706         startedFromSetupPosition =
3707           !CompareBoards(board, initialPosition);
3708         if(startedFromSetupPosition)
3709             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3710     }
3711
3712     /* [HGM] Set castling rights. Take the outermost Rooks,
3713        to make it also work for FRC opening positions. Note that board12
3714        is really defective for later FRC positions, as it has no way to
3715        indicate which Rook can castle if they are on the same side of King.
3716        For the initial position we grant rights to the outermost Rooks,
3717        and remember thos rights, and we then copy them on positions
3718        later in an FRC game. This means WB might not recognize castlings with
3719        Rooks that have moved back to their original position as illegal,
3720        but in ICS mode that is not its job anyway.
3721     */
3722     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3723     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3724
3725         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3726             if(board[0][i] == WhiteRook) j = i;
3727         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3728         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3729             if(board[0][i] == WhiteRook) j = i;
3730         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3731         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3732             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3733         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3734         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3735             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3737
3738         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3739         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3740             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3741         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3742             if(board[BOARD_HEIGHT-1][k] == bKing)
3743                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3744         if(gameInfo.variant == VariantTwoKings) {
3745             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3746             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3747             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3748         }
3749     } else { int r;
3750         r = boards[moveNum][CASTLING][0] = initialRights[0];
3751         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3752         r = boards[moveNum][CASTLING][1] = initialRights[1];
3753         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3754         r = boards[moveNum][CASTLING][3] = initialRights[3];
3755         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3756         r = boards[moveNum][CASTLING][4] = initialRights[4];
3757         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3758         /* wildcastle kludge: always assume King has rights */
3759         r = boards[moveNum][CASTLING][2] = initialRights[2];
3760         r = boards[moveNum][CASTLING][5] = initialRights[5];
3761     }
3762     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3763     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3764
3765     
3766     if (ics_getting_history == H_GOT_REQ_HEADER ||
3767         ics_getting_history == H_GOT_UNREQ_HEADER) {
3768         /* This was an initial position from a move list, not
3769            the current position */
3770         return;
3771     }
3772     
3773     /* Update currentMove and known move number limits */
3774     newMove = newGame || moveNum > forwardMostMove;
3775
3776     if (newGame) {
3777         forwardMostMove = backwardMostMove = currentMove = moveNum;
3778         if (gameMode == IcsExamining && moveNum == 0) {
3779           /* Workaround for ICS limitation: we are not told the wild
3780              type when starting to examine a game.  But if we ask for
3781              the move list, the move list header will tell us */
3782             ics_getting_history = H_REQUESTED;
3783             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3784             SendToICS(str);
3785         }
3786     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3787                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3788 #if ZIPPY
3789         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3790         /* [HGM] applied this also to an engine that is silently watching        */
3791         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3792             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3793             gameInfo.variant == currentlyInitializedVariant) {
3794           takeback = forwardMostMove - moveNum;
3795           for (i = 0; i < takeback; i++) {
3796             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3797             SendToProgram("undo\n", &first);
3798           }
3799         }
3800 #endif
3801
3802         forwardMostMove = moveNum;
3803         if (!pausing || currentMove > forwardMostMove)
3804           currentMove = forwardMostMove;
3805     } else {
3806         /* New part of history that is not contiguous with old part */ 
3807         if (pausing && gameMode == IcsExamining) {
3808             pauseExamInvalid = TRUE;
3809             forwardMostMove = pauseExamForwardMostMove;
3810             return;
3811         }
3812         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3813 #if ZIPPY
3814             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3815                 // [HGM] when we will receive the move list we now request, it will be
3816                 // fed to the engine from the first move on. So if the engine is not
3817                 // in the initial position now, bring it there.
3818                 InitChessProgram(&first, 0);
3819             }
3820 #endif
3821             ics_getting_history = H_REQUESTED;
3822             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3823             SendToICS(str);
3824         }
3825         forwardMostMove = backwardMostMove = currentMove = moveNum;
3826     }
3827     
3828     /* Update the clocks */
3829     if (strchr(elapsed_time, '.')) {
3830       /* Time is in ms */
3831       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3832       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3833     } else {
3834       /* Time is in seconds */
3835       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3836       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3837     }
3838       
3839
3840 #if ZIPPY
3841     if (appData.zippyPlay && newGame &&
3842         gameMode != IcsObserving && gameMode != IcsIdle &&
3843         gameMode != IcsExamining)
3844       ZippyFirstBoard(moveNum, basetime, increment);
3845 #endif
3846     
3847     /* Put the move on the move list, first converting
3848        to canonical algebraic form. */
3849     if (moveNum > 0) {
3850   if (appData.debugMode) {
3851     if (appData.debugMode) { int f = forwardMostMove;
3852         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3853                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3854                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3855     }
3856     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3857     fprintf(debugFP, "moveNum = %d\n", moveNum);
3858     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3859     setbuf(debugFP, NULL);
3860   }
3861         if (moveNum <= backwardMostMove) {
3862             /* We don't know what the board looked like before
3863                this move.  Punt. */
3864             strcpy(parseList[moveNum - 1], move_str);
3865             strcat(parseList[moveNum - 1], " ");
3866             strcat(parseList[moveNum - 1], elapsed_time);
3867             moveList[moveNum - 1][0] = NULLCHAR;
3868         } else if (strcmp(move_str, "none") == 0) {
3869             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3870             /* Again, we don't know what the board looked like;
3871                this is really the start of the game. */
3872             parseList[moveNum - 1][0] = NULLCHAR;
3873             moveList[moveNum - 1][0] = NULLCHAR;
3874             backwardMostMove = moveNum;
3875             startedFromSetupPosition = TRUE;
3876             fromX = fromY = toX = toY = -1;
3877         } else {
3878           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3879           //                 So we parse the long-algebraic move string in stead of the SAN move
3880           int valid; char buf[MSG_SIZ], *prom;
3881
3882           // str looks something like "Q/a1-a2"; kill the slash
3883           if(str[1] == '/') 
3884                 sprintf(buf, "%c%s", str[0], str+2);
3885           else  strcpy(buf, str); // might be castling
3886           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3887                 strcat(buf, prom); // long move lacks promo specification!
3888           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3889                 if(appData.debugMode) 
3890                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3891                 strcpy(move_str, buf);
3892           }
3893           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3894                                 &fromX, &fromY, &toX, &toY, &promoChar)
3895                || ParseOneMove(buf, moveNum - 1, &moveType,
3896                                 &fromX, &fromY, &toX, &toY, &promoChar);
3897           // end of long SAN patch
3898           if (valid) {
3899             (void) CoordsToAlgebraic(boards[moveNum - 1],
3900                                      PosFlags(moveNum - 1),
3901                                      fromY, fromX, toY, toX, promoChar,
3902                                      parseList[moveNum-1]);
3903             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3904               case MT_NONE:
3905               case MT_STALEMATE:
3906               default:
3907                 break;
3908               case MT_CHECK:
3909                 if(gameInfo.variant != VariantShogi)
3910                     strcat(parseList[moveNum - 1], "+");
3911                 break;
3912               case MT_CHECKMATE:
3913               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3914                 strcat(parseList[moveNum - 1], "#");
3915                 break;
3916             }
3917             strcat(parseList[moveNum - 1], " ");
3918             strcat(parseList[moveNum - 1], elapsed_time);
3919             /* currentMoveString is set as a side-effect of ParseOneMove */
3920             strcpy(moveList[moveNum - 1], currentMoveString);
3921             strcat(moveList[moveNum - 1], "\n");
3922           } else {
3923             /* Move from ICS was illegal!?  Punt. */
3924   if (appData.debugMode) {
3925     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3926     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3927   }
3928             strcpy(parseList[moveNum - 1], move_str);
3929             strcat(parseList[moveNum - 1], " ");
3930             strcat(parseList[moveNum - 1], elapsed_time);
3931             moveList[moveNum - 1][0] = NULLCHAR;
3932             fromX = fromY = toX = toY = -1;
3933           }
3934         }
3935   if (appData.debugMode) {
3936     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3937     setbuf(debugFP, NULL);
3938   }
3939
3940 #if ZIPPY
3941         /* Send move to chess program (BEFORE animating it). */
3942         if (appData.zippyPlay && !newGame && newMove && 
3943            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3944
3945             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3946                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3947                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3948                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3949                             move_str);
3950                     DisplayError(str, 0);
3951                 } else {
3952                     if (first.sendTime) {
3953                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3954                     }
3955                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3956                     if (firstMove && !bookHit) {
3957                         firstMove = FALSE;
3958                         if (first.useColors) {
3959                           SendToProgram(gameMode == IcsPlayingWhite ?
3960                                         "white\ngo\n" :
3961                                         "black\ngo\n", &first);
3962                         } else {
3963                           SendToProgram("go\n", &first);
3964                         }
3965                         first.maybeThinking = TRUE;
3966                     }
3967                 }
3968             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3969               if (moveList[moveNum - 1][0] == NULLCHAR) {
3970                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3971                 DisplayError(str, 0);
3972               } else {
3973                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3974                 SendMoveToProgram(moveNum - 1, &first);
3975               }
3976             }
3977         }
3978 #endif
3979     }
3980
3981     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3982         /* If move comes from a remote source, animate it.  If it
3983            isn't remote, it will have already been animated. */
3984         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3985             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3986         }
3987         if (!pausing && appData.highlightLastMove) {
3988             SetHighlights(fromX, fromY, toX, toY);
3989         }
3990     }
3991     
3992     /* Start the clocks */
3993     whiteFlag = blackFlag = FALSE;
3994     appData.clockMode = !(basetime == 0 && increment == 0);
3995     if (ticking == 0) {
3996       ics_clock_paused = TRUE;
3997       StopClocks();
3998     } else if (ticking == 1) {
3999       ics_clock_paused = FALSE;
4000     }
4001     if (gameMode == IcsIdle ||
4002         relation == RELATION_OBSERVING_STATIC ||
4003         relation == RELATION_EXAMINING ||
4004         ics_clock_paused)
4005       DisplayBothClocks();
4006     else
4007       StartClocks();
4008     
4009     /* Display opponents and material strengths */
4010     if (gameInfo.variant != VariantBughouse &&
4011         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4012         if (tinyLayout || smallLayout) {
4013             if(gameInfo.variant == VariantNormal)
4014                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4015                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4016                     basetime, increment);
4017             else
4018                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4019                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4020                     basetime, increment, (int) gameInfo.variant);
4021         } else {
4022             if(gameInfo.variant == VariantNormal)
4023                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4024                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4025                     basetime, increment);
4026             else
4027                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4028                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4029                     basetime, increment, VariantName(gameInfo.variant));
4030         }
4031         DisplayTitle(str);
4032   if (appData.debugMode) {
4033     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4034   }
4035     }
4036
4037    
4038     /* Display the board */
4039     if (!pausing && !appData.noGUI) {
4040       
4041       if (appData.premove)
4042           if (!gotPremove || 
4043              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4044              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4045               ClearPremoveHighlights();
4046
4047       DrawPosition(FALSE, boards[currentMove]);
4048       DisplayMove(moveNum - 1);
4049       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4050             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4051               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4052         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4053       }
4054     }
4055
4056     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4057 #if ZIPPY
4058     if(bookHit) { // [HGM] book: simulate book reply
4059         static char bookMove[MSG_SIZ]; // a bit generous?
4060
4061         programStats.nodes = programStats.depth = programStats.time = 
4062         programStats.score = programStats.got_only_move = 0;
4063         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4064
4065         strcpy(bookMove, "move ");
4066         strcat(bookMove, bookHit);
4067         HandleMachineMove(bookMove, &first);
4068     }
4069 #endif
4070 }
4071
4072 void
4073 GetMoveListEvent()
4074 {
4075     char buf[MSG_SIZ];
4076     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4077         ics_getting_history = H_REQUESTED;
4078         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4079         SendToICS(buf);
4080     }
4081 }
4082
4083 void
4084 AnalysisPeriodicEvent(force)
4085      int force;
4086 {
4087     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4088          && !force) || !appData.periodicUpdates)
4089       return;
4090
4091     /* Send . command to Crafty to collect stats */
4092     SendToProgram(".\n", &first);
4093
4094     /* Don't send another until we get a response (this makes
4095        us stop sending to old Crafty's which don't understand
4096        the "." command (sending illegal cmds resets node count & time,
4097        which looks bad)) */
4098     programStats.ok_to_send = 0;
4099 }
4100
4101 void ics_update_width(new_width)
4102         int new_width;
4103 {
4104         ics_printf("set width %d\n", new_width);
4105 }
4106
4107 void
4108 SendMoveToProgram(moveNum, cps)
4109      int moveNum;
4110      ChessProgramState *cps;
4111 {
4112     char buf[MSG_SIZ];
4113
4114     if (cps->useUsermove) {
4115       SendToProgram("usermove ", cps);
4116     }
4117     if (cps->useSAN) {
4118       char *space;
4119       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4120         int len = space - parseList[moveNum];
4121         memcpy(buf, parseList[moveNum], len);
4122         buf[len++] = '\n';
4123         buf[len] = NULLCHAR;
4124       } else {
4125         sprintf(buf, "%s\n", parseList[moveNum]);
4126       }
4127       SendToProgram(buf, cps);
4128     } else {
4129       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4130         AlphaRank(moveList[moveNum], 4);
4131         SendToProgram(moveList[moveNum], cps);
4132         AlphaRank(moveList[moveNum], 4); // and back
4133       } else
4134       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4135        * the engine. It would be nice to have a better way to identify castle 
4136        * moves here. */
4137       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4138                                                                          && cps->useOOCastle) {
4139         int fromX = moveList[moveNum][0] - AAA; 
4140         int fromY = moveList[moveNum][1] - ONE;
4141         int toX = moveList[moveNum][2] - AAA; 
4142         int toY = moveList[moveNum][3] - ONE;
4143         if((boards[moveNum][fromY][fromX] == WhiteKing 
4144             && boards[moveNum][toY][toX] == WhiteRook)
4145            || (boards[moveNum][fromY][fromX] == BlackKing 
4146                && boards[moveNum][toY][toX] == BlackRook)) {
4147           if(toX > fromX) SendToProgram("O-O\n", cps);
4148           else SendToProgram("O-O-O\n", cps);
4149         }
4150         else SendToProgram(moveList[moveNum], cps);
4151       }
4152       else SendToProgram(moveList[moveNum], cps);
4153       /* End of additions by Tord */
4154     }
4155
4156     /* [HGM] setting up the opening has brought engine in force mode! */
4157     /*       Send 'go' if we are in a mode where machine should play. */
4158     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4159         (gameMode == TwoMachinesPlay   ||
4160 #ifdef ZIPPY
4161          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4162 #endif
4163          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4164         SendToProgram("go\n", cps);
4165   if (appData.debugMode) {
4166     fprintf(debugFP, "(extra)\n");
4167   }
4168     }
4169     setboardSpoiledMachineBlack = 0;
4170 }
4171
4172 void
4173 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4174      ChessMove moveType;
4175      int fromX, fromY, toX, toY;
4176 {
4177     char user_move[MSG_SIZ];
4178
4179     switch (moveType) {
4180       default:
4181         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4182                 (int)moveType, fromX, fromY, toX, toY);
4183         DisplayError(user_move + strlen("say "), 0);
4184         break;
4185       case WhiteKingSideCastle:
4186       case BlackKingSideCastle:
4187       case WhiteQueenSideCastleWild:
4188       case BlackQueenSideCastleWild:
4189       /* PUSH Fabien */
4190       case WhiteHSideCastleFR:
4191       case BlackHSideCastleFR:
4192       /* POP Fabien */
4193         sprintf(user_move, "o-o\n");
4194         break;
4195       case WhiteQueenSideCastle:
4196       case BlackQueenSideCastle:
4197       case WhiteKingSideCastleWild:
4198       case BlackKingSideCastleWild:
4199       /* PUSH Fabien */
4200       case WhiteASideCastleFR:
4201       case BlackASideCastleFR:
4202       /* POP Fabien */
4203         sprintf(user_move, "o-o-o\n");
4204         break;
4205       case WhitePromotionQueen:
4206       case BlackPromotionQueen:
4207       case WhitePromotionRook:
4208       case BlackPromotionRook:
4209       case WhitePromotionBishop:
4210       case BlackPromotionBishop:
4211       case WhitePromotionKnight:
4212       case BlackPromotionKnight:
4213       case WhitePromotionKing:
4214       case BlackPromotionKing:
4215       case WhitePromotionChancellor:
4216       case BlackPromotionChancellor:
4217       case WhitePromotionArchbishop:
4218       case BlackPromotionArchbishop:
4219         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4220             sprintf(user_move, "%c%c%c%c=%c\n",
4221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4222                 PieceToChar(WhiteFerz));
4223         else if(gameInfo.variant == VariantGreat)
4224             sprintf(user_move, "%c%c%c%c=%c\n",
4225                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4226                 PieceToChar(WhiteMan));
4227         else
4228             sprintf(user_move, "%c%c%c%c=%c\n",
4229                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4230                 PieceToChar(PromoPiece(moveType)));
4231         break;
4232       case WhiteDrop:
4233       case BlackDrop:
4234         sprintf(user_move, "%c@%c%c\n",
4235                 ToUpper(PieceToChar((ChessSquare) fromX)),
4236                 AAA + toX, ONE + toY);
4237         break;
4238       case NormalMove:
4239       case WhiteCapturesEnPassant:
4240       case BlackCapturesEnPassant:
4241       case IllegalMove:  /* could be a variant we don't quite understand */
4242         sprintf(user_move, "%c%c%c%c\n",
4243                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4244         break;
4245     }
4246     SendToICS(user_move);
4247     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4248         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4249 }
4250
4251 void
4252 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4253      int rf, ff, rt, ft;
4254      char promoChar;
4255      char move[7];
4256 {
4257     if (rf == DROP_RANK) {
4258         sprintf(move, "%c@%c%c\n",
4259                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4260     } else {
4261         if (promoChar == 'x' || promoChar == NULLCHAR) {
4262             sprintf(move, "%c%c%c%c\n",
4263                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4264         } else {
4265             sprintf(move, "%c%c%c%c%c\n",
4266                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4267         }
4268     }
4269 }
4270
4271 void
4272 ProcessICSInitScript(f)
4273      FILE *f;
4274 {
4275     char buf[MSG_SIZ];
4276
4277     while (fgets(buf, MSG_SIZ, f)) {
4278         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4279     }
4280
4281     fclose(f);
4282 }
4283
4284
4285 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4286 void
4287 AlphaRank(char *move, int n)
4288 {
4289 //    char *p = move, c; int x, y;
4290
4291     if (appData.debugMode) {
4292         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4293     }
4294
4295     if(move[1]=='*' && 
4296        move[2]>='0' && move[2]<='9' &&
4297        move[3]>='a' && move[3]<='x'    ) {
4298         move[1] = '@';
4299         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4300         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4301     } else
4302     if(move[0]>='0' && move[0]<='9' &&
4303        move[1]>='a' && move[1]<='x' &&
4304        move[2]>='0' && move[2]<='9' &&
4305        move[3]>='a' && move[3]<='x'    ) {
4306         /* input move, Shogi -> normal */
4307         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4308         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4309         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4310         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4311     } else
4312     if(move[1]=='@' &&
4313        move[3]>='0' && move[3]<='9' &&
4314        move[2]>='a' && move[2]<='x'    ) {
4315         move[1] = '*';
4316         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4317         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4318     } else
4319     if(
4320        move[0]>='a' && move[0]<='x' &&
4321        move[3]>='0' && move[3]<='9' &&
4322        move[2]>='a' && move[2]<='x'    ) {
4323          /* output move, normal -> Shogi */
4324         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4325         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4326         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4327         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4328         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4329     }
4330     if (appData.debugMode) {
4331         fprintf(debugFP, "   out = '%s'\n", move);
4332     }
4333 }
4334
4335 /* Parser for moves from gnuchess, ICS, or user typein box */
4336 Boolean
4337 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4338      char *move;
4339      int moveNum;
4340      ChessMove *moveType;
4341      int *fromX, *fromY, *toX, *toY;
4342      char *promoChar;
4343 {       
4344     if (appData.debugMode) {
4345         fprintf(debugFP, "move to parse: %s\n", move);
4346     }
4347     *moveType = yylexstr(moveNum, move);
4348
4349     switch (*moveType) {
4350       case WhitePromotionChancellor:
4351       case BlackPromotionChancellor:
4352       case WhitePromotionArchbishop:
4353       case BlackPromotionArchbishop:
4354       case WhitePromotionQueen:
4355       case BlackPromotionQueen:
4356       case WhitePromotionRook:
4357       case BlackPromotionRook:
4358       case WhitePromotionBishop:
4359       case BlackPromotionBishop:
4360       case WhitePromotionKnight:
4361       case BlackPromotionKnight:
4362       case WhitePromotionKing:
4363       case BlackPromotionKing:
4364       case NormalMove:
4365       case WhiteCapturesEnPassant:
4366       case BlackCapturesEnPassant:
4367       case WhiteKingSideCastle:
4368       case WhiteQueenSideCastle:
4369       case BlackKingSideCastle:
4370       case BlackQueenSideCastle:
4371       case WhiteKingSideCastleWild:
4372       case WhiteQueenSideCastleWild:
4373       case BlackKingSideCastleWild:
4374       case BlackQueenSideCastleWild:
4375       /* Code added by Tord: */
4376       case WhiteHSideCastleFR:
4377       case WhiteASideCastleFR:
4378       case BlackHSideCastleFR:
4379       case BlackASideCastleFR:
4380       /* End of code added by Tord */
4381       case IllegalMove:         /* bug or odd chess variant */
4382         *fromX = currentMoveString[0] - AAA;
4383         *fromY = currentMoveString[1] - ONE;
4384         *toX = currentMoveString[2] - AAA;
4385         *toY = currentMoveString[3] - ONE;
4386         *promoChar = currentMoveString[4];
4387         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4388             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4389     if (appData.debugMode) {
4390         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4391     }
4392             *fromX = *fromY = *toX = *toY = 0;
4393             return FALSE;
4394         }
4395         if (appData.testLegality) {
4396           return (*moveType != IllegalMove);
4397         } else {
4398           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4399                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4400         }
4401
4402       case WhiteDrop:
4403       case BlackDrop:
4404         *fromX = *moveType == WhiteDrop ?
4405           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4406           (int) CharToPiece(ToLower(currentMoveString[0]));
4407         *fromY = DROP_RANK;
4408         *toX = currentMoveString[2] - AAA;
4409         *toY = currentMoveString[3] - ONE;
4410         *promoChar = NULLCHAR;
4411         return TRUE;
4412
4413       case AmbiguousMove:
4414       case ImpossibleMove:
4415       case (ChessMove) 0:       /* end of file */
4416       case ElapsedTime:
4417       case Comment:
4418       case PGNTag:
4419       case NAG:
4420       case WhiteWins:
4421       case BlackWins:
4422       case GameIsDrawn:
4423       default:
4424     if (appData.debugMode) {
4425         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4426     }
4427         /* bug? */
4428         *fromX = *fromY = *toX = *toY = 0;
4429         *promoChar = NULLCHAR;
4430         return FALSE;
4431     }
4432 }
4433
4434
4435 void
4436 ParsePV(char *pv)
4437 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4438   int fromX, fromY, toX, toY; char promoChar;
4439   ChessMove moveType;
4440   Boolean valid;
4441   int nr = 0;
4442
4443   endPV = forwardMostMove;
4444   do {
4445     while(*pv == ' ') pv++;
4446     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4447     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4448 if(appData.debugMode){
4449 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4450 }
4451     if(!valid && nr == 0 &&
4452        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4453         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4454     }
4455     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4456     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4457     nr++;
4458     if(endPV+1 > framePtr) break; // no space, truncate
4459     if(!valid) break;
4460     endPV++;
4461     CopyBoard(boards[endPV], boards[endPV-1]);
4462     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4463     moveList[endPV-1][0] = fromX + AAA;
4464     moveList[endPV-1][1] = fromY + ONE;
4465     moveList[endPV-1][2] = toX + AAA;
4466     moveList[endPV-1][3] = toY + ONE;
4467     parseList[endPV-1][0] = NULLCHAR;
4468   } while(valid);
4469   currentMove = endPV;
4470   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4471   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4472                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4473   DrawPosition(TRUE, boards[currentMove]);
4474 }
4475
4476 static int lastX, lastY;
4477
4478 Boolean
4479 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4480 {
4481         int startPV;
4482
4483         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4484         lastX = x; lastY = y;
4485         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4486         startPV = index;
4487       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4488       index = startPV;
4489         while(buf[index] && buf[index] != '\n') index++;
4490         buf[index] = 0;
4491         ParsePV(buf+startPV);
4492         *start = startPV; *end = index-1;
4493         return TRUE;
4494 }
4495
4496 Boolean
4497 LoadPV(int x, int y)
4498 { // called on right mouse click to load PV
4499   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4500   lastX = x; lastY = y;
4501   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4502   return TRUE;
4503 }
4504
4505 void
4506 UnLoadPV()
4507 {
4508   if(endPV < 0) return;
4509   endPV = -1;
4510   currentMove = forwardMostMove;
4511   ClearPremoveHighlights();
4512   DrawPosition(TRUE, boards[currentMove]);
4513 }
4514
4515 void
4516 MovePV(int x, int y, int h)
4517 { // step through PV based on mouse coordinates (called on mouse move)
4518   int margin = h>>3, step = 0;
4519
4520   if(endPV < 0) return;
4521   // we must somehow check if right button is still down (might be released off board!)
4522   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4523   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4524   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4525   if(!step) return;
4526   lastX = x; lastY = y;
4527   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4528   currentMove += step;
4529   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4530   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4531                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4532   DrawPosition(FALSE, boards[currentMove]);
4533 }
4534
4535
4536 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4537 // All positions will have equal probability, but the current method will not provide a unique
4538 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4539 #define DARK 1
4540 #define LITE 2
4541 #define ANY 3
4542
4543 int squaresLeft[4];
4544 int piecesLeft[(int)BlackPawn];
4545 int seed, nrOfShuffles;
4546
4547 void GetPositionNumber()
4548 {       // sets global variable seed
4549         int i;
4550
4551         seed = appData.defaultFrcPosition;
4552         if(seed < 0) { // randomize based on time for negative FRC position numbers
4553                 for(i=0; i<50; i++) seed += random();
4554                 seed = random() ^ random() >> 8 ^ random() << 8;
4555                 if(seed<0) seed = -seed;
4556         }
4557 }
4558
4559 int put(Board board, int pieceType, int rank, int n, int shade)
4560 // put the piece on the (n-1)-th empty squares of the given shade
4561 {
4562         int i;
4563
4564         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4565                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4566                         board[rank][i] = (ChessSquare) pieceType;
4567                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4568                         squaresLeft[ANY]--;
4569                         piecesLeft[pieceType]--; 
4570                         return i;
4571                 }
4572         }
4573         return -1;
4574 }
4575
4576
4577 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4578 // calculate where the next piece goes, (any empty square), and put it there
4579 {
4580         int i;
4581
4582         i = seed % squaresLeft[shade];
4583         nrOfShuffles *= squaresLeft[shade];
4584         seed /= squaresLeft[shade];
4585         put(board, pieceType, rank, i, shade);
4586 }
4587
4588 void AddTwoPieces(Board board, int pieceType, int rank)
4589 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4590 {
4591         int i, n=squaresLeft[ANY], j=n-1, k;
4592
4593         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4594         i = seed % k;  // pick one
4595         nrOfShuffles *= k;
4596         seed /= k;
4597         while(i >= j) i -= j--;
4598         j = n - 1 - j; i += j;
4599         put(board, pieceType, rank, j, ANY);
4600         put(board, pieceType, rank, i, ANY);
4601 }
4602
4603 void SetUpShuffle(Board board, int number)
4604 {
4605         int i, p, first=1;
4606
4607         GetPositionNumber(); nrOfShuffles = 1;
4608
4609         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4610         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4611         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4612
4613         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4614
4615         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4616             p = (int) board[0][i];
4617             if(p < (int) BlackPawn) piecesLeft[p] ++;
4618             board[0][i] = EmptySquare;
4619         }
4620
4621         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4622             // shuffles restricted to allow normal castling put KRR first
4623             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4624                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4625             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4626                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4627             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4628                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4629             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4630                 put(board, WhiteRook, 0, 0, ANY);
4631             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4632         }
4633
4634         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4635             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4636             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4637                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4638                 while(piecesLeft[p] >= 2) {
4639                     AddOnePiece(board, p, 0, LITE);
4640                     AddOnePiece(board, p, 0, DARK);
4641                 }
4642                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4643             }
4644
4645         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4646             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4647             // but we leave King and Rooks for last, to possibly obey FRC restriction
4648             if(p == (int)WhiteRook) continue;
4649             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4650             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4651         }
4652
4653         // now everything is placed, except perhaps King (Unicorn) and Rooks
4654
4655         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4656             // Last King gets castling rights
4657             while(piecesLeft[(int)WhiteUnicorn]) {
4658                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4659                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4660             }
4661
4662             while(piecesLeft[(int)WhiteKing]) {
4663                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4664                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4665             }
4666
4667
4668         } else {
4669             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4670             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4671         }
4672
4673         // Only Rooks can be left; simply place them all
4674         while(piecesLeft[(int)WhiteRook]) {
4675                 i = put(board, WhiteRook, 0, 0, ANY);
4676                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4677                         if(first) {
4678                                 first=0;
4679                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4680                         }
4681                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4682                 }
4683         }
4684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4685             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4686         }
4687
4688         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4689 }
4690
4691 int SetCharTable( char *table, const char * map )
4692 /* [HGM] moved here from winboard.c because of its general usefulness */
4693 /*       Basically a safe strcpy that uses the last character as King */
4694 {
4695     int result = FALSE; int NrPieces;
4696
4697     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4698                     && NrPieces >= 12 && !(NrPieces&1)) {
4699         int i; /* [HGM] Accept even length from 12 to 34 */
4700
4701         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4702         for( i=0; i<NrPieces/2-1; i++ ) {
4703             table[i] = map[i];
4704             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4705         }
4706         table[(int) WhiteKing]  = map[NrPieces/2-1];
4707         table[(int) BlackKing]  = map[NrPieces-1];
4708
4709         result = TRUE;
4710     }
4711
4712     return result;
4713 }
4714
4715 void Prelude(Board board)
4716 {       // [HGM] superchess: random selection of exo-pieces
4717         int i, j, k; ChessSquare p; 
4718         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4719
4720         GetPositionNumber(); // use FRC position number
4721
4722         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4723             SetCharTable(pieceToChar, appData.pieceToCharTable);
4724             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4725                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4726         }
4727
4728         j = seed%4;                 seed /= 4; 
4729         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4730         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4731         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4732         j = seed%3 + (seed%3 >= j); seed /= 3; 
4733         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4734         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4735         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4736         j = seed%3;                 seed /= 3; 
4737         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4738         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4739         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4740         j = seed%2 + (seed%2 >= j); seed /= 2; 
4741         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4742         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4743         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4744         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4745         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4746         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4747         put(board, exoPieces[0],    0, 0, ANY);
4748         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4749 }
4750
4751 void
4752 InitPosition(redraw)
4753      int redraw;
4754 {
4755     ChessSquare (* pieces)[BOARD_FILES];
4756     int i, j, pawnRow, overrule,
4757     oldx = gameInfo.boardWidth,
4758     oldy = gameInfo.boardHeight,
4759     oldh = gameInfo.holdingsWidth,
4760     oldv = gameInfo.variant;
4761
4762     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4763
4764     /* [AS] Initialize pv info list [HGM] and game status */
4765     {
4766         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4767             pvInfoList[i].depth = 0;
4768             boards[i][EP_STATUS] = EP_NONE;
4769             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4770         }
4771
4772         initialRulePlies = 0; /* 50-move counter start */
4773
4774         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4775         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4776     }
4777
4778     
4779     /* [HGM] logic here is completely changed. In stead of full positions */
4780     /* the initialized data only consist of the two backranks. The switch */
4781     /* selects which one we will use, which is than copied to the Board   */
4782     /* initialPosition, which for the rest is initialized by Pawns and    */
4783     /* empty squares. This initial position is then copied to boards[0],  */
4784     /* possibly after shuffling, so that it remains available.            */
4785
4786     gameInfo.holdingsWidth = 0; /* default board sizes */
4787     gameInfo.boardWidth    = 8;
4788     gameInfo.boardHeight   = 8;
4789     gameInfo.holdingsSize  = 0;
4790     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4791     for(i=0; i<BOARD_FILES-2; i++)
4792       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4793     initialPosition[EP_STATUS] = EP_NONE;
4794     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4795
4796     switch (gameInfo.variant) {
4797     case VariantFischeRandom:
4798       shuffleOpenings = TRUE;
4799     default:
4800       pieces = FIDEArray;
4801       break;
4802     case VariantShatranj:
4803       pieces = ShatranjArray;
4804       nrCastlingRights = 0;
4805       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4806       break;
4807     case VariantMakruk:
4808       pieces = makrukArray;
4809       nrCastlingRights = 0;
4810       startedFromSetupPosition = TRUE;
4811       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4812       break;
4813     case VariantTwoKings:
4814       pieces = twoKingsArray;
4815       break;
4816     case VariantCapaRandom:
4817       shuffleOpenings = TRUE;
4818     case VariantCapablanca:
4819       pieces = CapablancaArray;
4820       gameInfo.boardWidth = 10;
4821       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4822       break;
4823     case VariantGothic:
4824       pieces = GothicArray;
4825       gameInfo.boardWidth = 10;
4826       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4827       break;
4828     case VariantJanus:
4829       pieces = JanusArray;
4830       gameInfo.boardWidth = 10;
4831       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4832       nrCastlingRights = 6;
4833         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4834         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4835         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4836         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4837         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4838         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4839       break;
4840     case VariantFalcon:
4841       pieces = FalconArray;
4842       gameInfo.boardWidth = 10;
4843       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4844       break;
4845     case VariantXiangqi:
4846       pieces = XiangqiArray;
4847       gameInfo.boardWidth  = 9;
4848       gameInfo.boardHeight = 10;
4849       nrCastlingRights = 0;
4850       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4851       break;
4852     case VariantShogi:
4853       pieces = ShogiArray;
4854       gameInfo.boardWidth  = 9;
4855       gameInfo.boardHeight = 9;
4856       gameInfo.holdingsSize = 7;
4857       nrCastlingRights = 0;
4858       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4859       break;
4860     case VariantCourier:
4861       pieces = CourierArray;
4862       gameInfo.boardWidth  = 12;
4863       nrCastlingRights = 0;
4864       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4865       break;
4866     case VariantKnightmate:
4867       pieces = KnightmateArray;
4868       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4869       break;
4870     case VariantFairy:
4871       pieces = fairyArray;
4872       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4873       break;
4874     case VariantGreat:
4875       pieces = GreatArray;
4876       gameInfo.boardWidth = 10;
4877       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4878       gameInfo.holdingsSize = 8;
4879       break;
4880     case VariantSuper:
4881       pieces = FIDEArray;
4882       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4883       gameInfo.holdingsSize = 8;
4884       startedFromSetupPosition = TRUE;
4885       break;
4886     case VariantCrazyhouse:
4887     case VariantBughouse:
4888       pieces = FIDEArray;
4889       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4890       gameInfo.holdingsSize = 5;
4891       break;
4892     case VariantWildCastle:
4893       pieces = FIDEArray;
4894       /* !!?shuffle with kings guaranteed to be on d or e file */
4895       shuffleOpenings = 1;
4896       break;
4897     case VariantNoCastle:
4898       pieces = FIDEArray;
4899       nrCastlingRights = 0;
4900       /* !!?unconstrained back-rank shuffle */
4901       shuffleOpenings = 1;
4902       break;
4903     }
4904
4905     overrule = 0;
4906     if(appData.NrFiles >= 0) {
4907         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4908         gameInfo.boardWidth = appData.NrFiles;
4909     }
4910     if(appData.NrRanks >= 0) {
4911         gameInfo.boardHeight = appData.NrRanks;
4912     }
4913     if(appData.holdingsSize >= 0) {
4914         i = appData.holdingsSize;
4915         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4916         gameInfo.holdingsSize = i;
4917     }
4918     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4919     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4920         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4921
4922     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4923     if(pawnRow < 1) pawnRow = 1;
4924     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4925
4926     /* User pieceToChar list overrules defaults */
4927     if(appData.pieceToCharTable != NULL)
4928         SetCharTable(pieceToChar, appData.pieceToCharTable);
4929
4930     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4931
4932         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4933             s = (ChessSquare) 0; /* account holding counts in guard band */
4934         for( i=0; i<BOARD_HEIGHT; i++ )
4935             initialPosition[i][j] = s;
4936
4937         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4938         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4939         initialPosition[pawnRow][j] = WhitePawn;
4940         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4941         if(gameInfo.variant == VariantXiangqi) {
4942             if(j&1) {
4943                 initialPosition[pawnRow][j] = 
4944                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4945                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4946                    initialPosition[2][j] = WhiteCannon;
4947                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4948                 }
4949             }
4950         }
4951         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4952     }
4953     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4954
4955             j=BOARD_LEFT+1;
4956             initialPosition[1][j] = WhiteBishop;
4957             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4958             j=BOARD_RGHT-2;
4959             initialPosition[1][j] = WhiteRook;
4960             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4961     }
4962
4963     if( nrCastlingRights == -1) {
4964         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4965         /*       This sets default castling rights from none to normal corners   */
4966         /* Variants with other castling rights must set them themselves above    */
4967         nrCastlingRights = 6;
4968        
4969         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4970         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4971         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4972         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4973         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4974         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4975      }
4976
4977      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4978      if(gameInfo.variant == VariantGreat) { // promotion commoners
4979         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4981         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4983      }
4984   if (appData.debugMode) {
4985     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4986   }
4987     if(shuffleOpenings) {
4988         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4989         startedFromSetupPosition = TRUE;
4990     }
4991     if(startedFromPositionFile) {
4992       /* [HGM] loadPos: use PositionFile for every new game */
4993       CopyBoard(initialPosition, filePosition);
4994       for(i=0; i<nrCastlingRights; i++)
4995           initialRights[i] = filePosition[CASTLING][i];
4996       startedFromSetupPosition = TRUE;
4997     }
4998
4999     CopyBoard(boards[0], initialPosition);
5000
5001     if(oldx != gameInfo.boardWidth ||
5002        oldy != gameInfo.boardHeight ||
5003        oldh != gameInfo.holdingsWidth
5004 #ifdef GOTHIC
5005        || oldv == VariantGothic ||        // For licensing popups
5006        gameInfo.variant == VariantGothic
5007 #endif
5008 #ifdef FALCON
5009        || oldv == VariantFalcon ||
5010        gameInfo.variant == VariantFalcon
5011 #endif
5012                                          )
5013             InitDrawingSizes(-2 ,0);
5014
5015     if (redraw)
5016       DrawPosition(TRUE, boards[currentMove]);
5017 }
5018
5019 void
5020 SendBoard(cps, moveNum)
5021      ChessProgramState *cps;
5022      int moveNum;
5023 {
5024     char message[MSG_SIZ];
5025     
5026     if (cps->useSetboard) {
5027       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5028       sprintf(message, "setboard %s\n", fen);
5029       SendToProgram(message, cps);
5030       free(fen);
5031
5032     } else {
5033       ChessSquare *bp;
5034       int i, j;
5035       /* Kludge to set black to move, avoiding the troublesome and now
5036        * deprecated "black" command.
5037        */
5038       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5039
5040       SendToProgram("edit\n", cps);
5041       SendToProgram("#\n", cps);
5042       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5043         bp = &boards[moveNum][i][BOARD_LEFT];
5044         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5045           if ((int) *bp < (int) BlackPawn) {
5046             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5047                     AAA + j, ONE + i);
5048             if(message[0] == '+' || message[0] == '~') {
5049                 sprintf(message, "%c%c%c+\n",
5050                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5051                         AAA + j, ONE + i);
5052             }
5053             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5054                 message[1] = BOARD_RGHT   - 1 - j + '1';
5055                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5056             }
5057             SendToProgram(message, cps);
5058           }
5059         }
5060       }
5061     
5062       SendToProgram("c\n", cps);
5063       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5064         bp = &boards[moveNum][i][BOARD_LEFT];
5065         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5066           if (((int) *bp != (int) EmptySquare)
5067               && ((int) *bp >= (int) BlackPawn)) {
5068             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5069                     AAA + j, ONE + i);
5070             if(message[0] == '+' || message[0] == '~') {
5071                 sprintf(message, "%c%c%c+\n",
5072                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5073                         AAA + j, ONE + i);
5074             }
5075             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5076                 message[1] = BOARD_RGHT   - 1 - j + '1';
5077                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5078             }
5079             SendToProgram(message, cps);
5080           }
5081         }
5082       }
5083     
5084       SendToProgram(".\n", cps);
5085     }
5086     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5087 }
5088
5089 int
5090 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5091 {
5092     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5093     /* [HGM] add Shogi promotions */
5094     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5095     ChessSquare piece;
5096     ChessMove moveType;
5097     Boolean premove;
5098
5099     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5100     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5101
5102     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5103       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5104         return FALSE;
5105
5106     piece = boards[currentMove][fromY][fromX];
5107     if(gameInfo.variant == VariantShogi) {
5108         promotionZoneSize = 3;
5109         highestPromotingPiece = (int)WhiteFerz;
5110     } else if(gameInfo.variant == VariantMakruk) {
5111         promotionZoneSize = 3;
5112     }
5113
5114     // next weed out all moves that do not touch the promotion zone at all
5115     if((int)piece >= BlackPawn) {
5116         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5117              return FALSE;
5118         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5119     } else {
5120         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5121            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5122     }
5123
5124     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5125
5126     // weed out mandatory Shogi promotions
5127     if(gameInfo.variant == VariantShogi) {
5128         if(piece >= BlackPawn) {
5129             if(toY == 0 && piece == BlackPawn ||
5130                toY == 0 && piece == BlackQueen ||
5131                toY <= 1 && piece == BlackKnight) {
5132                 *promoChoice = '+';
5133                 return FALSE;
5134             }
5135         } else {
5136             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5137                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5138                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5139                 *promoChoice = '+';
5140                 return FALSE;
5141             }
5142         }
5143     }
5144
5145     // weed out obviously illegal Pawn moves
5146     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5147         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5148         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5149         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5150         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5151         // note we are not allowed to test for valid (non-)capture, due to premove
5152     }
5153
5154     // we either have a choice what to promote to, or (in Shogi) whether to promote
5155     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5156         *promoChoice = PieceToChar(BlackFerz);  // no choice
5157         return FALSE;
5158     }
5159     if(appData.alwaysPromoteToQueen) { // predetermined
5160         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5161              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5162         else *promoChoice = PieceToChar(BlackQueen);
5163         return FALSE;
5164     }
5165
5166     // suppress promotion popup on illegal moves that are not premoves
5167     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5168               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5169     if(appData.testLegality && !premove) {
5170         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5171                         fromY, fromX, toY, toX, NULLCHAR);
5172         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5173            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5174             return FALSE;
5175     }
5176
5177     return TRUE;
5178 }
5179
5180 int
5181 InPalace(row, column)
5182      int row, column;
5183 {   /* [HGM] for Xiangqi */
5184     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5185          column < (BOARD_WIDTH + 4)/2 &&
5186          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5187     return FALSE;
5188 }
5189
5190 int
5191 PieceForSquare (x, y)
5192      int x;
5193      int y;
5194 {
5195   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5196      return -1;
5197   else
5198      return boards[currentMove][y][x];
5199 }
5200
5201 int
5202 OKToStartUserMove(x, y)
5203      int x, y;
5204 {
5205     ChessSquare from_piece;
5206     int white_piece;
5207
5208     if (matchMode) return FALSE;
5209     if (gameMode == EditPosition) return TRUE;
5210
5211     if (x >= 0 && y >= 0)
5212       from_piece = boards[currentMove][y][x];
5213     else
5214       from_piece = EmptySquare;
5215
5216     if (from_piece == EmptySquare) return FALSE;
5217
5218     white_piece = (int)from_piece >= (int)WhitePawn &&
5219       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5220
5221     switch (gameMode) {
5222       case PlayFromGameFile:
5223       case AnalyzeFile:
5224       case TwoMachinesPlay:
5225       case EndOfGame:
5226         return FALSE;
5227
5228       case IcsObserving:
5229       case IcsIdle:
5230         return FALSE;
5231
5232       case MachinePlaysWhite:
5233       case IcsPlayingBlack:
5234         if (appData.zippyPlay) return FALSE;
5235         if (white_piece) {
5236             DisplayMoveError(_("You are playing Black"));
5237             return FALSE;
5238         }
5239         break;
5240
5241       case MachinePlaysBlack:
5242       case IcsPlayingWhite:
5243         if (appData.zippyPlay) return FALSE;
5244         if (!white_piece) {
5245             DisplayMoveError(_("You are playing White"));
5246             return FALSE;
5247         }
5248         break;
5249
5250       case EditGame:
5251         if (!white_piece && WhiteOnMove(currentMove)) {
5252             DisplayMoveError(_("It is White's turn"));
5253             return FALSE;
5254         }           
5255         if (white_piece && !WhiteOnMove(currentMove)) {
5256             DisplayMoveError(_("It is Black's turn"));
5257             return FALSE;
5258         }           
5259         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5260             /* Editing correspondence game history */
5261             /* Could disallow this or prompt for confirmation */
5262             cmailOldMove = -1;
5263         }
5264         break;
5265
5266       case BeginningOfGame:
5267         if (appData.icsActive) return FALSE;
5268         if (!appData.noChessProgram) {
5269             if (!white_piece) {
5270                 DisplayMoveError(_("You are playing White"));
5271                 return FALSE;
5272             }
5273         }
5274         break;
5275         
5276       case Training:
5277         if (!white_piece && WhiteOnMove(currentMove)) {
5278             DisplayMoveError(_("It is White's turn"));
5279             return FALSE;
5280         }           
5281         if (white_piece && !WhiteOnMove(currentMove)) {
5282             DisplayMoveError(_("It is Black's turn"));
5283             return FALSE;
5284         }           
5285         break;
5286
5287       default:
5288       case IcsExamining:
5289         break;
5290     }
5291     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5292         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5293         && gameMode != AnalyzeFile && gameMode != Training) {
5294         DisplayMoveError(_("Displayed position is not current"));
5295         return FALSE;
5296     }
5297     return TRUE;
5298 }
5299
5300 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5301 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5302 int lastLoadGameUseList = FALSE;
5303 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5304 ChessMove lastLoadGameStart = (ChessMove) 0;
5305
5306 ChessMove
5307 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5308      int fromX, fromY, toX, toY;
5309      int promoChar;
5310      Boolean captureOwn;
5311 {
5312     ChessMove moveType;
5313     ChessSquare pdown, pup;
5314
5315     /* Check if the user is playing in turn.  This is complicated because we
5316        let the user "pick up" a piece before it is his turn.  So the piece he
5317        tried to pick up may have been captured by the time he puts it down!
5318        Therefore we use the color the user is supposed to be playing in this
5319        test, not the color of the piece that is currently on the starting
5320        square---except in EditGame mode, where the user is playing both
5321        sides; fortunately there the capture race can't happen.  (It can
5322        now happen in IcsExamining mode, but that's just too bad.  The user
5323        will get a somewhat confusing message in that case.)
5324        */
5325
5326     switch (gameMode) {
5327       case PlayFromGameFile:
5328       case AnalyzeFile:
5329       case TwoMachinesPlay:
5330       case EndOfGame:
5331       case IcsObserving:
5332       case IcsIdle:
5333         /* We switched into a game mode where moves are not accepted,
5334            perhaps while the mouse button was down. */
5335         return ImpossibleMove;
5336
5337       case MachinePlaysWhite:
5338         /* User is moving for Black */
5339         if (WhiteOnMove(currentMove)) {
5340             DisplayMoveError(_("It is White's turn"));
5341             return ImpossibleMove;
5342         }
5343         break;
5344
5345       case MachinePlaysBlack:
5346         /* User is moving for White */
5347         if (!WhiteOnMove(currentMove)) {
5348             DisplayMoveError(_("It is Black's turn"));
5349             return ImpossibleMove;
5350         }
5351         break;
5352
5353       case EditGame:
5354       case IcsExamining:
5355       case BeginningOfGame:
5356       case AnalyzeMode:
5357       case Training:
5358         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5359             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5360             /* User is moving for Black */
5361             if (WhiteOnMove(currentMove)) {
5362                 DisplayMoveError(_("It is White's turn"));
5363                 return ImpossibleMove;
5364             }
5365         } else {
5366             /* User is moving for White */
5367             if (!WhiteOnMove(currentMove)) {
5368                 DisplayMoveError(_("It is Black's turn"));
5369                 return ImpossibleMove;
5370             }
5371         }
5372         break;
5373
5374       case IcsPlayingBlack:
5375         /* User is moving for Black */
5376         if (WhiteOnMove(currentMove)) {
5377             if (!appData.premove) {
5378                 DisplayMoveError(_("It is White's turn"));
5379             } else if (toX >= 0 && toY >= 0) {
5380                 premoveToX = toX;
5381                 premoveToY = toY;
5382                 premoveFromX = fromX;
5383                 premoveFromY = fromY;
5384                 premovePromoChar = promoChar;
5385                 gotPremove = 1;
5386                 if (appData.debugMode) 
5387                     fprintf(debugFP, "Got premove: fromX %d,"
5388                             "fromY %d, toX %d, toY %d\n",
5389                             fromX, fromY, toX, toY);
5390             }
5391             return ImpossibleMove;
5392         }
5393         break;
5394
5395       case IcsPlayingWhite:
5396         /* User is moving for White */
5397         if (!WhiteOnMove(currentMove)) {
5398             if (!appData.premove) {
5399                 DisplayMoveError(_("It is Black's turn"));
5400             } else if (toX >= 0 && toY >= 0) {
5401                 premoveToX = toX;
5402                 premoveToY = toY;
5403                 premoveFromX = fromX;
5404                 premoveFromY = fromY;
5405                 premovePromoChar = promoChar;
5406                 gotPremove = 1;
5407                 if (appData.debugMode) 
5408                     fprintf(debugFP, "Got premove: fromX %d,"
5409                             "fromY %d, toX %d, toY %d\n",
5410                             fromX, fromY, toX, toY);
5411             }
5412             return ImpossibleMove;
5413         }
5414         break;
5415
5416       default:
5417         break;
5418
5419       case EditPosition:
5420         /* EditPosition, empty square, or different color piece;
5421            click-click move is possible */
5422         if (toX == -2 || toY == -2) {
5423             boards[0][fromY][fromX] = EmptySquare;
5424             return AmbiguousMove;
5425         } else if (toX >= 0 && toY >= 0) {
5426             boards[0][toY][toX] = boards[0][fromY][fromX];
5427             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5428                 if(boards[0][fromY][0] != EmptySquare) {
5429                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5430                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5431                 }
5432             } else
5433             if(fromX == BOARD_RGHT+1) {
5434                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5435                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5436                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5437                 }
5438             } else
5439             boards[0][fromY][fromX] = EmptySquare;
5440             return AmbiguousMove;
5441         }
5442         return ImpossibleMove;
5443     }
5444
5445     if(toX < 0 || toY < 0) return ImpossibleMove;
5446     pdown = boards[currentMove][fromY][fromX];
5447     pup = boards[currentMove][toY][toX];
5448
5449     /* [HGM] If move started in holdings, it means a drop */
5450     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5451          if( pup != EmptySquare ) return ImpossibleMove;
5452          if(appData.testLegality) {
5453              /* it would be more logical if LegalityTest() also figured out
5454               * which drops are legal. For now we forbid pawns on back rank.
5455               * Shogi is on its own here...
5456               */
5457              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5458                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5459                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5460          }
5461          return WhiteDrop; /* Not needed to specify white or black yet */
5462     }
5463
5464     userOfferedDraw = FALSE;
5465         
5466     /* [HGM] always test for legality, to get promotion info */
5467     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5468                                          fromY, fromX, toY, toX, promoChar);
5469     /* [HGM] but possibly ignore an IllegalMove result */
5470     if (appData.testLegality) {
5471         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5472             DisplayMoveError(_("Illegal move"));
5473             return ImpossibleMove;
5474         }
5475     }
5476
5477     return moveType;
5478     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5479        function is made into one that returns an OK move type if FinishMove
5480        should be called. This to give the calling driver routine the
5481        opportunity to finish the userMove input with a promotion popup,
5482        without bothering the user with this for invalid or illegal moves */
5483
5484 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5485 }
5486
5487 /* Common tail of UserMoveEvent and DropMenuEvent */
5488 int
5489 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5490      ChessMove moveType;
5491      int fromX, fromY, toX, toY;
5492      /*char*/int promoChar;
5493 {
5494     char *bookHit = 0;
5495
5496     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5497         // [HGM] superchess: suppress promotions to non-available piece
5498         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5499         if(WhiteOnMove(currentMove)) {
5500             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5501         } else {
5502             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5503         }
5504     }
5505
5506     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5507        move type in caller when we know the move is a legal promotion */
5508     if(moveType == NormalMove && promoChar)
5509         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5510
5511     /* [HGM] convert drag-and-drop piece drops to standard form */
5512     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5513          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5514            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5515                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5516            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5517            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5518            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5519            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5520          fromY = DROP_RANK;
5521     }
5522
5523     /* [HGM] <popupFix> The following if has been moved here from
5524        UserMoveEvent(). Because it seemed to belong here (why not allow
5525        piece drops in training games?), and because it can only be
5526        performed after it is known to what we promote. */
5527     if (gameMode == Training) {
5528       /* compare the move played on the board to the next move in the
5529        * game. If they match, display the move and the opponent's response. 
5530        * If they don't match, display an error message.
5531        */
5532       int saveAnimate;
5533       Board testBoard;
5534       CopyBoard(testBoard, boards[currentMove]);
5535       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5536
5537       if (CompareBoards(testBoard, boards[currentMove+1])) {
5538         ForwardInner(currentMove+1);
5539
5540         /* Autoplay the opponent's response.
5541          * if appData.animate was TRUE when Training mode was entered,
5542          * the response will be animated.
5543          */
5544         saveAnimate = appData.animate;
5545         appData.animate = animateTraining;
5546         ForwardInner(currentMove+1);
5547         appData.animate = saveAnimate;
5548
5549         /* check for the end of the game */
5550         if (currentMove >= forwardMostMove) {
5551           gameMode = PlayFromGameFile;
5552           ModeHighlight();
5553           SetTrainingModeOff();
5554           DisplayInformation(_("End of game"));
5555         }
5556       } else {
5557         DisplayError(_("Incorrect move"), 0);
5558       }
5559       return 1;
5560     }
5561
5562   /* Ok, now we know that the move is good, so we can kill
5563      the previous line in Analysis Mode */
5564   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5565                                 && currentMove < forwardMostMove) {
5566     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5567   }
5568
5569   /* If we need the chess program but it's dead, restart it */
5570   ResurrectChessProgram();
5571
5572   /* A user move restarts a paused game*/
5573   if (pausing)
5574     PauseEvent();
5575
5576   thinkOutput[0] = NULLCHAR;
5577
5578   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5579
5580   if (gameMode == BeginningOfGame) {
5581     if (appData.noChessProgram) {
5582       gameMode = EditGame;
5583       SetGameInfo();
5584     } else {
5585       char buf[MSG_SIZ];
5586       gameMode = MachinePlaysBlack;
5587       StartClocks();
5588       SetGameInfo();
5589       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5590       DisplayTitle(buf);
5591       if (first.sendName) {
5592         sprintf(buf, "name %s\n", gameInfo.white);
5593         SendToProgram(buf, &first);
5594       }
5595       StartClocks();
5596     }
5597     ModeHighlight();
5598   }
5599
5600   /* Relay move to ICS or chess engine */
5601   if (appData.icsActive) {
5602     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5603         gameMode == IcsExamining) {
5604       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5605       ics_user_moved = 1;
5606     }
5607   } else {
5608     if (first.sendTime && (gameMode == BeginningOfGame ||
5609                            gameMode == MachinePlaysWhite ||
5610                            gameMode == MachinePlaysBlack)) {
5611       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5612     }
5613     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5614          // [HGM] book: if program might be playing, let it use book
5615         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5616         first.maybeThinking = TRUE;
5617     } else SendMoveToProgram(forwardMostMove-1, &first);
5618     if (currentMove == cmailOldMove + 1) {
5619       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5620     }
5621   }
5622
5623   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5624
5625   switch (gameMode) {
5626   case EditGame:
5627     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5628     case MT_NONE:
5629     case MT_CHECK:
5630       break;
5631     case MT_CHECKMATE:
5632     case MT_STAINMATE:
5633       if (WhiteOnMove(currentMove)) {
5634         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5635       } else {
5636         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5637       }
5638       break;
5639     case MT_STALEMATE:
5640       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5641       break;
5642     }
5643     break;
5644     
5645   case MachinePlaysBlack:
5646   case MachinePlaysWhite:
5647     /* disable certain menu options while machine is thinking */
5648     SetMachineThinkingEnables();
5649     break;
5650
5651   default:
5652     break;
5653   }
5654
5655   if(bookHit) { // [HGM] book: simulate book reply
5656         static char bookMove[MSG_SIZ]; // a bit generous?
5657
5658         programStats.nodes = programStats.depth = programStats.time = 
5659         programStats.score = programStats.got_only_move = 0;
5660         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5661
5662         strcpy(bookMove, "move ");
5663         strcat(bookMove, bookHit);
5664         HandleMachineMove(bookMove, &first);
5665   }
5666   return 1;
5667 }
5668
5669 void
5670 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5671      int fromX, fromY, toX, toY;
5672      int promoChar;
5673 {
5674     /* [HGM] This routine was added to allow calling of its two logical
5675        parts from other modules in the old way. Before, UserMoveEvent()
5676        automatically called FinishMove() if the move was OK, and returned
5677        otherwise. I separated the two, in order to make it possible to
5678        slip a promotion popup in between. But that it always needs two
5679        calls, to the first part, (now called UserMoveTest() ), and to
5680        FinishMove if the first part succeeded. Calls that do not need
5681        to do anything in between, can call this routine the old way. 
5682     */
5683     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5684 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5685     if(moveType == AmbiguousMove)
5686         DrawPosition(FALSE, boards[currentMove]);
5687     else if(moveType != ImpossibleMove && moveType != Comment)
5688         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5689 }
5690
5691 void
5692 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5693      Board board;
5694      int flags;
5695      ChessMove kind;
5696      int rf, ff, rt, ft;
5697      VOIDSTAR closure;
5698 {
5699     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5700     Markers *m = (Markers *) closure;
5701     if(rf == fromY && ff == fromX)
5702         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5703                          || kind == WhiteCapturesEnPassant
5704                          || kind == BlackCapturesEnPassant);
5705     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5706 }
5707
5708 void
5709 MarkTargetSquares(int clear)
5710 {
5711   int x, y;
5712   if(!appData.markers || !appData.highlightDragging || 
5713      !appData.testLegality || gameMode == EditPosition) return;
5714   if(clear) {
5715     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5716   } else {
5717     int capt = 0;
5718     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5719     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5720       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5721       if(capt)
5722       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5723     }
5724   }
5725   DrawPosition(TRUE, NULL);
5726 }
5727
5728 void LeftClick(ClickType clickType, int xPix, int yPix)
5729 {
5730     int x, y;
5731     Boolean saveAnimate;
5732     static int second = 0, promotionChoice = 0;
5733     char promoChoice = NULLCHAR;
5734
5735     if (clickType == Press) ErrorPopDown();
5736     MarkTargetSquares(1);
5737
5738     x = EventToSquare(xPix, BOARD_WIDTH);
5739     y = EventToSquare(yPix, BOARD_HEIGHT);
5740     if (!flipView && y >= 0) {
5741         y = BOARD_HEIGHT - 1 - y;
5742     }
5743     if (flipView && x >= 0) {
5744         x = BOARD_WIDTH - 1 - x;
5745     }
5746
5747     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5748         if(clickType == Release) return; // ignore upclick of click-click destination
5749         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5750         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5751         if(gameInfo.holdingsWidth && 
5752                 (WhiteOnMove(currentMove) 
5753                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5754                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5755             // click in right holdings, for determining promotion piece
5756             ChessSquare p = boards[currentMove][y][x];
5757             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5758             if(p != EmptySquare) {
5759                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5760                 fromX = fromY = -1;
5761                 return;
5762             }
5763         }
5764         DrawPosition(FALSE, boards[currentMove]);
5765         return;
5766     }
5767
5768     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5769     if(clickType == Press
5770             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5771               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5772               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5773         return;
5774
5775     if (fromX == -1) {
5776         if (clickType == Press) {
5777             /* First square */
5778             if (OKToStartUserMove(x, y)) {
5779                 fromX = x;
5780                 fromY = y;
5781                 second = 0;
5782                 MarkTargetSquares(0);
5783                 DragPieceBegin(xPix, yPix);
5784                 if (appData.highlightDragging) {
5785                     SetHighlights(x, y, -1, -1);
5786                 }
5787             }
5788         }
5789         return;
5790     }
5791
5792     /* fromX != -1 */
5793     if (clickType == Press && gameMode != EditPosition) {
5794         ChessSquare fromP;
5795         ChessSquare toP;
5796         int frc;
5797
5798         // ignore off-board to clicks
5799         if(y < 0 || x < 0) return;
5800
5801         /* Check if clicking again on the same color piece */
5802         fromP = boards[currentMove][fromY][fromX];
5803         toP = boards[currentMove][y][x];
5804         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5805         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5806              WhitePawn <= toP && toP <= WhiteKing &&
5807              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5808              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5809             (BlackPawn <= fromP && fromP <= BlackKing && 
5810              BlackPawn <= toP && toP <= BlackKing &&
5811              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5812              !(fromP == BlackKing && toP == BlackRook && frc))) {
5813             /* Clicked again on same color piece -- changed his mind */
5814             second = (x == fromX && y == fromY);
5815             if (appData.highlightDragging) {
5816                 SetHighlights(x, y, -1, -1);
5817             } else {
5818                 ClearHighlights();
5819             }
5820             if (OKToStartUserMove(x, y)) {
5821                 fromX = x;
5822                 fromY = y;
5823                 MarkTargetSquares(0);
5824                 DragPieceBegin(xPix, yPix);
5825             }
5826             return;
5827         }
5828         // ignore clicks on holdings
5829         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5830     }
5831
5832     if (clickType == Release && x == fromX && y == fromY) {
5833         DragPieceEnd(xPix, yPix);
5834         if (appData.animateDragging) {
5835             /* Undo animation damage if any */
5836             DrawPosition(FALSE, NULL);
5837         }
5838         if (second) {
5839             /* Second up/down in same square; just abort move */
5840             second = 0;
5841             fromX = fromY = -1;
5842             ClearHighlights();
5843             gotPremove = 0;
5844             ClearPremoveHighlights();
5845         } else {
5846             /* First upclick in same square; start click-click mode */
5847             SetHighlights(x, y, -1, -1);
5848         }
5849         return;
5850     }
5851
5852     /* we now have a different from- and (possibly off-board) to-square */
5853     /* Completed move */
5854     toX = x;
5855     toY = y;
5856     saveAnimate = appData.animate;
5857     if (clickType == Press) {
5858         /* Finish clickclick move */
5859         if (appData.animate || appData.highlightLastMove) {
5860             SetHighlights(fromX, fromY, toX, toY);
5861         } else {
5862             ClearHighlights();
5863         }
5864     } else {
5865         /* Finish drag move */
5866         if (appData.highlightLastMove) {
5867             SetHighlights(fromX, fromY, toX, toY);
5868         } else {
5869             ClearHighlights();
5870         }
5871         DragPieceEnd(xPix, yPix);
5872         /* Don't animate move and drag both */
5873         appData.animate = FALSE;
5874     }
5875
5876     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5877     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5878         ChessSquare piece = boards[currentMove][fromY][fromX];
5879         if(gameMode == EditPosition && piece != EmptySquare &&
5880            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5881             int n;
5882              
5883             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5884                 n = PieceToNumber(piece - (int)BlackPawn);
5885                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5886                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5887                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5888             } else
5889             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5890                 n = PieceToNumber(piece);
5891                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5892                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5893                 boards[currentMove][n][BOARD_WIDTH-2]++;
5894             }
5895             boards[currentMove][fromY][fromX] = EmptySquare;
5896         }
5897         ClearHighlights();
5898         fromX = fromY = -1;
5899         DrawPosition(TRUE, boards[currentMove]);
5900         return;
5901     }
5902
5903     // off-board moves should not be highlighted
5904     if(x < 0 || x < 0) ClearHighlights();
5905
5906     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5907         SetHighlights(fromX, fromY, toX, toY);
5908         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5909             // [HGM] super: promotion to captured piece selected from holdings
5910             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5911             promotionChoice = TRUE;
5912             // kludge follows to temporarily execute move on display, without promoting yet
5913             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5914             boards[currentMove][toY][toX] = p;
5915             DrawPosition(FALSE, boards[currentMove]);
5916             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5917             boards[currentMove][toY][toX] = q;
5918             DisplayMessage("Click in holdings to choose piece", "");
5919             return;
5920         }
5921         PromotionPopUp();
5922     } else {
5923         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5924         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5925         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5926         fromX = fromY = -1;
5927     }
5928     appData.animate = saveAnimate;
5929     if (appData.animate || appData.animateDragging) {
5930         /* Undo animation damage if needed */
5931         DrawPosition(FALSE, NULL);
5932     }
5933 }
5934
5935 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5936 {
5937 //    char * hint = lastHint;
5938     FrontEndProgramStats stats;
5939
5940     stats.which = cps == &first ? 0 : 1;
5941     stats.depth = cpstats->depth;
5942     stats.nodes = cpstats->nodes;
5943     stats.score = cpstats->score;
5944     stats.time = cpstats->time;
5945     stats.pv = cpstats->movelist;
5946     stats.hint = lastHint;
5947     stats.an_move_index = 0;
5948     stats.an_move_count = 0;
5949
5950     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5951         stats.hint = cpstats->move_name;
5952         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5953         stats.an_move_count = cpstats->nr_moves;
5954     }
5955
5956     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5957
5958     SetProgramStats( &stats );
5959 }
5960
5961 int
5962 Adjudicate(ChessProgramState *cps)
5963 {       // [HGM] some adjudications useful with buggy engines
5964         // [HGM] adjudicate: made into separate routine, which now can be called after every move
5965         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5966         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
5967         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5968         int k, count = 0; static int bare = 1;
5969         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5970         Boolean canAdjudicate = !appData.icsActive;
5971
5972         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5973         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5974             if( appData.testLegality )
5975             {   /* [HGM] Some more adjudications for obstinate engines */
5976                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5977                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5978                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5979                 static int moveCount = 6;
5980                 ChessMove result;
5981                 char *reason = NULL;
5982
5983                 /* Count what is on board. */
5984                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5985                 {   ChessSquare p = boards[forwardMostMove][i][j];
5986                     int m=i;
5987
5988                     switch((int) p)
5989                     {   /* count B,N,R and other of each side */
5990                         case WhiteKing:
5991                         case BlackKing:
5992                              NrK++; break; // [HGM] atomic: count Kings
5993                         case WhiteKnight:
5994                              NrWN++; break;
5995                         case WhiteBishop:
5996                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5997                              bishopsColor |= 1 << ((i^j)&1);
5998                              NrWB++; break;
5999                         case BlackKnight:
6000                              NrBN++; break;
6001                         case BlackBishop:
6002                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6003                              bishopsColor |= 1 << ((i^j)&1);
6004                              NrBB++; break;
6005                         case WhiteRook:
6006                              NrWR++; break;
6007                         case BlackRook:
6008                              NrBR++; break;
6009                         case WhiteQueen:
6010                              NrWQ++; break;
6011                         case BlackQueen:
6012                              NrBQ++; break;
6013                         case EmptySquare: 
6014                              break;
6015                         case BlackPawn:
6016                              m = 7-i;
6017                         case WhitePawn:
6018                              PawnAdvance += m; NrPawns++;
6019                     }
6020                     NrPieces += (p != EmptySquare);
6021                     NrW += ((int)p < (int)BlackPawn);
6022                     if(gameInfo.variant == VariantXiangqi && 
6023                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6024                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6025                         NrW -= ((int)p < (int)BlackPawn);
6026                     }
6027                 }
6028
6029                 /* Some material-based adjudications that have to be made before stalemate test */
6030                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6031                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6032                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6033                      if(canAdjudicate && appData.checkMates) {
6034                          if(engineOpponent)
6035                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6036                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6037                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6038                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6039                          return 1;
6040                      }
6041                 }
6042
6043                 /* Bare King in Shatranj (loses) or Losers (wins) */
6044                 if( NrW == 1 || NrPieces - NrW == 1) {
6045                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6046                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6047                      if(canAdjudicate && appData.checkMates) {
6048                          if(engineOpponent)
6049                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6050                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6051                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6052                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6053                          return 1;
6054                      }
6055                   } else
6056                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6057                   {    /* bare King */
6058                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6059                         if(canAdjudicate && appData.checkMates) {
6060                             /* but only adjudicate if adjudication enabled */
6061                             if(engineOpponent)
6062                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6063                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6064                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6065                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6066                             return 1;
6067                         }
6068                   }
6069                 } else bare = 1;
6070
6071
6072             // don't wait for engine to announce game end if we can judge ourselves
6073             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6074               case MT_CHECK:
6075                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6076                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6077                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6078                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6079                             checkCnt++;
6080                         if(checkCnt >= 2) {
6081                             reason = "Xboard adjudication: 3rd check";
6082                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6083                             break;
6084                         }
6085                     }
6086                 }
6087               case MT_NONE:
6088               default:
6089                 break;
6090               case MT_STALEMATE:
6091               case MT_STAINMATE:
6092                 reason = "Xboard adjudication: Stalemate";
6093                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6094                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6095                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6096                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6097                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6098                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6099                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6100                                                                         EP_CHECKMATE : EP_WINS);
6101                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6102                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6103                 }
6104                 break;
6105               case MT_CHECKMATE:
6106                 reason = "Xboard adjudication: Checkmate";
6107                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6108                 break;
6109             }
6110
6111                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6112                     case EP_STALEMATE:
6113                         result = GameIsDrawn; break;
6114                     case EP_CHECKMATE:
6115                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6116                     case EP_WINS:
6117                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6118                     default:
6119                         result = (ChessMove) 0;
6120                 }
6121                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6122                     if(engineOpponent)
6123                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6124                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125                     GameEnds( result, reason, GE_XBOARD );
6126                     return 1;
6127                 }
6128
6129                 /* Next absolutely insufficient mating material. */
6130                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6131                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6132                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6133                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6134                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6135
6136                      /* always flag draws, for judging claims */
6137                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6138
6139                      if(canAdjudicate && appData.materialDraws) {
6140                          /* but only adjudicate them if adjudication enabled */
6141                          if(engineOpponent) {
6142                            SendToProgram("force\n", engineOpponent); // suppress reply
6143                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6144                          }
6145                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6147                          return 1;
6148                      }
6149                 }
6150
6151                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6152                 if(NrPieces == 4 && 
6153                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6154                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6155                    || NrWN==2 || NrBN==2     /* KNNK */
6156                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6157                   ) ) {
6158                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6159                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6160                           if(engineOpponent) {
6161                             SendToProgram("force\n", engineOpponent); // suppress reply
6162                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6163                           }
6164                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6165                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6166                           return 1;
6167                      }
6168                 } else moveCount = 6;
6169             }
6170         }
6171           
6172         if (appData.debugMode) { int i;
6173             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6174                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6175                     appData.drawRepeats);
6176             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6177               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6178             
6179         }
6180
6181         // Repetition draws and 50-move rule can be applied independently of legality testing
6182
6183                 /* Check for rep-draws */
6184                 count = 0;
6185                 for(k = forwardMostMove-2;
6186                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6187                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6188                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6189                     k-=2)
6190                 {   int rights=0;
6191                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6192                         /* compare castling rights */
6193                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6194                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6195                                 rights++; /* King lost rights, while rook still had them */
6196                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6197                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6198                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6199                                    rights++; /* but at least one rook lost them */
6200                         }
6201                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6202                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6203                                 rights++; 
6204                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6205                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6206                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6207                                    rights++;
6208                         }
6209                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6210                             && appData.drawRepeats > 1) {
6211                              /* adjudicate after user-specified nr of repeats */
6212                              if(engineOpponent) {
6213                                SendToProgram("force\n", engineOpponent); // suppress reply
6214                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6215                              }
6216                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6218                                 // [HGM] xiangqi: check for forbidden perpetuals
6219                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6220                                 for(m=forwardMostMove; m>k; m-=2) {
6221                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6222                                         ourPerpetual = 0; // the current mover did not always check
6223                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6224                                         hisPerpetual = 0; // the opponent did not always check
6225                                 }
6226                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6227                                                                         ourPerpetual, hisPerpetual);
6228                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6229                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6230                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6231                                     return 1;
6232                                 }
6233                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6234                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6235                                 // Now check for perpetual chases
6236                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6237                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6238                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6239                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6240                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6241                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6242                                         return 1;
6243                                     }
6244                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6245                                         break; // Abort repetition-checking loop.
6246                                 }
6247                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6248                              }
6249                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6250                              return 1;
6251                         }
6252                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6253                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6254                     }
6255                 }
6256
6257                 /* Now we test for 50-move draws. Determine ply count */
6258                 count = forwardMostMove;
6259                 /* look for last irreversble move */
6260                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6261                     count--;
6262                 /* if we hit starting position, add initial plies */
6263                 if( count == backwardMostMove )
6264                     count -= initialRulePlies;
6265                 count = forwardMostMove - count; 
6266                 if( count >= 100)
6267                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6268                          /* this is used to judge if draw claims are legal */
6269                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6270                          if(engineOpponent) {
6271                            SendToProgram("force\n", engineOpponent); // suppress reply
6272                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6273                          }
6274                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6276                          return 1;
6277                 }
6278
6279                 /* if draw offer is pending, treat it as a draw claim
6280                  * when draw condition present, to allow engines a way to
6281                  * claim draws before making their move to avoid a race
6282                  * condition occurring after their move
6283                  */
6284                 if(gameMode == TwoMachinesPlay) // for now; figure out how to handle claims in human games
6285                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6286                          char *p = NULL;
6287                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6288                              p = "Draw claim: 50-move rule";
6289                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6290                              p = "Draw claim: 3-fold repetition";
6291                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6292                              p = "Draw claim: insufficient mating material";
6293                          if( p != NULL ) {
6294                              if(engineOpponent) {
6295                                SendToProgram("force\n", engineOpponent); // suppress reply
6296                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6297                              }
6298                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6299                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6300                              return 1;
6301                          }
6302                 }
6303
6304                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6305                     if(engineOpponent) {
6306                       SendToProgram("force\n", engineOpponent); // suppress reply
6307                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6308                     }
6309                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6310                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6311                     return 1;
6312                 }
6313         return 0;
6314 }
6315
6316 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6317 {   // [HGM] book: this routine intercepts moves to simulate book replies
6318     char *bookHit = NULL;
6319
6320     //first determine if the incoming move brings opponent into his book
6321     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6322         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6323     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6324     if(bookHit != NULL && !cps->bookSuspend) {
6325         // make sure opponent is not going to reply after receiving move to book position
6326         SendToProgram("force\n", cps);
6327         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6328     }
6329     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6330     // now arrange restart after book miss
6331     if(bookHit) {
6332         // after a book hit we never send 'go', and the code after the call to this routine
6333         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6334         char buf[MSG_SIZ];
6335         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6336         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6337         SendToProgram(buf, cps);
6338         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6339     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6340         SendToProgram("go\n", cps);
6341         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6342     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6343         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6344             SendToProgram("go\n", cps); 
6345         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6346     }
6347     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6348 }
6349
6350 char *savedMessage;
6351 ChessProgramState *savedState;
6352 void DeferredBookMove(void)
6353 {
6354         if(savedState->lastPing != savedState->lastPong)
6355                     ScheduleDelayedEvent(DeferredBookMove, 10);
6356         else
6357         HandleMachineMove(savedMessage, savedState);
6358 }
6359
6360 void
6361 HandleMachineMove(message, cps)
6362      char *message;
6363      ChessProgramState *cps;
6364 {
6365     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6366     char realname[MSG_SIZ];
6367     int fromX, fromY, toX, toY;
6368     ChessMove moveType;
6369     char promoChar;
6370     char *p;
6371     int machineWhite;
6372     char *bookHit;
6373
6374     cps->userError = 0;
6375
6376 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6377     /*
6378      * Kludge to ignore BEL characters
6379      */
6380     while (*message == '\007') message++;
6381
6382     /*
6383      * [HGM] engine debug message: ignore lines starting with '#' character
6384      */
6385     if(cps->debug && *message == '#') return;
6386
6387     /*
6388      * Look for book output
6389      */
6390     if (cps == &first && bookRequested) {
6391         if (message[0] == '\t' || message[0] == ' ') {
6392             /* Part of the book output is here; append it */
6393             strcat(bookOutput, message);
6394             strcat(bookOutput, "  \n");
6395             return;
6396         } else if (bookOutput[0] != NULLCHAR) {
6397             /* All of book output has arrived; display it */
6398             char *p = bookOutput;
6399             while (*p != NULLCHAR) {
6400                 if (*p == '\t') *p = ' ';
6401                 p++;
6402             }
6403             DisplayInformation(bookOutput);
6404             bookRequested = FALSE;
6405             /* Fall through to parse the current output */
6406         }
6407     }
6408
6409     /*
6410      * Look for machine move.
6411      */
6412     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6413         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6414     {
6415         /* This method is only useful on engines that support ping */
6416         if (cps->lastPing != cps->lastPong) {
6417           if (gameMode == BeginningOfGame) {
6418             /* Extra move from before last new; ignore */
6419             if (appData.debugMode) {
6420                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6421             }
6422           } else {
6423             if (appData.debugMode) {
6424                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6425                         cps->which, gameMode);
6426             }
6427
6428             SendToProgram("undo\n", cps);
6429           }
6430           return;
6431         }
6432
6433         switch (gameMode) {
6434           case BeginningOfGame:
6435             /* Extra move from before last reset; ignore */
6436             if (appData.debugMode) {
6437                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6438             }
6439             return;
6440
6441           case EndOfGame:
6442           case IcsIdle:
6443           default:
6444             /* Extra move after we tried to stop.  The mode test is
6445                not a reliable way of detecting this problem, but it's
6446                the best we can do on engines that don't support ping.
6447             */
6448             if (appData.debugMode) {
6449                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6450                         cps->which, gameMode);
6451             }
6452             SendToProgram("undo\n", cps);
6453             return;
6454
6455           case MachinePlaysWhite:
6456           case IcsPlayingWhite:
6457             machineWhite = TRUE;
6458             break;
6459
6460           case MachinePlaysBlack:
6461           case IcsPlayingBlack:
6462             machineWhite = FALSE;
6463             break;
6464
6465           case TwoMachinesPlay:
6466             machineWhite = (cps->twoMachinesColor[0] == 'w');
6467             break;
6468         }
6469         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6470             if (appData.debugMode) {
6471                 fprintf(debugFP,
6472                         "Ignoring move out of turn by %s, gameMode %d"
6473                         ", forwardMost %d\n",
6474                         cps->which, gameMode, forwardMostMove);
6475             }
6476             return;
6477         }
6478
6479     if (appData.debugMode) { int f = forwardMostMove;
6480         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6481                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6482                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6483     }
6484         if(cps->alphaRank) AlphaRank(machineMove, 4);
6485         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6486                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6487             /* Machine move could not be parsed; ignore it. */
6488             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6489                     machineMove, cps->which);
6490             DisplayError(buf1, 0);
6491             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6492                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6493             if (gameMode == TwoMachinesPlay) {
6494               GameEnds(machineWhite ? BlackWins : WhiteWins,
6495                        buf1, GE_XBOARD);
6496             }
6497             return;
6498         }
6499
6500         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6501         /* So we have to redo legality test with true e.p. status here,  */
6502         /* to make sure an illegal e.p. capture does not slip through,   */
6503         /* to cause a forfeit on a justified illegal-move complaint      */
6504         /* of the opponent.                                              */
6505         if( gameMode==TwoMachinesPlay && appData.testLegality
6506             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6507                                                               ) {
6508            ChessMove moveType;
6509            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6510                              fromY, fromX, toY, toX, promoChar);
6511             if (appData.debugMode) {
6512                 int i;
6513                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6514                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6515                 fprintf(debugFP, "castling rights\n");
6516             }
6517             if(moveType == IllegalMove) {
6518                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6519                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6520                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6521                            buf1, GE_XBOARD);
6522                 return;
6523            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6524            /* [HGM] Kludge to handle engines that send FRC-style castling
6525               when they shouldn't (like TSCP-Gothic) */
6526            switch(moveType) {
6527              case WhiteASideCastleFR:
6528              case BlackASideCastleFR:
6529                toX+=2;
6530                currentMoveString[2]++;
6531                break;
6532              case WhiteHSideCastleFR:
6533              case BlackHSideCastleFR:
6534                toX--;
6535                currentMoveString[2]--;
6536                break;
6537              default: ; // nothing to do, but suppresses warning of pedantic compilers
6538            }
6539         }
6540         hintRequested = FALSE;
6541         lastHint[0] = NULLCHAR;
6542         bookRequested = FALSE;
6543         /* Program may be pondering now */
6544         cps->maybeThinking = TRUE;
6545         if (cps->sendTime == 2) cps->sendTime = 1;
6546         if (cps->offeredDraw) cps->offeredDraw--;
6547
6548 #if ZIPPY
6549         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6550             first.initDone) {
6551           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6552           ics_user_moved = 1;
6553           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6554                 char buf[3*MSG_SIZ];
6555
6556                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6557                         programStats.score / 100.,
6558                         programStats.depth,
6559                         programStats.time / 100.,
6560                         (unsigned int)programStats.nodes,
6561                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6562                         programStats.movelist);
6563                 SendToICS(buf);
6564 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6565           }
6566         }
6567 #endif
6568         /* currentMoveString is set as a side-effect of ParseOneMove */
6569         strcpy(machineMove, currentMoveString);
6570         strcat(machineMove, "\n");
6571         strcpy(moveList[forwardMostMove], machineMove);
6572
6573         /* [AS] Save move info and clear stats for next move */
6574         pvInfoList[ forwardMostMove ].score = programStats.score;
6575         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6576         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6577         ClearProgramStats();
6578         thinkOutput[0] = NULLCHAR;
6579         hiddenThinkOutputState = 0;
6580
6581         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6582
6583         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6584         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6585             int count = 0;
6586
6587             while( count < adjudicateLossPlies ) {
6588                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6589
6590                 if( count & 1 ) {
6591                     score = -score; /* Flip score for winning side */
6592                 }
6593
6594                 if( score > adjudicateLossThreshold ) {
6595                     break;
6596                 }
6597
6598                 count++;
6599             }
6600
6601             if( count >= adjudicateLossPlies ) {
6602                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603
6604                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6605                     "Xboard adjudication", 
6606                     GE_XBOARD );
6607
6608                 return;
6609             }
6610         }
6611
6612         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6613
6614         bookHit = NULL;
6615         if (gameMode == TwoMachinesPlay) {
6616             /* [HGM] relaying draw offers moved to after reception of move */
6617             /* and interpreting offer as claim if it brings draw condition */
6618             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6619                 SendToProgram("draw\n", cps->other);
6620             }
6621             if (cps->other->sendTime) {
6622                 SendTimeRemaining(cps->other,
6623                                   cps->other->twoMachinesColor[0] == 'w');
6624             }
6625             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6626             if (firstMove && !bookHit) {
6627                 firstMove = FALSE;
6628                 if (cps->other->useColors) {
6629                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6630                 }
6631                 SendToProgram("go\n", cps->other);
6632             }
6633             cps->other->maybeThinking = TRUE;
6634         }
6635
6636         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6637         
6638         if (!pausing && appData.ringBellAfterMoves) {
6639             RingBell();
6640         }
6641
6642         /* 
6643          * Reenable menu items that were disabled while
6644          * machine was thinking
6645          */
6646         if (gameMode != TwoMachinesPlay)
6647             SetUserThinkingEnables();
6648
6649         // [HGM] book: after book hit opponent has received move and is now in force mode
6650         // force the book reply into it, and then fake that it outputted this move by jumping
6651         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6652         if(bookHit) {
6653                 static char bookMove[MSG_SIZ]; // a bit generous?
6654
6655                 strcpy(bookMove, "move ");
6656                 strcat(bookMove, bookHit);
6657                 message = bookMove;
6658                 cps = cps->other;
6659                 programStats.nodes = programStats.depth = programStats.time = 
6660                 programStats.score = programStats.got_only_move = 0;
6661                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6662
6663                 if(cps->lastPing != cps->lastPong) {
6664                     savedMessage = message; // args for deferred call
6665                     savedState = cps;
6666                     ScheduleDelayedEvent(DeferredBookMove, 10);
6667                     return;
6668                 }
6669                 goto FakeBookMove;
6670         }
6671
6672         return;
6673     }
6674
6675     /* Set special modes for chess engines.  Later something general
6676      *  could be added here; for now there is just one kludge feature,
6677      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6678      *  when "xboard" is given as an interactive command.
6679      */
6680     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6681         cps->useSigint = FALSE;
6682         cps->useSigterm = FALSE;
6683     }
6684     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6685       ParseFeatures(message+8, cps);
6686       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6687     }
6688
6689     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6690      * want this, I was asked to put it in, and obliged.
6691      */
6692     if (!strncmp(message, "setboard ", 9)) {
6693         Board initial_position;
6694
6695         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6696
6697         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6698             DisplayError(_("Bad FEN received from engine"), 0);
6699             return ;
6700         } else {
6701            Reset(TRUE, FALSE);
6702            CopyBoard(boards[0], initial_position);
6703            initialRulePlies = FENrulePlies;
6704            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6705            else gameMode = MachinePlaysBlack;                 
6706            DrawPosition(FALSE, boards[currentMove]);
6707         }
6708         return;
6709     }
6710
6711     /*
6712      * Look for communication commands
6713      */
6714     if (!strncmp(message, "telluser ", 9)) {
6715         DisplayNote(message + 9);
6716         return;
6717     }
6718     if (!strncmp(message, "tellusererror ", 14)) {
6719         cps->userError = 1;
6720         DisplayError(message + 14, 0);
6721         return;
6722     }
6723     if (!strncmp(message, "tellopponent ", 13)) {
6724       if (appData.icsActive) {
6725         if (loggedOn) {
6726           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6727           SendToICS(buf1);
6728         }
6729       } else {
6730         DisplayNote(message + 13);
6731       }
6732       return;
6733     }
6734     if (!strncmp(message, "tellothers ", 11)) {
6735       if (appData.icsActive) {
6736         if (loggedOn) {
6737           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6738           SendToICS(buf1);
6739         }
6740       }
6741       return;
6742     }
6743     if (!strncmp(message, "tellall ", 8)) {
6744       if (appData.icsActive) {
6745         if (loggedOn) {
6746           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6747           SendToICS(buf1);
6748         }
6749       } else {
6750         DisplayNote(message + 8);
6751       }
6752       return;
6753     }
6754     if (strncmp(message, "warning", 7) == 0) {
6755         /* Undocumented feature, use tellusererror in new code */
6756         DisplayError(message, 0);
6757         return;
6758     }
6759     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6760         strcpy(realname, cps->tidy);
6761         strcat(realname, " query");
6762         AskQuestion(realname, buf2, buf1, cps->pr);
6763         return;
6764     }
6765     /* Commands from the engine directly to ICS.  We don't allow these to be 
6766      *  sent until we are logged on. Crafty kibitzes have been known to 
6767      *  interfere with the login process.
6768      */
6769     if (loggedOn) {
6770         if (!strncmp(message, "tellics ", 8)) {
6771             SendToICS(message + 8);
6772             SendToICS("\n");
6773             return;
6774         }
6775         if (!strncmp(message, "tellicsnoalias ", 15)) {
6776             SendToICS(ics_prefix);
6777             SendToICS(message + 15);
6778             SendToICS("\n");
6779             return;
6780         }
6781         /* The following are for backward compatibility only */
6782         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6783             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6784             SendToICS(ics_prefix);
6785             SendToICS(message);
6786             SendToICS("\n");
6787             return;
6788         }
6789     }
6790     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6791         return;
6792     }
6793     /*
6794      * If the move is illegal, cancel it and redraw the board.
6795      * Also deal with other error cases.  Matching is rather loose
6796      * here to accommodate engines written before the spec.
6797      */
6798     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6799         strncmp(message, "Error", 5) == 0) {
6800         if (StrStr(message, "name") || 
6801             StrStr(message, "rating") || StrStr(message, "?") ||
6802             StrStr(message, "result") || StrStr(message, "board") ||
6803             StrStr(message, "bk") || StrStr(message, "computer") ||
6804             StrStr(message, "variant") || StrStr(message, "hint") ||
6805             StrStr(message, "random") || StrStr(message, "depth") ||
6806             StrStr(message, "accepted")) {
6807             return;
6808         }
6809         if (StrStr(message, "protover")) {
6810           /* Program is responding to input, so it's apparently done
6811              initializing, and this error message indicates it is
6812              protocol version 1.  So we don't need to wait any longer
6813              for it to initialize and send feature commands. */
6814           FeatureDone(cps, 1);
6815           cps->protocolVersion = 1;
6816           return;
6817         }
6818         cps->maybeThinking = FALSE;
6819
6820         if (StrStr(message, "draw")) {
6821             /* Program doesn't have "draw" command */
6822             cps->sendDrawOffers = 0;
6823             return;
6824         }
6825         if (cps->sendTime != 1 &&
6826             (StrStr(message, "time") || StrStr(message, "otim"))) {
6827           /* Program apparently doesn't have "time" or "otim" command */
6828           cps->sendTime = 0;
6829           return;
6830         }
6831         if (StrStr(message, "analyze")) {
6832             cps->analysisSupport = FALSE;
6833             cps->analyzing = FALSE;
6834             Reset(FALSE, TRUE);
6835             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6836             DisplayError(buf2, 0);
6837             return;
6838         }
6839         if (StrStr(message, "(no matching move)st")) {
6840           /* Special kludge for GNU Chess 4 only */
6841           cps->stKludge = TRUE;
6842           SendTimeControl(cps, movesPerSession, timeControl,
6843                           timeIncrement, appData.searchDepth,
6844                           searchTime);
6845           return;
6846         }
6847         if (StrStr(message, "(no matching move)sd")) {
6848           /* Special kludge for GNU Chess 4 only */
6849           cps->sdKludge = TRUE;
6850           SendTimeControl(cps, movesPerSession, timeControl,
6851                           timeIncrement, appData.searchDepth,
6852                           searchTime);
6853           return;
6854         }
6855         if (!StrStr(message, "llegal")) {
6856             return;
6857         }
6858         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6859             gameMode == IcsIdle) return;
6860         if (forwardMostMove <= backwardMostMove) return;
6861         if (pausing) PauseEvent();
6862       if(appData.forceIllegal) {
6863             // [HGM] illegal: machine refused move; force position after move into it
6864           SendToProgram("force\n", cps);
6865           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6866                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6867                 // when black is to move, while there might be nothing on a2 or black
6868                 // might already have the move. So send the board as if white has the move.
6869                 // But first we must change the stm of the engine, as it refused the last move
6870                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6871                 if(WhiteOnMove(forwardMostMove)) {
6872                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6873                     SendBoard(cps, forwardMostMove); // kludgeless board
6874                 } else {
6875                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6876                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6877                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6878                 }
6879           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6880             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6881                  gameMode == TwoMachinesPlay)
6882               SendToProgram("go\n", cps);
6883             return;
6884       } else
6885         if (gameMode == PlayFromGameFile) {
6886             /* Stop reading this game file */
6887             gameMode = EditGame;
6888             ModeHighlight();
6889         }
6890         currentMove = --forwardMostMove;
6891         DisplayMove(currentMove-1); /* before DisplayMoveError */
6892         SwitchClocks();
6893         DisplayBothClocks();
6894         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6895                 parseList[currentMove], cps->which);
6896         DisplayMoveError(buf1);
6897         DrawPosition(FALSE, boards[currentMove]);
6898
6899         /* [HGM] illegal-move claim should forfeit game when Xboard */
6900         /* only passes fully legal moves                            */
6901         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6902             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6903                                 "False illegal-move claim", GE_XBOARD );
6904         }
6905         return;
6906     }
6907     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6908         /* Program has a broken "time" command that
6909            outputs a string not ending in newline.
6910            Don't use it. */
6911         cps->sendTime = 0;
6912     }
6913     
6914     /*
6915      * If chess program startup fails, exit with an error message.
6916      * Attempts to recover here are futile.
6917      */
6918     if ((StrStr(message, "unknown host") != NULL)
6919         || (StrStr(message, "No remote directory") != NULL)
6920         || (StrStr(message, "not found") != NULL)
6921         || (StrStr(message, "No such file") != NULL)
6922         || (StrStr(message, "can't alloc") != NULL)
6923         || (StrStr(message, "Permission denied") != NULL)) {
6924
6925         cps->maybeThinking = FALSE;
6926         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6927                 cps->which, cps->program, cps->host, message);
6928         RemoveInputSource(cps->isr);
6929         DisplayFatalError(buf1, 0, 1);
6930         return;
6931     }
6932     
6933     /* 
6934      * Look for hint output
6935      */
6936     if (sscanf(message, "Hint: %s", buf1) == 1) {
6937         if (cps == &first && hintRequested) {
6938             hintRequested = FALSE;
6939             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6940                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6941                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6942                                     PosFlags(forwardMostMove),
6943                                     fromY, fromX, toY, toX, promoChar, buf1);
6944                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6945                 DisplayInformation(buf2);
6946             } else {
6947                 /* Hint move could not be parsed!? */
6948               snprintf(buf2, sizeof(buf2),
6949                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6950                         buf1, cps->which);
6951                 DisplayError(buf2, 0);
6952             }
6953         } else {
6954             strcpy(lastHint, buf1);
6955         }
6956         return;
6957     }
6958
6959     /*
6960      * Ignore other messages if game is not in progress
6961      */
6962     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6963         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6964
6965     /*
6966      * look for win, lose, draw, or draw offer
6967      */
6968     if (strncmp(message, "1-0", 3) == 0) {
6969         char *p, *q, *r = "";
6970         p = strchr(message, '{');
6971         if (p) {
6972             q = strchr(p, '}');
6973             if (q) {
6974                 *q = NULLCHAR;
6975                 r = p + 1;
6976             }
6977         }
6978         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6979         return;
6980     } else if (strncmp(message, "0-1", 3) == 0) {
6981         char *p, *q, *r = "";
6982         p = strchr(message, '{');
6983         if (p) {
6984             q = strchr(p, '}');
6985             if (q) {
6986                 *q = NULLCHAR;
6987                 r = p + 1;
6988             }
6989         }
6990         /* Kludge for Arasan 4.1 bug */
6991         if (strcmp(r, "Black resigns") == 0) {
6992             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6993             return;
6994         }
6995         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6996         return;
6997     } else if (strncmp(message, "1/2", 3) == 0) {
6998         char *p, *q, *r = "";
6999         p = strchr(message, '{');
7000         if (p) {
7001             q = strchr(p, '}');
7002             if (q) {
7003                 *q = NULLCHAR;
7004                 r = p + 1;
7005             }
7006         }
7007             
7008         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7009         return;
7010
7011     } else if (strncmp(message, "White resign", 12) == 0) {
7012         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7013         return;
7014     } else if (strncmp(message, "Black resign", 12) == 0) {
7015         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7016         return;
7017     } else if (strncmp(message, "White matches", 13) == 0 ||
7018                strncmp(message, "Black matches", 13) == 0   ) {
7019         /* [HGM] ignore GNUShogi noises */
7020         return;
7021     } else if (strncmp(message, "White", 5) == 0 &&
7022                message[5] != '(' &&
7023                StrStr(message, "Black") == NULL) {
7024         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7025         return;
7026     } else if (strncmp(message, "Black", 5) == 0 &&
7027                message[5] != '(') {
7028         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7029         return;
7030     } else if (strcmp(message, "resign") == 0 ||
7031                strcmp(message, "computer resigns") == 0) {
7032         switch (gameMode) {
7033           case MachinePlaysBlack:
7034           case IcsPlayingBlack:
7035             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7036             break;
7037           case MachinePlaysWhite:
7038           case IcsPlayingWhite:
7039             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7040             break;
7041           case TwoMachinesPlay:
7042             if (cps->twoMachinesColor[0] == 'w')
7043               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7044             else
7045               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7046             break;
7047           default:
7048             /* can't happen */
7049             break;
7050         }
7051         return;
7052     } else if (strncmp(message, "opponent mates", 14) == 0) {
7053         switch (gameMode) {
7054           case MachinePlaysBlack:
7055           case IcsPlayingBlack:
7056             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7057             break;
7058           case MachinePlaysWhite:
7059           case IcsPlayingWhite:
7060             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7061             break;
7062           case TwoMachinesPlay:
7063             if (cps->twoMachinesColor[0] == 'w')
7064               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7065             else
7066               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7067             break;
7068           default:
7069             /* can't happen */
7070             break;
7071         }
7072         return;
7073     } else if (strncmp(message, "computer mates", 14) == 0) {
7074         switch (gameMode) {
7075           case MachinePlaysBlack:
7076           case IcsPlayingBlack:
7077             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7078             break;
7079           case MachinePlaysWhite:
7080           case IcsPlayingWhite:
7081             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7082             break;
7083           case TwoMachinesPlay:
7084             if (cps->twoMachinesColor[0] == 'w')
7085               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7086             else
7087               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7088             break;
7089           default:
7090             /* can't happen */
7091             break;
7092         }
7093         return;
7094     } else if (strncmp(message, "checkmate", 9) == 0) {
7095         if (WhiteOnMove(forwardMostMove)) {
7096             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7097         } else {
7098             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7099         }
7100         return;
7101     } else if (strstr(message, "Draw") != NULL ||
7102                strstr(message, "game is a draw") != NULL) {
7103         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7104         return;
7105     } else if (strstr(message, "offer") != NULL &&
7106                strstr(message, "draw") != NULL) {
7107 #if ZIPPY
7108         if (appData.zippyPlay && first.initDone) {
7109             /* Relay offer to ICS */
7110             SendToICS(ics_prefix);
7111             SendToICS("draw\n");
7112         }
7113 #endif
7114         cps->offeredDraw = 2; /* valid until this engine moves twice */
7115         if (gameMode == TwoMachinesPlay) {
7116             if (cps->other->offeredDraw) {
7117                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7118             /* [HGM] in two-machine mode we delay relaying draw offer      */
7119             /* until after we also have move, to see if it is really claim */
7120             }
7121         } else if (gameMode == MachinePlaysWhite ||
7122                    gameMode == MachinePlaysBlack) {
7123           if (userOfferedDraw) {
7124             DisplayInformation(_("Machine accepts your draw offer"));
7125             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7126           } else {
7127             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7128           }
7129         }
7130     }
7131
7132     
7133     /*
7134      * Look for thinking output
7135      */
7136     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7137           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7138                                 ) {
7139         int plylev, mvleft, mvtot, curscore, time;
7140         char mvname[MOVE_LEN];
7141         u64 nodes; // [DM]
7142         char plyext;
7143         int ignore = FALSE;
7144         int prefixHint = FALSE;
7145         mvname[0] = NULLCHAR;
7146
7147         switch (gameMode) {
7148           case MachinePlaysBlack:
7149           case IcsPlayingBlack:
7150             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7151             break;
7152           case MachinePlaysWhite:
7153           case IcsPlayingWhite:
7154             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7155             break;
7156           case AnalyzeMode:
7157           case AnalyzeFile:
7158             break;
7159           case IcsObserving: /* [DM] icsEngineAnalyze */
7160             if (!appData.icsEngineAnalyze) ignore = TRUE;
7161             break;
7162           case TwoMachinesPlay:
7163             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7164                 ignore = TRUE;
7165             }
7166             break;
7167           default:
7168             ignore = TRUE;
7169             break;
7170         }
7171
7172         if (!ignore) {
7173             buf1[0] = NULLCHAR;
7174             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7175                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7176
7177                 if (plyext != ' ' && plyext != '\t') {
7178                     time *= 100;
7179                 }
7180
7181                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7182                 if( cps->scoreIsAbsolute && 
7183                     ( gameMode == MachinePlaysBlack ||
7184                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7185                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7186                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7187                      !WhiteOnMove(currentMove)
7188                     ) )
7189                 {
7190                     curscore = -curscore;
7191                 }
7192
7193
7194                 programStats.depth = plylev;
7195                 programStats.nodes = nodes;
7196                 programStats.time = time;
7197                 programStats.score = curscore;
7198                 programStats.got_only_move = 0;
7199
7200                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7201                         int ticklen;
7202
7203                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7204                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7205                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7206                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7207                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7208                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7209                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7210                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7211                 }
7212
7213                 /* Buffer overflow protection */
7214                 if (buf1[0] != NULLCHAR) {
7215                     if (strlen(buf1) >= sizeof(programStats.movelist)
7216                         && appData.debugMode) {
7217                         fprintf(debugFP,
7218                                 "PV is too long; using the first %u bytes.\n",
7219                                 (unsigned) sizeof(programStats.movelist) - 1);
7220                     }
7221
7222                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7223                 } else {
7224                     sprintf(programStats.movelist, " no PV\n");
7225                 }
7226
7227                 if (programStats.seen_stat) {
7228                     programStats.ok_to_send = 1;
7229                 }
7230
7231                 if (strchr(programStats.movelist, '(') != NULL) {
7232                     programStats.line_is_book = 1;
7233                     programStats.nr_moves = 0;
7234                     programStats.moves_left = 0;
7235                 } else {
7236                     programStats.line_is_book = 0;
7237                 }
7238
7239                 SendProgramStatsToFrontend( cps, &programStats );
7240
7241                 /* 
7242                     [AS] Protect the thinkOutput buffer from overflow... this
7243                     is only useful if buf1 hasn't overflowed first!
7244                 */
7245                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7246                         plylev, 
7247                         (gameMode == TwoMachinesPlay ?
7248                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7249                         ((double) curscore) / 100.0,
7250                         prefixHint ? lastHint : "",
7251                         prefixHint ? " " : "" );
7252
7253                 if( buf1[0] != NULLCHAR ) {
7254                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7255
7256                     if( strlen(buf1) > max_len ) {
7257                         if( appData.debugMode) {
7258                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7259                         }
7260                         buf1[max_len+1] = '\0';
7261                     }
7262
7263                     strcat( thinkOutput, buf1 );
7264                 }
7265
7266                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7267                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7268                     DisplayMove(currentMove - 1);
7269                 }
7270                 return;
7271
7272             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7273                 /* crafty (9.25+) says "(only move) <move>"
7274                  * if there is only 1 legal move
7275                  */
7276                 sscanf(p, "(only move) %s", buf1);
7277                 sprintf(thinkOutput, "%s (only move)", buf1);
7278                 sprintf(programStats.movelist, "%s (only move)", buf1);
7279                 programStats.depth = 1;
7280                 programStats.nr_moves = 1;
7281                 programStats.moves_left = 1;
7282                 programStats.nodes = 1;
7283                 programStats.time = 1;
7284                 programStats.got_only_move = 1;
7285
7286                 /* Not really, but we also use this member to
7287                    mean "line isn't going to change" (Crafty
7288                    isn't searching, so stats won't change) */
7289                 programStats.line_is_book = 1;
7290
7291                 SendProgramStatsToFrontend( cps, &programStats );
7292                 
7293                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7294                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7295                     DisplayMove(currentMove - 1);
7296                 }
7297                 return;
7298             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7299                               &time, &nodes, &plylev, &mvleft,
7300                               &mvtot, mvname) >= 5) {
7301                 /* The stat01: line is from Crafty (9.29+) in response
7302                    to the "." command */
7303                 programStats.seen_stat = 1;
7304                 cps->maybeThinking = TRUE;
7305
7306                 if (programStats.got_only_move || !appData.periodicUpdates)
7307                   return;
7308
7309                 programStats.depth = plylev;
7310                 programStats.time = time;
7311                 programStats.nodes = nodes;
7312                 programStats.moves_left = mvleft;
7313                 programStats.nr_moves = mvtot;
7314                 strcpy(programStats.move_name, mvname);
7315                 programStats.ok_to_send = 1;
7316                 programStats.movelist[0] = '\0';
7317
7318                 SendProgramStatsToFrontend( cps, &programStats );
7319
7320                 return;
7321
7322             } else if (strncmp(message,"++",2) == 0) {
7323                 /* Crafty 9.29+ outputs this */
7324                 programStats.got_fail = 2;
7325                 return;
7326
7327             } else if (strncmp(message,"--",2) == 0) {
7328                 /* Crafty 9.29+ outputs this */
7329                 programStats.got_fail = 1;
7330                 return;
7331
7332             } else if (thinkOutput[0] != NULLCHAR &&
7333                        strncmp(message, "    ", 4) == 0) {
7334                 unsigned message_len;
7335
7336                 p = message;
7337                 while (*p && *p == ' ') p++;
7338
7339                 message_len = strlen( p );
7340
7341                 /* [AS] Avoid buffer overflow */
7342                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7343                     strcat(thinkOutput, " ");
7344                     strcat(thinkOutput, p);
7345                 }
7346
7347                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7348                     strcat(programStats.movelist, " ");
7349                     strcat(programStats.movelist, p);
7350                 }
7351
7352                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7353                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7354                     DisplayMove(currentMove - 1);
7355                 }
7356                 return;
7357             }
7358         }
7359         else {
7360             buf1[0] = NULLCHAR;
7361
7362             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7363                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7364             {
7365                 ChessProgramStats cpstats;
7366
7367                 if (plyext != ' ' && plyext != '\t') {
7368                     time *= 100;
7369                 }
7370
7371                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7372                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7373                     curscore = -curscore;
7374                 }
7375
7376                 cpstats.depth = plylev;
7377                 cpstats.nodes = nodes;
7378                 cpstats.time = time;
7379                 cpstats.score = curscore;
7380                 cpstats.got_only_move = 0;
7381                 cpstats.movelist[0] = '\0';
7382
7383                 if (buf1[0] != NULLCHAR) {
7384                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7385                 }
7386
7387                 cpstats.ok_to_send = 0;
7388                 cpstats.line_is_book = 0;
7389                 cpstats.nr_moves = 0;
7390                 cpstats.moves_left = 0;
7391
7392                 SendProgramStatsToFrontend( cps, &cpstats );
7393             }
7394         }
7395     }
7396 }
7397
7398
7399 /* Parse a game score from the character string "game", and
7400    record it as the history of the current game.  The game
7401    score is NOT assumed to start from the standard position. 
7402    The display is not updated in any way.
7403    */
7404 void
7405 ParseGameHistory(game)
7406      char *game;
7407 {
7408     ChessMove moveType;
7409     int fromX, fromY, toX, toY, boardIndex;
7410     char promoChar;
7411     char *p, *q;
7412     char buf[MSG_SIZ];
7413
7414     if (appData.debugMode)
7415       fprintf(debugFP, "Parsing game history: %s\n", game);
7416
7417     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7418     gameInfo.site = StrSave(appData.icsHost);
7419     gameInfo.date = PGNDate();
7420     gameInfo.round = StrSave("-");
7421
7422     /* Parse out names of players */
7423     while (*game == ' ') game++;
7424     p = buf;
7425     while (*game != ' ') *p++ = *game++;
7426     *p = NULLCHAR;
7427     gameInfo.white = StrSave(buf);
7428     while (*game == ' ') game++;
7429     p = buf;
7430     while (*game != ' ' && *game != '\n') *p++ = *game++;
7431     *p = NULLCHAR;
7432     gameInfo.black = StrSave(buf);
7433
7434     /* Parse moves */
7435     boardIndex = blackPlaysFirst ? 1 : 0;
7436     yynewstr(game);
7437     for (;;) {
7438         yyboardindex = boardIndex;
7439         moveType = (ChessMove) yylex();
7440         switch (moveType) {
7441           case IllegalMove:             /* maybe suicide chess, etc. */
7442   if (appData.debugMode) {
7443     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7444     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7445     setbuf(debugFP, NULL);
7446   }
7447           case WhitePromotionChancellor:
7448           case BlackPromotionChancellor:
7449           case WhitePromotionArchbishop:
7450           case BlackPromotionArchbishop:
7451           case WhitePromotionQueen:
7452           case BlackPromotionQueen:
7453           case WhitePromotionRook:
7454           case BlackPromotionRook:
7455           case WhitePromotionBishop:
7456           case BlackPromotionBishop:
7457           case WhitePromotionKnight:
7458           case BlackPromotionKnight:
7459           case WhitePromotionKing:
7460           case BlackPromotionKing:
7461           case NormalMove:
7462           case WhiteCapturesEnPassant:
7463           case BlackCapturesEnPassant:
7464           case WhiteKingSideCastle:
7465           case WhiteQueenSideCastle:
7466           case BlackKingSideCastle:
7467           case BlackQueenSideCastle:
7468           case WhiteKingSideCastleWild:
7469           case WhiteQueenSideCastleWild:
7470           case BlackKingSideCastleWild:
7471           case BlackQueenSideCastleWild:
7472           /* PUSH Fabien */
7473           case WhiteHSideCastleFR:
7474           case WhiteASideCastleFR:
7475           case BlackHSideCastleFR:
7476           case BlackASideCastleFR:
7477           /* POP Fabien */
7478             fromX = currentMoveString[0] - AAA;
7479             fromY = currentMoveString[1] - ONE;
7480             toX = currentMoveString[2] - AAA;
7481             toY = currentMoveString[3] - ONE;
7482             promoChar = currentMoveString[4];
7483             break;
7484           case WhiteDrop:
7485           case BlackDrop:
7486             fromX = moveType == WhiteDrop ?
7487               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7488             (int) CharToPiece(ToLower(currentMoveString[0]));
7489             fromY = DROP_RANK;
7490             toX = currentMoveString[2] - AAA;
7491             toY = currentMoveString[3] - ONE;
7492             promoChar = NULLCHAR;
7493             break;
7494           case AmbiguousMove:
7495             /* bug? */
7496             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7497   if (appData.debugMode) {
7498     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7499     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7500     setbuf(debugFP, NULL);
7501   }
7502             DisplayError(buf, 0);
7503             return;
7504           case ImpossibleMove:
7505             /* bug? */
7506             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7507   if (appData.debugMode) {
7508     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7509     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7510     setbuf(debugFP, NULL);
7511   }
7512             DisplayError(buf, 0);
7513             return;
7514           case (ChessMove) 0:   /* end of file */
7515             if (boardIndex < backwardMostMove) {
7516                 /* Oops, gap.  How did that happen? */
7517                 DisplayError(_("Gap in move list"), 0);
7518                 return;
7519             }
7520             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7521             if (boardIndex > forwardMostMove) {
7522                 forwardMostMove = boardIndex;
7523             }
7524             return;
7525           case ElapsedTime:
7526             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7527                 strcat(parseList[boardIndex-1], " ");
7528                 strcat(parseList[boardIndex-1], yy_text);
7529             }
7530             continue;
7531           case Comment:
7532           case PGNTag:
7533           case NAG:
7534           default:
7535             /* ignore */
7536             continue;
7537           case WhiteWins:
7538           case BlackWins:
7539           case GameIsDrawn:
7540           case GameUnfinished:
7541             if (gameMode == IcsExamining) {
7542                 if (boardIndex < backwardMostMove) {
7543                     /* Oops, gap.  How did that happen? */
7544                     return;
7545                 }
7546                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7547                 return;
7548             }
7549             gameInfo.result = moveType;
7550             p = strchr(yy_text, '{');
7551             if (p == NULL) p = strchr(yy_text, '(');
7552             if (p == NULL) {
7553                 p = yy_text;
7554                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7555             } else {
7556                 q = strchr(p, *p == '{' ? '}' : ')');
7557                 if (q != NULL) *q = NULLCHAR;
7558                 p++;
7559             }
7560             gameInfo.resultDetails = StrSave(p);
7561             continue;
7562         }
7563         if (boardIndex >= forwardMostMove &&
7564             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7565             backwardMostMove = blackPlaysFirst ? 1 : 0;
7566             return;
7567         }
7568         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7569                                  fromY, fromX, toY, toX, promoChar,
7570                                  parseList[boardIndex]);
7571         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7572         /* currentMoveString is set as a side-effect of yylex */
7573         strcpy(moveList[boardIndex], currentMoveString);
7574         strcat(moveList[boardIndex], "\n");
7575         boardIndex++;
7576         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7577         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7578           case MT_NONE:
7579           case MT_STALEMATE:
7580           default:
7581             break;
7582           case MT_CHECK:
7583             if(gameInfo.variant != VariantShogi)
7584                 strcat(parseList[boardIndex - 1], "+");
7585             break;
7586           case MT_CHECKMATE:
7587           case MT_STAINMATE:
7588             strcat(parseList[boardIndex - 1], "#");
7589             break;
7590         }
7591     }
7592 }
7593
7594
7595 /* Apply a move to the given board  */
7596 void
7597 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7598      int fromX, fromY, toX, toY;
7599      int promoChar;
7600      Board board;
7601 {
7602   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7603   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7604
7605     /* [HGM] compute & store e.p. status and castling rights for new position */
7606     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7607     { int i;
7608
7609       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7610       oldEP = (signed char)board[EP_STATUS];
7611       board[EP_STATUS] = EP_NONE;
7612
7613       if( board[toY][toX] != EmptySquare ) 
7614            board[EP_STATUS] = EP_CAPTURE;  
7615
7616       if( board[fromY][fromX] == WhitePawn ) {
7617            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7618                board[EP_STATUS] = EP_PAWN_MOVE;
7619            if( toY-fromY==2) {
7620                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7621                         gameInfo.variant != VariantBerolina || toX < fromX)
7622                       board[EP_STATUS] = toX | berolina;
7623                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7624                         gameInfo.variant != VariantBerolina || toX > fromX) 
7625                       board[EP_STATUS] = toX;
7626            }
7627       } else 
7628       if( board[fromY][fromX] == BlackPawn ) {
7629            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7630                board[EP_STATUS] = EP_PAWN_MOVE; 
7631            if( toY-fromY== -2) {
7632                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7633                         gameInfo.variant != VariantBerolina || toX < fromX)
7634                       board[EP_STATUS] = toX | berolina;
7635                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7636                         gameInfo.variant != VariantBerolina || toX > fromX) 
7637                       board[EP_STATUS] = toX;
7638            }
7639        }
7640
7641        for(i=0; i<nrCastlingRights; i++) {
7642            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7643               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7644              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7645        }
7646
7647     }
7648
7649   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7650   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7651        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7652          
7653   if (fromX == toX && fromY == toY) return;
7654
7655   if (fromY == DROP_RANK) {
7656         /* must be first */
7657         piece = board[toY][toX] = (ChessSquare) fromX;
7658   } else {
7659      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7660      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7661      if(gameInfo.variant == VariantKnightmate)
7662          king += (int) WhiteUnicorn - (int) WhiteKing;
7663
7664     /* Code added by Tord: */
7665     /* FRC castling assumed when king captures friendly rook. */
7666     if (board[fromY][fromX] == WhiteKing &&
7667              board[toY][toX] == WhiteRook) {
7668       board[fromY][fromX] = EmptySquare;
7669       board[toY][toX] = EmptySquare;
7670       if(toX > fromX) {
7671         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7672       } else {
7673         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7674       }
7675     } else if (board[fromY][fromX] == BlackKing &&
7676                board[toY][toX] == BlackRook) {
7677       board[fromY][fromX] = EmptySquare;
7678       board[toY][toX] = EmptySquare;
7679       if(toX > fromX) {
7680         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7681       } else {
7682         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7683       }
7684     /* End of code added by Tord */
7685
7686     } else if (board[fromY][fromX] == king
7687         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7688         && toY == fromY && toX > fromX+1) {
7689         board[fromY][fromX] = EmptySquare;
7690         board[toY][toX] = king;
7691         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7692         board[fromY][BOARD_RGHT-1] = EmptySquare;
7693     } else if (board[fromY][fromX] == king
7694         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7695                && toY == fromY && toX < fromX-1) {
7696         board[fromY][fromX] = EmptySquare;
7697         board[toY][toX] = king;
7698         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7699         board[fromY][BOARD_LEFT] = EmptySquare;
7700     } else if (board[fromY][fromX] == WhitePawn
7701                && toY >= BOARD_HEIGHT-promoRank
7702                && gameInfo.variant != VariantXiangqi
7703                ) {
7704         /* white pawn promotion */
7705         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7706         if (board[toY][toX] == EmptySquare) {
7707             board[toY][toX] = WhiteQueen;
7708         }
7709         if(gameInfo.variant==VariantBughouse ||
7710            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7711             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7712         board[fromY][fromX] = EmptySquare;
7713     } else if ((fromY == BOARD_HEIGHT-4)
7714                && (toX != fromX)
7715                && gameInfo.variant != VariantXiangqi
7716                && gameInfo.variant != VariantBerolina
7717                && (board[fromY][fromX] == WhitePawn)
7718                && (board[toY][toX] == EmptySquare)) {
7719         board[fromY][fromX] = EmptySquare;
7720         board[toY][toX] = WhitePawn;
7721         captured = board[toY - 1][toX];
7722         board[toY - 1][toX] = EmptySquare;
7723     } else if ((fromY == BOARD_HEIGHT-4)
7724                && (toX == fromX)
7725                && gameInfo.variant == VariantBerolina
7726                && (board[fromY][fromX] == WhitePawn)
7727                && (board[toY][toX] == EmptySquare)) {
7728         board[fromY][fromX] = EmptySquare;
7729         board[toY][toX] = WhitePawn;
7730         if(oldEP & EP_BEROLIN_A) {
7731                 captured = board[fromY][fromX-1];
7732                 board[fromY][fromX-1] = EmptySquare;
7733         }else{  captured = board[fromY][fromX+1];
7734                 board[fromY][fromX+1] = EmptySquare;
7735         }
7736     } else if (board[fromY][fromX] == king
7737         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7738                && toY == fromY && toX > fromX+1) {
7739         board[fromY][fromX] = EmptySquare;
7740         board[toY][toX] = king;
7741         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7742         board[fromY][BOARD_RGHT-1] = EmptySquare;
7743     } else if (board[fromY][fromX] == king
7744         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7745                && toY == fromY && toX < fromX-1) {
7746         board[fromY][fromX] = EmptySquare;
7747         board[toY][toX] = king;
7748         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7749         board[fromY][BOARD_LEFT] = EmptySquare;
7750     } else if (fromY == 7 && fromX == 3
7751                && board[fromY][fromX] == BlackKing
7752                && toY == 7 && toX == 5) {
7753         board[fromY][fromX] = EmptySquare;
7754         board[toY][toX] = BlackKing;
7755         board[fromY][7] = EmptySquare;
7756         board[toY][4] = BlackRook;
7757     } else if (fromY == 7 && fromX == 3
7758                && board[fromY][fromX] == BlackKing
7759                && toY == 7 && toX == 1) {
7760         board[fromY][fromX] = EmptySquare;
7761         board[toY][toX] = BlackKing;
7762         board[fromY][0] = EmptySquare;
7763         board[toY][2] = BlackRook;
7764     } else if (board[fromY][fromX] == BlackPawn
7765                && toY < promoRank
7766                && gameInfo.variant != VariantXiangqi
7767                ) {
7768         /* black pawn promotion */
7769         board[toY][toX] = CharToPiece(ToLower(promoChar));
7770         if (board[toY][toX] == EmptySquare) {
7771             board[toY][toX] = BlackQueen;
7772         }
7773         if(gameInfo.variant==VariantBughouse ||
7774            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7775             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7776         board[fromY][fromX] = EmptySquare;
7777     } else if ((fromY == 3)
7778                && (toX != fromX)
7779                && gameInfo.variant != VariantXiangqi
7780                && gameInfo.variant != VariantBerolina
7781                && (board[fromY][fromX] == BlackPawn)
7782                && (board[toY][toX] == EmptySquare)) {
7783         board[fromY][fromX] = EmptySquare;
7784         board[toY][toX] = BlackPawn;
7785         captured = board[toY + 1][toX];
7786         board[toY + 1][toX] = EmptySquare;
7787     } else if ((fromY == 3)
7788                && (toX == fromX)
7789                && gameInfo.variant == VariantBerolina
7790                && (board[fromY][fromX] == BlackPawn)
7791                && (board[toY][toX] == EmptySquare)) {
7792         board[fromY][fromX] = EmptySquare;
7793         board[toY][toX] = BlackPawn;
7794         if(oldEP & EP_BEROLIN_A) {
7795                 captured = board[fromY][fromX-1];
7796                 board[fromY][fromX-1] = EmptySquare;
7797         }else{  captured = board[fromY][fromX+1];
7798                 board[fromY][fromX+1] = EmptySquare;
7799         }
7800     } else {
7801         board[toY][toX] = board[fromY][fromX];
7802         board[fromY][fromX] = EmptySquare;
7803     }
7804
7805     /* [HGM] now we promote for Shogi, if needed */
7806     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7807         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7808   }
7809
7810     if (gameInfo.holdingsWidth != 0) {
7811
7812       /* !!A lot more code needs to be written to support holdings  */
7813       /* [HGM] OK, so I have written it. Holdings are stored in the */
7814       /* penultimate board files, so they are automaticlly stored   */
7815       /* in the game history.                                       */
7816       if (fromY == DROP_RANK) {
7817         /* Delete from holdings, by decreasing count */
7818         /* and erasing image if necessary            */
7819         p = (int) fromX;
7820         if(p < (int) BlackPawn) { /* white drop */
7821              p -= (int)WhitePawn;
7822                  p = PieceToNumber((ChessSquare)p);
7823              if(p >= gameInfo.holdingsSize) p = 0;
7824              if(--board[p][BOARD_WIDTH-2] <= 0)
7825                   board[p][BOARD_WIDTH-1] = EmptySquare;
7826              if((int)board[p][BOARD_WIDTH-2] < 0)
7827                         board[p][BOARD_WIDTH-2] = 0;
7828         } else {                  /* black drop */
7829              p -= (int)BlackPawn;
7830                  p = PieceToNumber((ChessSquare)p);
7831              if(p >= gameInfo.holdingsSize) p = 0;
7832              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7833                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7834              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7835                         board[BOARD_HEIGHT-1-p][1] = 0;
7836         }
7837       }
7838       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7839           && gameInfo.variant != VariantBughouse        ) {
7840         /* [HGM] holdings: Add to holdings, if holdings exist */
7841         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7842                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7843                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7844         }
7845         p = (int) captured;
7846         if (p >= (int) BlackPawn) {
7847           p -= (int)BlackPawn;
7848           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7849                   /* in Shogi restore piece to its original  first */
7850                   captured = (ChessSquare) (DEMOTED captured);
7851                   p = DEMOTED p;
7852           }
7853           p = PieceToNumber((ChessSquare)p);
7854           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7855           board[p][BOARD_WIDTH-2]++;
7856           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7857         } else {
7858           p -= (int)WhitePawn;
7859           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7860                   captured = (ChessSquare) (DEMOTED captured);
7861                   p = DEMOTED p;
7862           }
7863           p = PieceToNumber((ChessSquare)p);
7864           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7865           board[BOARD_HEIGHT-1-p][1]++;
7866           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7867         }
7868       }
7869     } else if (gameInfo.variant == VariantAtomic) {
7870       if (captured != EmptySquare) {
7871         int y, x;
7872         for (y = toY-1; y <= toY+1; y++) {
7873           for (x = toX-1; x <= toX+1; x++) {
7874             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7875                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7876               board[y][x] = EmptySquare;
7877             }
7878           }
7879         }
7880         board[toY][toX] = EmptySquare;
7881       }
7882     }
7883     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7884         /* [HGM] Shogi promotions */
7885         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7886     }
7887
7888     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7889                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7890         // [HGM] superchess: take promotion piece out of holdings
7891         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7892         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7893             if(!--board[k][BOARD_WIDTH-2])
7894                 board[k][BOARD_WIDTH-1] = EmptySquare;
7895         } else {
7896             if(!--board[BOARD_HEIGHT-1-k][1])
7897                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7898         }
7899     }
7900
7901 }
7902
7903 /* Updates forwardMostMove */
7904 void
7905 MakeMove(fromX, fromY, toX, toY, promoChar)
7906      int fromX, fromY, toX, toY;
7907      int promoChar;
7908 {
7909 //    forwardMostMove++; // [HGM] bare: moved downstream
7910
7911     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7912         int timeLeft; static int lastLoadFlag=0; int king, piece;
7913         piece = boards[forwardMostMove][fromY][fromX];
7914         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7915         if(gameInfo.variant == VariantKnightmate)
7916             king += (int) WhiteUnicorn - (int) WhiteKing;
7917         if(forwardMostMove == 0) {
7918             if(blackPlaysFirst) 
7919                 fprintf(serverMoves, "%s;", second.tidy);
7920             fprintf(serverMoves, "%s;", first.tidy);
7921             if(!blackPlaysFirst) 
7922                 fprintf(serverMoves, "%s;", second.tidy);
7923         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7924         lastLoadFlag = loadFlag;
7925         // print base move
7926         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7927         // print castling suffix
7928         if( toY == fromY && piece == king ) {
7929             if(toX-fromX > 1)
7930                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7931             if(fromX-toX >1)
7932                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7933         }
7934         // e.p. suffix
7935         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7936              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7937              boards[forwardMostMove][toY][toX] == EmptySquare
7938              && fromX != toX )
7939                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7940         // promotion suffix
7941         if(promoChar != NULLCHAR)
7942                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7943         if(!loadFlag) {
7944             fprintf(serverMoves, "/%d/%d",
7945                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7946             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7947             else                      timeLeft = blackTimeRemaining/1000;
7948             fprintf(serverMoves, "/%d", timeLeft);
7949         }
7950         fflush(serverMoves);
7951     }
7952
7953     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7954       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7955                         0, 1);
7956       return;
7957     }
7958     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7959     if (commentList[forwardMostMove+1] != NULL) {
7960         free(commentList[forwardMostMove+1]);
7961         commentList[forwardMostMove+1] = NULL;
7962     }
7963     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7964     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7965     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7966     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7967     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7968     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7969     gameInfo.result = GameUnfinished;
7970     if (gameInfo.resultDetails != NULL) {
7971         free(gameInfo.resultDetails);
7972         gameInfo.resultDetails = NULL;
7973     }
7974     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7975                               moveList[forwardMostMove - 1]);
7976     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7977                              PosFlags(forwardMostMove - 1),
7978                              fromY, fromX, toY, toX, promoChar,
7979                              parseList[forwardMostMove - 1]);
7980     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7981       case MT_NONE:
7982       case MT_STALEMATE:
7983       default:
7984         break;
7985       case MT_CHECK:
7986         if(gameInfo.variant != VariantShogi)
7987             strcat(parseList[forwardMostMove - 1], "+");
7988         break;
7989       case MT_CHECKMATE:
7990       case MT_STAINMATE:
7991         strcat(parseList[forwardMostMove - 1], "#");
7992         break;
7993     }
7994     if (appData.debugMode) {
7995         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7996     }
7997
7998 }
7999
8000 /* Updates currentMove if not pausing */
8001 void
8002 ShowMove(fromX, fromY, toX, toY)
8003 {
8004     int instant = (gameMode == PlayFromGameFile) ?
8005         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8006     if(appData.noGUI) return;
8007     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8008         if (!instant) {
8009             if (forwardMostMove == currentMove + 1) {
8010                 AnimateMove(boards[forwardMostMove - 1],
8011                             fromX, fromY, toX, toY);
8012             }
8013             if (appData.highlightLastMove) {
8014                 SetHighlights(fromX, fromY, toX, toY);
8015             }
8016         }
8017         currentMove = forwardMostMove;
8018     }
8019
8020     if (instant) return;
8021
8022     DisplayMove(currentMove - 1);
8023     DrawPosition(FALSE, boards[currentMove]);
8024     DisplayBothClocks();
8025     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8026 }
8027
8028 void SendEgtPath(ChessProgramState *cps)
8029 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8030         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8031
8032         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8033
8034         while(*p) {
8035             char c, *q = name+1, *r, *s;
8036
8037             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8038             while(*p && *p != ',') *q++ = *p++;
8039             *q++ = ':'; *q = 0;
8040             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8041                 strcmp(name, ",nalimov:") == 0 ) {
8042                 // take nalimov path from the menu-changeable option first, if it is defined
8043                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8044                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8045             } else
8046             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8047                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8048                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8049                 s = r = StrStr(s, ":") + 1; // beginning of path info
8050                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8051                 c = *r; *r = 0;             // temporarily null-terminate path info
8052                     *--q = 0;               // strip of trailig ':' from name
8053                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8054                 *r = c;
8055                 SendToProgram(buf,cps);     // send egtbpath command for this format
8056             }
8057             if(*p == ',') p++; // read away comma to position for next format name
8058         }
8059 }
8060
8061 void
8062 InitChessProgram(cps, setup)
8063      ChessProgramState *cps;
8064      int setup; /* [HGM] needed to setup FRC opening position */
8065 {
8066     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8067     if (appData.noChessProgram) return;
8068     hintRequested = FALSE;
8069     bookRequested = FALSE;
8070
8071     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8072     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8073     if(cps->memSize) { /* [HGM] memory */
8074         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8075         SendToProgram(buf, cps);
8076     }
8077     SendEgtPath(cps); /* [HGM] EGT */
8078     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8079         sprintf(buf, "cores %d\n", appData.smpCores);
8080         SendToProgram(buf, cps);
8081     }
8082
8083     SendToProgram(cps->initString, cps);
8084     if (gameInfo.variant != VariantNormal &&
8085         gameInfo.variant != VariantLoadable
8086         /* [HGM] also send variant if board size non-standard */
8087         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8088                                             ) {
8089       char *v = VariantName(gameInfo.variant);
8090       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8091         /* [HGM] in protocol 1 we have to assume all variants valid */
8092         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8093         DisplayFatalError(buf, 0, 1);
8094         return;
8095       }
8096
8097       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8098       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8099       if( gameInfo.variant == VariantXiangqi )
8100            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8101       if( gameInfo.variant == VariantShogi )
8102            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8103       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8104            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8105       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8106                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8107            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8108       if( gameInfo.variant == VariantCourier )
8109            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8110       if( gameInfo.variant == VariantSuper )
8111            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8112       if( gameInfo.variant == VariantGreat )
8113            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8114
8115       if(overruled) {
8116            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8117                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8118            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8119            if(StrStr(cps->variants, b) == NULL) { 
8120                // specific sized variant not known, check if general sizing allowed
8121                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8122                    if(StrStr(cps->variants, "boardsize") == NULL) {
8123                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8124                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8125                        DisplayFatalError(buf, 0, 1);
8126                        return;
8127                    }
8128                    /* [HGM] here we really should compare with the maximum supported board size */
8129                }
8130            }
8131       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8132       sprintf(buf, "variant %s\n", b);
8133       SendToProgram(buf, cps);
8134     }
8135     currentlyInitializedVariant = gameInfo.variant;
8136
8137     /* [HGM] send opening position in FRC to first engine */
8138     if(setup) {
8139           SendToProgram("force\n", cps);
8140           SendBoard(cps, 0);
8141           /* engine is now in force mode! Set flag to wake it up after first move. */
8142           setboardSpoiledMachineBlack = 1;
8143     }
8144
8145     if (cps->sendICS) {
8146       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8147       SendToProgram(buf, cps);
8148     }
8149     cps->maybeThinking = FALSE;
8150     cps->offeredDraw = 0;
8151     if (!appData.icsActive) {
8152         SendTimeControl(cps, movesPerSession, timeControl,
8153                         timeIncrement, appData.searchDepth,
8154                         searchTime);
8155     }
8156     if (appData.showThinking 
8157         // [HGM] thinking: four options require thinking output to be sent
8158         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8159                                 ) {
8160         SendToProgram("post\n", cps);
8161     }
8162     SendToProgram("hard\n", cps);
8163     if (!appData.ponderNextMove) {
8164         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8165            it without being sure what state we are in first.  "hard"
8166            is not a toggle, so that one is OK.
8167          */
8168         SendToProgram("easy\n", cps);
8169     }
8170     if (cps->usePing) {
8171       sprintf(buf, "ping %d\n", ++cps->lastPing);
8172       SendToProgram(buf, cps);
8173     }
8174     cps->initDone = TRUE;
8175 }   
8176
8177
8178 void
8179 StartChessProgram(cps)
8180      ChessProgramState *cps;
8181 {
8182     char buf[MSG_SIZ];
8183     int err;
8184
8185     if (appData.noChessProgram) return;
8186     cps->initDone = FALSE;
8187
8188     if (strcmp(cps->host, "localhost") == 0) {
8189         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8190     } else if (*appData.remoteShell == NULLCHAR) {
8191         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8192     } else {
8193         if (*appData.remoteUser == NULLCHAR) {
8194           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8195                     cps->program);
8196         } else {
8197           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8198                     cps->host, appData.remoteUser, cps->program);
8199         }
8200         err = StartChildProcess(buf, "", &cps->pr);
8201     }
8202     
8203     if (err != 0) {
8204         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8205         DisplayFatalError(buf, err, 1);
8206         cps->pr = NoProc;
8207         cps->isr = NULL;
8208         return;
8209     }
8210     
8211     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8212     if (cps->protocolVersion > 1) {
8213       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8214       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8215       cps->comboCnt = 0;  //                and values of combo boxes
8216       SendToProgram(buf, cps);
8217     } else {
8218       SendToProgram("xboard\n", cps);
8219     }
8220 }
8221
8222
8223 void
8224 TwoMachinesEventIfReady P((void))
8225 {
8226   if (first.lastPing != first.lastPong) {
8227     DisplayMessage("", _("Waiting for first chess program"));
8228     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8229     return;
8230   }
8231   if (second.lastPing != second.lastPong) {
8232     DisplayMessage("", _("Waiting for second chess program"));
8233     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8234     return;
8235   }
8236   ThawUI();
8237   TwoMachinesEvent();
8238 }
8239
8240 void
8241 NextMatchGame P((void))
8242 {
8243     int index; /* [HGM] autoinc: step load index during match */
8244     Reset(FALSE, TRUE);
8245     if (*appData.loadGameFile != NULLCHAR) {
8246         index = appData.loadGameIndex;
8247         if(index < 0) { // [HGM] autoinc
8248             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8249             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8250         } 
8251         LoadGameFromFile(appData.loadGameFile,
8252                          index,
8253                          appData.loadGameFile, FALSE);
8254     } else if (*appData.loadPositionFile != NULLCHAR) {
8255         index = appData.loadPositionIndex;
8256         if(index < 0) { // [HGM] autoinc
8257             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8258             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8259         } 
8260         LoadPositionFromFile(appData.loadPositionFile,
8261                              index,
8262                              appData.loadPositionFile);
8263     }
8264     TwoMachinesEventIfReady();
8265 }
8266
8267 void UserAdjudicationEvent( int result )
8268 {
8269     ChessMove gameResult = GameIsDrawn;
8270
8271     if( result > 0 ) {
8272         gameResult = WhiteWins;
8273     }
8274     else if( result < 0 ) {
8275         gameResult = BlackWins;
8276     }
8277
8278     if( gameMode == TwoMachinesPlay ) {
8279         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8280     }
8281 }
8282
8283
8284 // [HGM] save: calculate checksum of game to make games easily identifiable
8285 int StringCheckSum(char *s)
8286 {
8287         int i = 0;
8288         if(s==NULL) return 0;
8289         while(*s) i = i*259 + *s++;
8290         return i;
8291 }
8292
8293 int GameCheckSum()
8294 {
8295         int i, sum=0;
8296         for(i=backwardMostMove; i<forwardMostMove; i++) {
8297                 sum += pvInfoList[i].depth;
8298                 sum += StringCheckSum(parseList[i]);
8299                 sum += StringCheckSum(commentList[i]);
8300                 sum *= 261;
8301         }
8302         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8303         return sum + StringCheckSum(commentList[i]);
8304 } // end of save patch
8305
8306 void
8307 GameEnds(result, resultDetails, whosays)
8308      ChessMove result;
8309      char *resultDetails;
8310      int whosays;
8311 {
8312     GameMode nextGameMode;
8313     int isIcsGame;
8314     char buf[MSG_SIZ];
8315
8316     if(endingGame) return; /* [HGM] crash: forbid recursion */
8317     endingGame = 1;
8318
8319     if (appData.debugMode) {
8320       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8321               result, resultDetails ? resultDetails : "(null)", whosays);
8322     }
8323
8324     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8325         /* If we are playing on ICS, the server decides when the
8326            game is over, but the engine can offer to draw, claim 
8327            a draw, or resign. 
8328          */
8329 #if ZIPPY
8330         if (appData.zippyPlay && first.initDone) {
8331             if (result == GameIsDrawn) {
8332                 /* In case draw still needs to be claimed */
8333                 SendToICS(ics_prefix);
8334                 SendToICS("draw\n");
8335             } else if (StrCaseStr(resultDetails, "resign")) {
8336                 SendToICS(ics_prefix);
8337                 SendToICS("resign\n");
8338             }
8339         }
8340 #endif
8341         endingGame = 0; /* [HGM] crash */
8342         return;
8343     }
8344
8345     /* If we're loading the game from a file, stop */
8346     if (whosays == GE_FILE) {
8347       (void) StopLoadGameTimer();
8348       gameFileFP = NULL;
8349     }
8350
8351     /* Cancel draw offers */
8352     first.offeredDraw = second.offeredDraw = 0;
8353
8354     /* If this is an ICS game, only ICS can really say it's done;
8355        if not, anyone can. */
8356     isIcsGame = (gameMode == IcsPlayingWhite || 
8357                  gameMode == IcsPlayingBlack || 
8358                  gameMode == IcsObserving    || 
8359                  gameMode == IcsExamining);
8360
8361     if (!isIcsGame || whosays == GE_ICS) {
8362         /* OK -- not an ICS game, or ICS said it was done */
8363         StopClocks();
8364         if (!isIcsGame && !appData.noChessProgram) 
8365           SetUserThinkingEnables();
8366     
8367         /* [HGM] if a machine claims the game end we verify this claim */
8368         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8369             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8370                 char claimer;
8371                 ChessMove trueResult = (ChessMove) -1;
8372
8373                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8374                                             first.twoMachinesColor[0] :
8375                                             second.twoMachinesColor[0] ;
8376
8377                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8378                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8379                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8380                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8381                 } else
8382                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8383                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8384                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8385                 } else
8386                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8387                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8388                 }
8389
8390                 // now verify win claims, but not in drop games, as we don't understand those yet
8391                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8392                                                  || gameInfo.variant == VariantGreat) &&
8393                     (result == WhiteWins && claimer == 'w' ||
8394                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8395                       if (appData.debugMode) {
8396                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8397                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8398                       }
8399                       if(result != trueResult) {
8400                               sprintf(buf, "False win claim: '%s'", resultDetails);
8401                               result = claimer == 'w' ? BlackWins : WhiteWins;
8402                               resultDetails = buf;
8403                       }
8404                 } else
8405                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8406                     && (forwardMostMove <= backwardMostMove ||
8407                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8408                         (claimer=='b')==(forwardMostMove&1))
8409                                                                                   ) {
8410                       /* [HGM] verify: draws that were not flagged are false claims */
8411                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8412                       result = claimer == 'w' ? BlackWins : WhiteWins;
8413                       resultDetails = buf;
8414                 }
8415                 /* (Claiming a loss is accepted no questions asked!) */
8416             }
8417             /* [HGM] bare: don't allow bare King to win */
8418             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8419                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8420                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8421                && result != GameIsDrawn)
8422             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8423                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8424                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8425                         if(p >= 0 && p <= (int)WhiteKing) k++;
8426                 }
8427                 if (appData.debugMode) {
8428                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8429                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8430                 }
8431                 if(k <= 1) {
8432                         result = GameIsDrawn;
8433                         sprintf(buf, "%s but bare king", resultDetails);
8434                         resultDetails = buf;
8435                 }
8436             }
8437         }
8438
8439
8440         if(serverMoves != NULL && !loadFlag) { char c = '=';
8441             if(result==WhiteWins) c = '+';
8442             if(result==BlackWins) c = '-';
8443             if(resultDetails != NULL)
8444                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8445         }
8446         if (resultDetails != NULL) {
8447             gameInfo.result = result;
8448             gameInfo.resultDetails = StrSave(resultDetails);
8449
8450             /* display last move only if game was not loaded from file */
8451             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8452                 DisplayMove(currentMove - 1);
8453     
8454             if (forwardMostMove != 0) {
8455                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8456                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8457                                                                 ) {
8458                     if (*appData.saveGameFile != NULLCHAR) {
8459                         SaveGameToFile(appData.saveGameFile, TRUE);
8460                     } else if (appData.autoSaveGames) {
8461                         AutoSaveGame();
8462                     }
8463                     if (*appData.savePositionFile != NULLCHAR) {
8464                         SavePositionToFile(appData.savePositionFile);
8465                     }
8466                 }
8467             }
8468
8469             /* Tell program how game ended in case it is learning */
8470             /* [HGM] Moved this to after saving the PGN, just in case */
8471             /* engine died and we got here through time loss. In that */
8472             /* case we will get a fatal error writing the pipe, which */
8473             /* would otherwise lose us the PGN.                       */
8474             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8475             /* output during GameEnds should never be fatal anymore   */
8476             if (gameMode == MachinePlaysWhite ||
8477                 gameMode == MachinePlaysBlack ||
8478                 gameMode == TwoMachinesPlay ||
8479                 gameMode == IcsPlayingWhite ||
8480                 gameMode == IcsPlayingBlack ||
8481                 gameMode == BeginningOfGame) {
8482                 char buf[MSG_SIZ];
8483                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8484                         resultDetails);
8485                 if (first.pr != NoProc) {
8486                     SendToProgram(buf, &first);
8487                 }
8488                 if (second.pr != NoProc &&
8489                     gameMode == TwoMachinesPlay) {
8490                     SendToProgram(buf, &second);
8491                 }
8492             }
8493         }
8494
8495         if (appData.icsActive) {
8496             if (appData.quietPlay &&
8497                 (gameMode == IcsPlayingWhite ||
8498                  gameMode == IcsPlayingBlack)) {
8499                 SendToICS(ics_prefix);
8500                 SendToICS("set shout 1\n");
8501             }
8502             nextGameMode = IcsIdle;
8503             ics_user_moved = FALSE;
8504             /* clean up premove.  It's ugly when the game has ended and the
8505              * premove highlights are still on the board.
8506              */
8507             if (gotPremove) {
8508               gotPremove = FALSE;
8509               ClearPremoveHighlights();
8510               DrawPosition(FALSE, boards[currentMove]);
8511             }
8512             if (whosays == GE_ICS) {
8513                 switch (result) {
8514                 case WhiteWins:
8515                     if (gameMode == IcsPlayingWhite)
8516                         PlayIcsWinSound();
8517                     else if(gameMode == IcsPlayingBlack)
8518                         PlayIcsLossSound();
8519                     break;
8520                 case BlackWins:
8521                     if (gameMode == IcsPlayingBlack)
8522                         PlayIcsWinSound();
8523                     else if(gameMode == IcsPlayingWhite)
8524                         PlayIcsLossSound();
8525                     break;
8526                 case GameIsDrawn:
8527                     PlayIcsDrawSound();
8528                     break;
8529                 default:
8530                     PlayIcsUnfinishedSound();
8531                 }
8532             }
8533         } else if (gameMode == EditGame ||
8534                    gameMode == PlayFromGameFile || 
8535                    gameMode == AnalyzeMode || 
8536                    gameMode == AnalyzeFile) {
8537             nextGameMode = gameMode;
8538         } else {
8539             nextGameMode = EndOfGame;
8540         }
8541         pausing = FALSE;
8542         ModeHighlight();
8543     } else {
8544         nextGameMode = gameMode;
8545     }
8546
8547     if (appData.noChessProgram) {
8548         gameMode = nextGameMode;
8549         ModeHighlight();
8550         endingGame = 0; /* [HGM] crash */
8551         return;
8552     }
8553
8554     if (first.reuse) {
8555         /* Put first chess program into idle state */
8556         if (first.pr != NoProc &&
8557             (gameMode == MachinePlaysWhite ||
8558              gameMode == MachinePlaysBlack ||
8559              gameMode == TwoMachinesPlay ||
8560              gameMode == IcsPlayingWhite ||
8561              gameMode == IcsPlayingBlack ||
8562              gameMode == BeginningOfGame)) {
8563             SendToProgram("force\n", &first);
8564             if (first.usePing) {
8565               char buf[MSG_SIZ];
8566               sprintf(buf, "ping %d\n", ++first.lastPing);
8567               SendToProgram(buf, &first);
8568             }
8569         }
8570     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8571         /* Kill off first chess program */
8572         if (first.isr != NULL)
8573           RemoveInputSource(first.isr);
8574         first.isr = NULL;
8575     
8576         if (first.pr != NoProc) {
8577             ExitAnalyzeMode();
8578             DoSleep( appData.delayBeforeQuit );
8579             SendToProgram("quit\n", &first);
8580             DoSleep( appData.delayAfterQuit );
8581             DestroyChildProcess(first.pr, first.useSigterm);
8582         }
8583         first.pr = NoProc;
8584     }
8585     if (second.reuse) {
8586         /* Put second chess program into idle state */
8587         if (second.pr != NoProc &&
8588             gameMode == TwoMachinesPlay) {
8589             SendToProgram("force\n", &second);
8590             if (second.usePing) {
8591               char buf[MSG_SIZ];
8592               sprintf(buf, "ping %d\n", ++second.lastPing);
8593               SendToProgram(buf, &second);
8594             }
8595         }
8596     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8597         /* Kill off second chess program */
8598         if (second.isr != NULL)
8599           RemoveInputSource(second.isr);
8600         second.isr = NULL;
8601     
8602         if (second.pr != NoProc) {
8603             DoSleep( appData.delayBeforeQuit );
8604             SendToProgram("quit\n", &second);
8605             DoSleep( appData.delayAfterQuit );
8606             DestroyChildProcess(second.pr, second.useSigterm);
8607         }
8608         second.pr = NoProc;
8609     }
8610
8611     if (matchMode && gameMode == TwoMachinesPlay) {
8612         switch (result) {
8613         case WhiteWins:
8614           if (first.twoMachinesColor[0] == 'w') {
8615             first.matchWins++;
8616           } else {
8617             second.matchWins++;
8618           }
8619           break;
8620         case BlackWins:
8621           if (first.twoMachinesColor[0] == 'b') {
8622             first.matchWins++;
8623           } else {
8624             second.matchWins++;
8625           }
8626           break;
8627         default:
8628           break;
8629         }
8630         if (matchGame < appData.matchGames) {
8631             char *tmp;
8632             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8633                 tmp = first.twoMachinesColor;
8634                 first.twoMachinesColor = second.twoMachinesColor;
8635                 second.twoMachinesColor = tmp;
8636             }
8637             gameMode = nextGameMode;
8638             matchGame++;
8639             if(appData.matchPause>10000 || appData.matchPause<10)
8640                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8641             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8642             endingGame = 0; /* [HGM] crash */
8643             return;
8644         } else {
8645             char buf[MSG_SIZ];
8646             gameMode = nextGameMode;
8647             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8648                     first.tidy, second.tidy,
8649                     first.matchWins, second.matchWins,
8650                     appData.matchGames - (first.matchWins + second.matchWins));
8651             DisplayFatalError(buf, 0, 0);
8652         }
8653     }
8654     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8655         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8656       ExitAnalyzeMode();
8657     gameMode = nextGameMode;
8658     ModeHighlight();
8659     endingGame = 0;  /* [HGM] crash */
8660 }
8661
8662 /* Assumes program was just initialized (initString sent).
8663    Leaves program in force mode. */
8664 void
8665 FeedMovesToProgram(cps, upto) 
8666      ChessProgramState *cps;
8667      int upto;
8668 {
8669     int i;
8670     
8671     if (appData.debugMode)
8672       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8673               startedFromSetupPosition ? "position and " : "",
8674               backwardMostMove, upto, cps->which);
8675     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8676         // [HGM] variantswitch: make engine aware of new variant
8677         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8678                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8679         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8680         SendToProgram(buf, cps);
8681         currentlyInitializedVariant = gameInfo.variant;
8682     }
8683     SendToProgram("force\n", cps);
8684     if (startedFromSetupPosition) {
8685         SendBoard(cps, backwardMostMove);
8686     if (appData.debugMode) {
8687         fprintf(debugFP, "feedMoves\n");
8688     }
8689     }
8690     for (i = backwardMostMove; i < upto; i++) {
8691         SendMoveToProgram(i, cps);
8692     }
8693 }
8694
8695
8696 void
8697 ResurrectChessProgram()
8698 {
8699      /* The chess program may have exited.
8700         If so, restart it and feed it all the moves made so far. */
8701
8702     if (appData.noChessProgram || first.pr != NoProc) return;
8703     
8704     StartChessProgram(&first);
8705     InitChessProgram(&first, FALSE);
8706     FeedMovesToProgram(&first, currentMove);
8707
8708     if (!first.sendTime) {
8709         /* can't tell gnuchess what its clock should read,
8710            so we bow to its notion. */
8711         ResetClocks();
8712         timeRemaining[0][currentMove] = whiteTimeRemaining;
8713         timeRemaining[1][currentMove] = blackTimeRemaining;
8714     }
8715
8716     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8717                 appData.icsEngineAnalyze) && first.analysisSupport) {
8718       SendToProgram("analyze\n", &first);
8719       first.analyzing = TRUE;
8720     }
8721 }
8722
8723 /*
8724  * Button procedures
8725  */
8726 void
8727 Reset(redraw, init)
8728      int redraw, init;
8729 {
8730     int i;
8731
8732     if (appData.debugMode) {
8733         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8734                 redraw, init, gameMode);
8735     }
8736     CleanupTail(); // [HGM] vari: delete any stored variations
8737     pausing = pauseExamInvalid = FALSE;
8738     startedFromSetupPosition = blackPlaysFirst = FALSE;
8739     firstMove = TRUE;
8740     whiteFlag = blackFlag = FALSE;
8741     userOfferedDraw = FALSE;
8742     hintRequested = bookRequested = FALSE;
8743     first.maybeThinking = FALSE;
8744     second.maybeThinking = FALSE;
8745     first.bookSuspend = FALSE; // [HGM] book
8746     second.bookSuspend = FALSE;
8747     thinkOutput[0] = NULLCHAR;
8748     lastHint[0] = NULLCHAR;
8749     ClearGameInfo(&gameInfo);
8750     gameInfo.variant = StringToVariant(appData.variant);
8751     ics_user_moved = ics_clock_paused = FALSE;
8752     ics_getting_history = H_FALSE;
8753     ics_gamenum = -1;
8754     white_holding[0] = black_holding[0] = NULLCHAR;
8755     ClearProgramStats();
8756     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8757     
8758     ResetFrontEnd();
8759     ClearHighlights();
8760     flipView = appData.flipView;
8761     ClearPremoveHighlights();
8762     gotPremove = FALSE;
8763     alarmSounded = FALSE;
8764
8765     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8766     if(appData.serverMovesName != NULL) {
8767         /* [HGM] prepare to make moves file for broadcasting */
8768         clock_t t = clock();
8769         if(serverMoves != NULL) fclose(serverMoves);
8770         serverMoves = fopen(appData.serverMovesName, "r");
8771         if(serverMoves != NULL) {
8772             fclose(serverMoves);
8773             /* delay 15 sec before overwriting, so all clients can see end */
8774             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8775         }
8776         serverMoves = fopen(appData.serverMovesName, "w");
8777     }
8778
8779     ExitAnalyzeMode();
8780     gameMode = BeginningOfGame;
8781     ModeHighlight();
8782     if(appData.icsActive) gameInfo.variant = VariantNormal;
8783     currentMove = forwardMostMove = backwardMostMove = 0;
8784     InitPosition(redraw);
8785     for (i = 0; i < MAX_MOVES; i++) {
8786         if (commentList[i] != NULL) {
8787             free(commentList[i]);
8788             commentList[i] = NULL;
8789         }
8790     }
8791     ResetClocks();
8792     timeRemaining[0][0] = whiteTimeRemaining;
8793     timeRemaining[1][0] = blackTimeRemaining;
8794     if (first.pr == NULL) {
8795         StartChessProgram(&first);
8796     }
8797     if (init) {
8798             InitChessProgram(&first, startedFromSetupPosition);
8799     }
8800     DisplayTitle("");
8801     DisplayMessage("", "");
8802     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8803     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8804 }
8805
8806 void
8807 AutoPlayGameLoop()
8808 {
8809     for (;;) {
8810         if (!AutoPlayOneMove())
8811           return;
8812         if (matchMode || appData.timeDelay == 0)
8813           continue;
8814         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8815           return;
8816         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8817         break;
8818     }
8819 }
8820
8821
8822 int
8823 AutoPlayOneMove()
8824 {
8825     int fromX, fromY, toX, toY;
8826
8827     if (appData.debugMode) {
8828       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8829     }
8830
8831     if (gameMode != PlayFromGameFile)
8832       return FALSE;
8833
8834     if (currentMove >= forwardMostMove) {
8835       gameMode = EditGame;
8836       ModeHighlight();
8837
8838       /* [AS] Clear current move marker at the end of a game */
8839       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8840
8841       return FALSE;
8842     }
8843     
8844     toX = moveList[currentMove][2] - AAA;
8845     toY = moveList[currentMove][3] - ONE;
8846
8847     if (moveList[currentMove][1] == '@') {
8848         if (appData.highlightLastMove) {
8849             SetHighlights(-1, -1, toX, toY);
8850         }
8851     } else {
8852         fromX = moveList[currentMove][0] - AAA;
8853         fromY = moveList[currentMove][1] - ONE;
8854
8855         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8856
8857         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8858
8859         if (appData.highlightLastMove) {
8860             SetHighlights(fromX, fromY, toX, toY);
8861         }
8862     }
8863     DisplayMove(currentMove);
8864     SendMoveToProgram(currentMove++, &first);
8865     DisplayBothClocks();
8866     DrawPosition(FALSE, boards[currentMove]);
8867     // [HGM] PV info: always display, routine tests if empty
8868     DisplayComment(currentMove - 1, commentList[currentMove]);
8869     return TRUE;
8870 }
8871
8872
8873 int
8874 LoadGameOneMove(readAhead)
8875      ChessMove readAhead;
8876 {
8877     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8878     char promoChar = NULLCHAR;
8879     ChessMove moveType;
8880     char move[MSG_SIZ];
8881     char *p, *q;
8882     
8883     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8884         gameMode != AnalyzeMode && gameMode != Training) {
8885         gameFileFP = NULL;
8886         return FALSE;
8887     }
8888     
8889     yyboardindex = forwardMostMove;
8890     if (readAhead != (ChessMove)0) {
8891       moveType = readAhead;
8892     } else {
8893       if (gameFileFP == NULL)
8894           return FALSE;
8895       moveType = (ChessMove) yylex();
8896     }
8897     
8898     done = FALSE;
8899     switch (moveType) {
8900       case Comment:
8901         if (appData.debugMode) 
8902           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8903         p = yy_text;
8904
8905         /* append the comment but don't display it */
8906         AppendComment(currentMove, p, FALSE);
8907         return TRUE;
8908
8909       case WhiteCapturesEnPassant:
8910       case BlackCapturesEnPassant:
8911       case WhitePromotionChancellor:
8912       case BlackPromotionChancellor:
8913       case WhitePromotionArchbishop:
8914       case BlackPromotionArchbishop:
8915       case WhitePromotionCentaur:
8916       case BlackPromotionCentaur:
8917       case WhitePromotionQueen:
8918       case BlackPromotionQueen:
8919       case WhitePromotionRook:
8920       case BlackPromotionRook:
8921       case WhitePromotionBishop:
8922       case BlackPromotionBishop:
8923       case WhitePromotionKnight:
8924       case BlackPromotionKnight:
8925       case WhitePromotionKing:
8926       case BlackPromotionKing:
8927       case NormalMove:
8928       case WhiteKingSideCastle:
8929       case WhiteQueenSideCastle:
8930       case BlackKingSideCastle:
8931       case BlackQueenSideCastle:
8932       case WhiteKingSideCastleWild:
8933       case WhiteQueenSideCastleWild:
8934       case BlackKingSideCastleWild:
8935       case BlackQueenSideCastleWild:
8936       /* PUSH Fabien */
8937       case WhiteHSideCastleFR:
8938       case WhiteASideCastleFR:
8939       case BlackHSideCastleFR:
8940       case BlackASideCastleFR:
8941       /* POP Fabien */
8942         if (appData.debugMode)
8943           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8944         fromX = currentMoveString[0] - AAA;
8945         fromY = currentMoveString[1] - ONE;
8946         toX = currentMoveString[2] - AAA;
8947         toY = currentMoveString[3] - ONE;
8948         promoChar = currentMoveString[4];
8949         break;
8950
8951       case WhiteDrop:
8952       case BlackDrop:
8953         if (appData.debugMode)
8954           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8955         fromX = moveType == WhiteDrop ?
8956           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8957         (int) CharToPiece(ToLower(currentMoveString[0]));
8958         fromY = DROP_RANK;
8959         toX = currentMoveString[2] - AAA;
8960         toY = currentMoveString[3] - ONE;
8961         break;
8962
8963       case WhiteWins:
8964       case BlackWins:
8965       case GameIsDrawn:
8966       case GameUnfinished:
8967         if (appData.debugMode)
8968           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8969         p = strchr(yy_text, '{');
8970         if (p == NULL) p = strchr(yy_text, '(');
8971         if (p == NULL) {
8972             p = yy_text;
8973             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8974         } else {
8975             q = strchr(p, *p == '{' ? '}' : ')');
8976             if (q != NULL) *q = NULLCHAR;
8977             p++;
8978         }
8979         GameEnds(moveType, p, GE_FILE);
8980         done = TRUE;
8981         if (cmailMsgLoaded) {
8982             ClearHighlights();
8983             flipView = WhiteOnMove(currentMove);
8984             if (moveType == GameUnfinished) flipView = !flipView;
8985             if (appData.debugMode)
8986               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8987         }
8988         break;
8989
8990       case (ChessMove) 0:       /* end of file */
8991         if (appData.debugMode)
8992           fprintf(debugFP, "Parser hit end of file\n");
8993         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8994           case MT_NONE:
8995           case MT_CHECK:
8996             break;
8997           case MT_CHECKMATE:
8998           case MT_STAINMATE:
8999             if (WhiteOnMove(currentMove)) {
9000                 GameEnds(BlackWins, "Black mates", GE_FILE);
9001             } else {
9002                 GameEnds(WhiteWins, "White mates", GE_FILE);
9003             }
9004             break;
9005           case MT_STALEMATE:
9006             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9007             break;
9008         }
9009         done = TRUE;
9010         break;
9011
9012       case MoveNumberOne:
9013         if (lastLoadGameStart == GNUChessGame) {
9014             /* GNUChessGames have numbers, but they aren't move numbers */
9015             if (appData.debugMode)
9016               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9017                       yy_text, (int) moveType);
9018             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9019         }
9020         /* else fall thru */
9021
9022       case XBoardGame:
9023       case GNUChessGame:
9024       case PGNTag:
9025         /* Reached start of next game in file */
9026         if (appData.debugMode)
9027           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9028         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9029           case MT_NONE:
9030           case MT_CHECK:
9031             break;
9032           case MT_CHECKMATE:
9033           case MT_STAINMATE:
9034             if (WhiteOnMove(currentMove)) {
9035                 GameEnds(BlackWins, "Black mates", GE_FILE);
9036             } else {
9037                 GameEnds(WhiteWins, "White mates", GE_FILE);
9038             }
9039             break;
9040           case MT_STALEMATE:
9041             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9042             break;
9043         }
9044         done = TRUE;
9045         break;
9046
9047       case PositionDiagram:     /* should not happen; ignore */
9048       case ElapsedTime:         /* ignore */
9049       case NAG:                 /* ignore */
9050         if (appData.debugMode)
9051           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9052                   yy_text, (int) moveType);
9053         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9054
9055       case IllegalMove:
9056         if (appData.testLegality) {
9057             if (appData.debugMode)
9058               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9059             sprintf(move, _("Illegal move: %d.%s%s"),
9060                     (forwardMostMove / 2) + 1,
9061                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9062             DisplayError(move, 0);
9063             done = TRUE;
9064         } else {
9065             if (appData.debugMode)
9066               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9067                       yy_text, currentMoveString);
9068             fromX = currentMoveString[0] - AAA;
9069             fromY = currentMoveString[1] - ONE;
9070             toX = currentMoveString[2] - AAA;
9071             toY = currentMoveString[3] - ONE;
9072             promoChar = currentMoveString[4];
9073         }
9074         break;
9075
9076       case AmbiguousMove:
9077         if (appData.debugMode)
9078           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9079         sprintf(move, _("Ambiguous move: %d.%s%s"),
9080                 (forwardMostMove / 2) + 1,
9081                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9082         DisplayError(move, 0);
9083         done = TRUE;
9084         break;
9085
9086       default:
9087       case ImpossibleMove:
9088         if (appData.debugMode)
9089           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9090         sprintf(move, _("Illegal move: %d.%s%s"),
9091                 (forwardMostMove / 2) + 1,
9092                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9093         DisplayError(move, 0);
9094         done = TRUE;
9095         break;
9096     }
9097
9098     if (done) {
9099         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9100             DrawPosition(FALSE, boards[currentMove]);
9101             DisplayBothClocks();
9102             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9103               DisplayComment(currentMove - 1, commentList[currentMove]);
9104         }
9105         (void) StopLoadGameTimer();
9106         gameFileFP = NULL;
9107         cmailOldMove = forwardMostMove;
9108         return FALSE;
9109     } else {
9110         /* currentMoveString is set as a side-effect of yylex */
9111         strcat(currentMoveString, "\n");
9112         strcpy(moveList[forwardMostMove], currentMoveString);
9113         
9114         thinkOutput[0] = NULLCHAR;
9115         MakeMove(fromX, fromY, toX, toY, promoChar);
9116         currentMove = forwardMostMove;
9117         return TRUE;
9118     }
9119 }
9120
9121 /* Load the nth game from the given file */
9122 int
9123 LoadGameFromFile(filename, n, title, useList)
9124      char *filename;
9125      int n;
9126      char *title;
9127      /*Boolean*/ int useList;
9128 {
9129     FILE *f;
9130     char buf[MSG_SIZ];
9131
9132     if (strcmp(filename, "-") == 0) {
9133         f = stdin;
9134         title = "stdin";
9135     } else {
9136         f = fopen(filename, "rb");
9137         if (f == NULL) {
9138           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9139             DisplayError(buf, errno);
9140             return FALSE;
9141         }
9142     }
9143     if (fseek(f, 0, 0) == -1) {
9144         /* f is not seekable; probably a pipe */
9145         useList = FALSE;
9146     }
9147     if (useList && n == 0) {
9148         int error = GameListBuild(f);
9149         if (error) {
9150             DisplayError(_("Cannot build game list"), error);
9151         } else if (!ListEmpty(&gameList) &&
9152                    ((ListGame *) gameList.tailPred)->number > 1) {
9153             GameListPopUp(f, title);
9154             return TRUE;
9155         }
9156         GameListDestroy();
9157         n = 1;
9158     }
9159     if (n == 0) n = 1;
9160     return LoadGame(f, n, title, FALSE);
9161 }
9162
9163
9164 void
9165 MakeRegisteredMove()
9166 {
9167     int fromX, fromY, toX, toY;
9168     char promoChar;
9169     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9170         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9171           case CMAIL_MOVE:
9172           case CMAIL_DRAW:
9173             if (appData.debugMode)
9174               fprintf(debugFP, "Restoring %s for game %d\n",
9175                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9176     
9177             thinkOutput[0] = NULLCHAR;
9178             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9179             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9180             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9181             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9182             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9183             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9184             MakeMove(fromX, fromY, toX, toY, promoChar);
9185             ShowMove(fromX, fromY, toX, toY);
9186               
9187             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9188               case MT_NONE:
9189               case MT_CHECK:
9190                 break;
9191                 
9192               case MT_CHECKMATE:
9193               case MT_STAINMATE:
9194                 if (WhiteOnMove(currentMove)) {
9195                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9196                 } else {
9197                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9198                 }
9199                 break;
9200                 
9201               case MT_STALEMATE:
9202                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9203                 break;
9204             }
9205
9206             break;
9207             
9208           case CMAIL_RESIGN:
9209             if (WhiteOnMove(currentMove)) {
9210                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9211             } else {
9212                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9213             }
9214             break;
9215             
9216           case CMAIL_ACCEPT:
9217             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9218             break;
9219               
9220           default:
9221             break;
9222         }
9223     }
9224
9225     return;
9226 }
9227
9228 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9229 int
9230 CmailLoadGame(f, gameNumber, title, useList)
9231      FILE *f;
9232      int gameNumber;
9233      char *title;
9234      int useList;
9235 {
9236     int retVal;
9237
9238     if (gameNumber > nCmailGames) {
9239         DisplayError(_("No more games in this message"), 0);
9240         return FALSE;
9241     }
9242     if (f == lastLoadGameFP) {
9243         int offset = gameNumber - lastLoadGameNumber;
9244         if (offset == 0) {
9245             cmailMsg[0] = NULLCHAR;
9246             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9247                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9248                 nCmailMovesRegistered--;
9249             }
9250             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9251             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9252                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9253             }
9254         } else {
9255             if (! RegisterMove()) return FALSE;
9256         }
9257     }
9258
9259     retVal = LoadGame(f, gameNumber, title, useList);
9260
9261     /* Make move registered during previous look at this game, if any */
9262     MakeRegisteredMove();
9263
9264     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9265         commentList[currentMove]
9266           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9267         DisplayComment(currentMove - 1, commentList[currentMove]);
9268     }
9269
9270     return retVal;
9271 }
9272
9273 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9274 int
9275 ReloadGame(offset)
9276      int offset;
9277 {
9278     int gameNumber = lastLoadGameNumber + offset;
9279     if (lastLoadGameFP == NULL) {
9280         DisplayError(_("No game has been loaded yet"), 0);
9281         return FALSE;
9282     }
9283     if (gameNumber <= 0) {
9284         DisplayError(_("Can't back up any further"), 0);
9285         return FALSE;
9286     }
9287     if (cmailMsgLoaded) {
9288         return CmailLoadGame(lastLoadGameFP, gameNumber,
9289                              lastLoadGameTitle, lastLoadGameUseList);
9290     } else {
9291         return LoadGame(lastLoadGameFP, gameNumber,
9292                         lastLoadGameTitle, lastLoadGameUseList);
9293     }
9294 }
9295
9296
9297
9298 /* Load the nth game from open file f */
9299 int
9300 LoadGame(f, gameNumber, title, useList)
9301      FILE *f;
9302      int gameNumber;
9303      char *title;
9304      int useList;
9305 {
9306     ChessMove cm;
9307     char buf[MSG_SIZ];
9308     int gn = gameNumber;
9309     ListGame *lg = NULL;
9310     int numPGNTags = 0;
9311     int err;
9312     GameMode oldGameMode;
9313     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9314
9315     if (appData.debugMode) 
9316         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9317
9318     if (gameMode == Training )
9319         SetTrainingModeOff();
9320
9321     oldGameMode = gameMode;
9322     if (gameMode != BeginningOfGame) {
9323       Reset(FALSE, TRUE);
9324     }
9325
9326     gameFileFP = f;
9327     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9328         fclose(lastLoadGameFP);
9329     }
9330
9331     if (useList) {
9332         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9333         
9334         if (lg) {
9335             fseek(f, lg->offset, 0);
9336             GameListHighlight(gameNumber);
9337             gn = 1;
9338         }
9339         else {
9340             DisplayError(_("Game number out of range"), 0);
9341             return FALSE;
9342         }
9343     } else {
9344         GameListDestroy();
9345         if (fseek(f, 0, 0) == -1) {
9346             if (f == lastLoadGameFP ?
9347                 gameNumber == lastLoadGameNumber + 1 :
9348                 gameNumber == 1) {
9349                 gn = 1;
9350             } else {
9351                 DisplayError(_("Can't seek on game file"), 0);
9352                 return FALSE;
9353             }
9354         }
9355     }
9356     lastLoadGameFP = f;
9357     lastLoadGameNumber = gameNumber;
9358     strcpy(lastLoadGameTitle, title);
9359     lastLoadGameUseList = useList;
9360
9361     yynewfile(f);
9362
9363     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9364       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9365                 lg->gameInfo.black);
9366             DisplayTitle(buf);
9367     } else if (*title != NULLCHAR) {
9368         if (gameNumber > 1) {
9369             sprintf(buf, "%s %d", title, gameNumber);
9370             DisplayTitle(buf);
9371         } else {
9372             DisplayTitle(title);
9373         }
9374     }
9375
9376     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9377         gameMode = PlayFromGameFile;
9378         ModeHighlight();
9379     }
9380
9381     currentMove = forwardMostMove = backwardMostMove = 0;
9382     CopyBoard(boards[0], initialPosition);
9383     StopClocks();
9384
9385     /*
9386      * Skip the first gn-1 games in the file.
9387      * Also skip over anything that precedes an identifiable 
9388      * start of game marker, to avoid being confused by 
9389      * garbage at the start of the file.  Currently 
9390      * recognized start of game markers are the move number "1",
9391      * the pattern "gnuchess .* game", the pattern
9392      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9393      * A game that starts with one of the latter two patterns
9394      * will also have a move number 1, possibly
9395      * following a position diagram.
9396      * 5-4-02: Let's try being more lenient and allowing a game to
9397      * start with an unnumbered move.  Does that break anything?
9398      */
9399     cm = lastLoadGameStart = (ChessMove) 0;
9400     while (gn > 0) {
9401         yyboardindex = forwardMostMove;
9402         cm = (ChessMove) yylex();
9403         switch (cm) {
9404           case (ChessMove) 0:
9405             if (cmailMsgLoaded) {
9406                 nCmailGames = CMAIL_MAX_GAMES - gn;
9407             } else {
9408                 Reset(TRUE, TRUE);
9409                 DisplayError(_("Game not found in file"), 0);
9410             }
9411             return FALSE;
9412
9413           case GNUChessGame:
9414           case XBoardGame:
9415             gn--;
9416             lastLoadGameStart = cm;
9417             break;
9418             
9419           case MoveNumberOne:
9420             switch (lastLoadGameStart) {
9421               case GNUChessGame:
9422               case XBoardGame:
9423               case PGNTag:
9424                 break;
9425               case MoveNumberOne:
9426               case (ChessMove) 0:
9427                 gn--;           /* count this game */
9428                 lastLoadGameStart = cm;
9429                 break;
9430               default:
9431                 /* impossible */
9432                 break;
9433             }
9434             break;
9435
9436           case PGNTag:
9437             switch (lastLoadGameStart) {
9438               case GNUChessGame:
9439               case PGNTag:
9440               case MoveNumberOne:
9441               case (ChessMove) 0:
9442                 gn--;           /* count this game */
9443                 lastLoadGameStart = cm;
9444                 break;
9445               case XBoardGame:
9446                 lastLoadGameStart = cm; /* game counted already */
9447                 break;
9448               default:
9449                 /* impossible */
9450                 break;
9451             }
9452             if (gn > 0) {
9453                 do {
9454                     yyboardindex = forwardMostMove;
9455                     cm = (ChessMove) yylex();
9456                 } while (cm == PGNTag || cm == Comment);
9457             }
9458             break;
9459
9460           case WhiteWins:
9461           case BlackWins:
9462           case GameIsDrawn:
9463             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9464                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9465                     != CMAIL_OLD_RESULT) {
9466                     nCmailResults ++ ;
9467                     cmailResult[  CMAIL_MAX_GAMES
9468                                 - gn - 1] = CMAIL_OLD_RESULT;
9469                 }
9470             }
9471             break;
9472
9473           case NormalMove:
9474             /* Only a NormalMove can be at the start of a game
9475              * without a position diagram. */
9476             if (lastLoadGameStart == (ChessMove) 0) {
9477               gn--;
9478               lastLoadGameStart = MoveNumberOne;
9479             }
9480             break;
9481
9482           default:
9483             break;
9484         }
9485     }
9486     
9487     if (appData.debugMode)
9488       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9489
9490     if (cm == XBoardGame) {
9491         /* Skip any header junk before position diagram and/or move 1 */
9492         for (;;) {
9493             yyboardindex = forwardMostMove;
9494             cm = (ChessMove) yylex();
9495
9496             if (cm == (ChessMove) 0 ||
9497                 cm == GNUChessGame || cm == XBoardGame) {
9498                 /* Empty game; pretend end-of-file and handle later */
9499                 cm = (ChessMove) 0;
9500                 break;
9501             }
9502
9503             if (cm == MoveNumberOne || cm == PositionDiagram ||
9504                 cm == PGNTag || cm == Comment)
9505               break;
9506         }
9507     } else if (cm == GNUChessGame) {
9508         if (gameInfo.event != NULL) {
9509             free(gameInfo.event);
9510         }
9511         gameInfo.event = StrSave(yy_text);
9512     }   
9513
9514     startedFromSetupPosition = FALSE;
9515     while (cm == PGNTag) {
9516         if (appData.debugMode) 
9517           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9518         err = ParsePGNTag(yy_text, &gameInfo);
9519         if (!err) numPGNTags++;
9520
9521         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9522         if(gameInfo.variant != oldVariant) {
9523             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9524             InitPosition(TRUE);
9525             oldVariant = gameInfo.variant;
9526             if (appData.debugMode) 
9527               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9528         }
9529
9530
9531         if (gameInfo.fen != NULL) {
9532           Board initial_position;
9533           startedFromSetupPosition = TRUE;
9534           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9535             Reset(TRUE, TRUE);
9536             DisplayError(_("Bad FEN position in file"), 0);
9537             return FALSE;
9538           }
9539           CopyBoard(boards[0], initial_position);
9540           if (blackPlaysFirst) {
9541             currentMove = forwardMostMove = backwardMostMove = 1;
9542             CopyBoard(boards[1], initial_position);
9543             strcpy(moveList[0], "");
9544             strcpy(parseList[0], "");
9545             timeRemaining[0][1] = whiteTimeRemaining;
9546             timeRemaining[1][1] = blackTimeRemaining;
9547             if (commentList[0] != NULL) {
9548               commentList[1] = commentList[0];
9549               commentList[0] = NULL;
9550             }
9551           } else {
9552             currentMove = forwardMostMove = backwardMostMove = 0;
9553           }
9554           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9555           {   int i;
9556               initialRulePlies = FENrulePlies;
9557               for( i=0; i< nrCastlingRights; i++ )
9558                   initialRights[i] = initial_position[CASTLING][i];
9559           }
9560           yyboardindex = forwardMostMove;
9561           free(gameInfo.fen);
9562           gameInfo.fen = NULL;
9563         }
9564
9565         yyboardindex = forwardMostMove;
9566         cm = (ChessMove) yylex();
9567
9568         /* Handle comments interspersed among the tags */
9569         while (cm == Comment) {
9570             char *p;
9571             if (appData.debugMode) 
9572               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9573             p = yy_text;
9574             AppendComment(currentMove, p, FALSE);
9575             yyboardindex = forwardMostMove;
9576             cm = (ChessMove) yylex();
9577         }
9578     }
9579
9580     /* don't rely on existence of Event tag since if game was
9581      * pasted from clipboard the Event tag may not exist
9582      */
9583     if (numPGNTags > 0){
9584         char *tags;
9585         if (gameInfo.variant == VariantNormal) {
9586           gameInfo.variant = StringToVariant(gameInfo.event);
9587         }
9588         if (!matchMode) {
9589           if( appData.autoDisplayTags ) {
9590             tags = PGNTags(&gameInfo);
9591             TagsPopUp(tags, CmailMsg());
9592             free(tags);
9593           }
9594         }
9595     } else {
9596         /* Make something up, but don't display it now */
9597         SetGameInfo();
9598         TagsPopDown();
9599     }
9600
9601     if (cm == PositionDiagram) {
9602         int i, j;
9603         char *p;
9604         Board initial_position;
9605
9606         if (appData.debugMode)
9607           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9608
9609         if (!startedFromSetupPosition) {
9610             p = yy_text;
9611             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9612               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9613                 switch (*p) {
9614                   case '[':
9615                   case '-':
9616                   case ' ':
9617                   case '\t':
9618                   case '\n':
9619                   case '\r':
9620                     break;
9621                   default:
9622                     initial_position[i][j++] = CharToPiece(*p);
9623                     break;
9624                 }
9625             while (*p == ' ' || *p == '\t' ||
9626                    *p == '\n' || *p == '\r') p++;
9627         
9628             if (strncmp(p, "black", strlen("black"))==0)
9629               blackPlaysFirst = TRUE;
9630             else
9631               blackPlaysFirst = FALSE;
9632             startedFromSetupPosition = TRUE;
9633         
9634             CopyBoard(boards[0], initial_position);
9635             if (blackPlaysFirst) {
9636                 currentMove = forwardMostMove = backwardMostMove = 1;
9637                 CopyBoard(boards[1], initial_position);
9638                 strcpy(moveList[0], "");
9639                 strcpy(parseList[0], "");
9640                 timeRemaining[0][1] = whiteTimeRemaining;
9641                 timeRemaining[1][1] = blackTimeRemaining;
9642                 if (commentList[0] != NULL) {
9643                     commentList[1] = commentList[0];
9644                     commentList[0] = NULL;
9645                 }
9646             } else {
9647                 currentMove = forwardMostMove = backwardMostMove = 0;
9648             }
9649         }
9650         yyboardindex = forwardMostMove;
9651         cm = (ChessMove) yylex();
9652     }
9653
9654     if (first.pr == NoProc) {
9655         StartChessProgram(&first);
9656     }
9657     InitChessProgram(&first, FALSE);
9658     SendToProgram("force\n", &first);
9659     if (startedFromSetupPosition) {
9660         SendBoard(&first, forwardMostMove);
9661     if (appData.debugMode) {
9662         fprintf(debugFP, "Load Game\n");
9663     }
9664         DisplayBothClocks();
9665     }      
9666
9667     /* [HGM] server: flag to write setup moves in broadcast file as one */
9668     loadFlag = appData.suppressLoadMoves;
9669
9670     while (cm == Comment) {
9671         char *p;
9672         if (appData.debugMode) 
9673           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9674         p = yy_text;
9675         AppendComment(currentMove, p, FALSE);
9676         yyboardindex = forwardMostMove;
9677         cm = (ChessMove) yylex();
9678     }
9679
9680     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9681         cm == WhiteWins || cm == BlackWins ||
9682         cm == GameIsDrawn || cm == GameUnfinished) {
9683         DisplayMessage("", _("No moves in game"));
9684         if (cmailMsgLoaded) {
9685             if (appData.debugMode)
9686               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9687             ClearHighlights();
9688             flipView = FALSE;
9689         }
9690         DrawPosition(FALSE, boards[currentMove]);
9691         DisplayBothClocks();
9692         gameMode = EditGame;
9693         ModeHighlight();
9694         gameFileFP = NULL;
9695         cmailOldMove = 0;
9696         return TRUE;
9697     }
9698
9699     // [HGM] PV info: routine tests if comment empty
9700     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9701         DisplayComment(currentMove - 1, commentList[currentMove]);
9702     }
9703     if (!matchMode && appData.timeDelay != 0) 
9704       DrawPosition(FALSE, boards[currentMove]);
9705
9706     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9707       programStats.ok_to_send = 1;
9708     }
9709
9710     /* if the first token after the PGN tags is a move
9711      * and not move number 1, retrieve it from the parser 
9712      */
9713     if (cm != MoveNumberOne)
9714         LoadGameOneMove(cm);
9715
9716     /* load the remaining moves from the file */
9717     while (LoadGameOneMove((ChessMove)0)) {
9718       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9719       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9720     }
9721
9722     /* rewind to the start of the game */
9723     currentMove = backwardMostMove;
9724
9725     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9726
9727     if (oldGameMode == AnalyzeFile ||
9728         oldGameMode == AnalyzeMode) {
9729       AnalyzeFileEvent();
9730     }
9731
9732     if (matchMode || appData.timeDelay == 0) {
9733       ToEndEvent();
9734       gameMode = EditGame;
9735       ModeHighlight();
9736     } else if (appData.timeDelay > 0) {
9737       AutoPlayGameLoop();
9738     }
9739
9740     if (appData.debugMode) 
9741         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9742
9743     loadFlag = 0; /* [HGM] true game starts */
9744     return TRUE;
9745 }
9746
9747 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9748 int
9749 ReloadPosition(offset)
9750      int offset;
9751 {
9752     int positionNumber = lastLoadPositionNumber + offset;
9753     if (lastLoadPositionFP == NULL) {
9754         DisplayError(_("No position has been loaded yet"), 0);
9755         return FALSE;
9756     }
9757     if (positionNumber <= 0) {
9758         DisplayError(_("Can't back up any further"), 0);
9759         return FALSE;
9760     }
9761     return LoadPosition(lastLoadPositionFP, positionNumber,
9762                         lastLoadPositionTitle);
9763 }
9764
9765 /* Load the nth position from the given file */
9766 int
9767 LoadPositionFromFile(filename, n, title)
9768      char *filename;
9769      int n;
9770      char *title;
9771 {
9772     FILE *f;
9773     char buf[MSG_SIZ];
9774
9775     if (strcmp(filename, "-") == 0) {
9776         return LoadPosition(stdin, n, "stdin");
9777     } else {
9778         f = fopen(filename, "rb");
9779         if (f == NULL) {
9780             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9781             DisplayError(buf, errno);
9782             return FALSE;
9783         } else {
9784             return LoadPosition(f, n, title);
9785         }
9786     }
9787 }
9788
9789 /* Load the nth position from the given open file, and close it */
9790 int
9791 LoadPosition(f, positionNumber, title)
9792      FILE *f;
9793      int positionNumber;
9794      char *title;
9795 {
9796     char *p, line[MSG_SIZ];
9797     Board initial_position;
9798     int i, j, fenMode, pn;
9799     
9800     if (gameMode == Training )
9801         SetTrainingModeOff();
9802
9803     if (gameMode != BeginningOfGame) {
9804         Reset(FALSE, TRUE);
9805     }
9806     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9807         fclose(lastLoadPositionFP);
9808     }
9809     if (positionNumber == 0) positionNumber = 1;
9810     lastLoadPositionFP = f;
9811     lastLoadPositionNumber = positionNumber;
9812     strcpy(lastLoadPositionTitle, title);
9813     if (first.pr == NoProc) {
9814       StartChessProgram(&first);
9815       InitChessProgram(&first, FALSE);
9816     }    
9817     pn = positionNumber;
9818     if (positionNumber < 0) {
9819         /* Negative position number means to seek to that byte offset */
9820         if (fseek(f, -positionNumber, 0) == -1) {
9821             DisplayError(_("Can't seek on position file"), 0);
9822             return FALSE;
9823         };
9824         pn = 1;
9825     } else {
9826         if (fseek(f, 0, 0) == -1) {
9827             if (f == lastLoadPositionFP ?
9828                 positionNumber == lastLoadPositionNumber + 1 :
9829                 positionNumber == 1) {
9830                 pn = 1;
9831             } else {
9832                 DisplayError(_("Can't seek on position file"), 0);
9833                 return FALSE;
9834             }
9835         }
9836     }
9837     /* See if this file is FEN or old-style xboard */
9838     if (fgets(line, MSG_SIZ, f) == NULL) {
9839         DisplayError(_("Position not found in file"), 0);
9840         return FALSE;
9841     }
9842     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9843     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9844
9845     if (pn >= 2) {
9846         if (fenMode || line[0] == '#') pn--;
9847         while (pn > 0) {
9848             /* skip positions before number pn */
9849             if (fgets(line, MSG_SIZ, f) == NULL) {
9850                 Reset(TRUE, TRUE);
9851                 DisplayError(_("Position not found in file"), 0);
9852                 return FALSE;
9853             }
9854             if (fenMode || line[0] == '#') pn--;
9855         }
9856     }
9857
9858     if (fenMode) {
9859         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9860             DisplayError(_("Bad FEN position in file"), 0);
9861             return FALSE;
9862         }
9863     } else {
9864         (void) fgets(line, MSG_SIZ, f);
9865         (void) fgets(line, MSG_SIZ, f);
9866     
9867         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9868             (void) fgets(line, MSG_SIZ, f);
9869             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9870                 if (*p == ' ')
9871                   continue;
9872                 initial_position[i][j++] = CharToPiece(*p);
9873             }
9874         }
9875     
9876         blackPlaysFirst = FALSE;
9877         if (!feof(f)) {
9878             (void) fgets(line, MSG_SIZ, f);
9879             if (strncmp(line, "black", strlen("black"))==0)
9880               blackPlaysFirst = TRUE;
9881         }
9882     }
9883     startedFromSetupPosition = TRUE;
9884     
9885     SendToProgram("force\n", &first);
9886     CopyBoard(boards[0], initial_position);
9887     if (blackPlaysFirst) {
9888         currentMove = forwardMostMove = backwardMostMove = 1;
9889         strcpy(moveList[0], "");
9890         strcpy(parseList[0], "");
9891         CopyBoard(boards[1], initial_position);
9892         DisplayMessage("", _("Black to play"));
9893     } else {
9894         currentMove = forwardMostMove = backwardMostMove = 0;
9895         DisplayMessage("", _("White to play"));
9896     }
9897     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9898     SendBoard(&first, forwardMostMove);
9899     if (appData.debugMode) {
9900 int i, j;
9901   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9902   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9903         fprintf(debugFP, "Load Position\n");
9904     }
9905
9906     if (positionNumber > 1) {
9907         sprintf(line, "%s %d", title, positionNumber);
9908         DisplayTitle(line);
9909     } else {
9910         DisplayTitle(title);
9911     }
9912     gameMode = EditGame;
9913     ModeHighlight();
9914     ResetClocks();
9915     timeRemaining[0][1] = whiteTimeRemaining;
9916     timeRemaining[1][1] = blackTimeRemaining;
9917     DrawPosition(FALSE, boards[currentMove]);
9918    
9919     return TRUE;
9920 }
9921
9922
9923 void
9924 CopyPlayerNameIntoFileName(dest, src)
9925      char **dest, *src;
9926 {
9927     while (*src != NULLCHAR && *src != ',') {
9928         if (*src == ' ') {
9929             *(*dest)++ = '_';
9930             src++;
9931         } else {
9932             *(*dest)++ = *src++;
9933         }
9934     }
9935 }
9936
9937 char *DefaultFileName(ext)
9938      char *ext;
9939 {
9940     static char def[MSG_SIZ];
9941     char *p;
9942
9943     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9944         p = def;
9945         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9946         *p++ = '-';
9947         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9948         *p++ = '.';
9949         strcpy(p, ext);
9950     } else {
9951         def[0] = NULLCHAR;
9952     }
9953     return def;
9954 }
9955
9956 /* Save the current game to the given file */
9957 int
9958 SaveGameToFile(filename, append)
9959      char *filename;
9960      int append;
9961 {
9962     FILE *f;
9963     char buf[MSG_SIZ];
9964
9965     if (strcmp(filename, "-") == 0) {
9966         return SaveGame(stdout, 0, NULL);
9967     } else {
9968         f = fopen(filename, append ? "a" : "w");
9969         if (f == NULL) {
9970             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9971             DisplayError(buf, errno);
9972             return FALSE;
9973         } else {
9974             return SaveGame(f, 0, NULL);
9975         }
9976     }
9977 }
9978
9979 char *
9980 SavePart(str)
9981      char *str;
9982 {
9983     static char buf[MSG_SIZ];
9984     char *p;
9985     
9986     p = strchr(str, ' ');
9987     if (p == NULL) return str;
9988     strncpy(buf, str, p - str);
9989     buf[p - str] = NULLCHAR;
9990     return buf;
9991 }
9992
9993 #define PGN_MAX_LINE 75
9994
9995 #define PGN_SIDE_WHITE  0
9996 #define PGN_SIDE_BLACK  1
9997
9998 /* [AS] */
9999 static int FindFirstMoveOutOfBook( int side )
10000 {
10001     int result = -1;
10002
10003     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10004         int index = backwardMostMove;
10005         int has_book_hit = 0;
10006
10007         if( (index % 2) != side ) {
10008             index++;
10009         }
10010
10011         while( index < forwardMostMove ) {
10012             /* Check to see if engine is in book */
10013             int depth = pvInfoList[index].depth;
10014             int score = pvInfoList[index].score;
10015             int in_book = 0;
10016
10017             if( depth <= 2 ) {
10018                 in_book = 1;
10019             }
10020             else if( score == 0 && depth == 63 ) {
10021                 in_book = 1; /* Zappa */
10022             }
10023             else if( score == 2 && depth == 99 ) {
10024                 in_book = 1; /* Abrok */
10025             }
10026
10027             has_book_hit += in_book;
10028
10029             if( ! in_book ) {
10030                 result = index;
10031
10032                 break;
10033             }
10034
10035             index += 2;
10036         }
10037     }
10038
10039     return result;
10040 }
10041
10042 /* [AS] */
10043 void GetOutOfBookInfo( char * buf )
10044 {
10045     int oob[2];
10046     int i;
10047     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10048
10049     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10050     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10051
10052     *buf = '\0';
10053
10054     if( oob[0] >= 0 || oob[1] >= 0 ) {
10055         for( i=0; i<2; i++ ) {
10056             int idx = oob[i];
10057
10058             if( idx >= 0 ) {
10059                 if( i > 0 && oob[0] >= 0 ) {
10060                     strcat( buf, "   " );
10061                 }
10062
10063                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10064                 sprintf( buf+strlen(buf), "%s%.2f", 
10065                     pvInfoList[idx].score >= 0 ? "+" : "",
10066                     pvInfoList[idx].score / 100.0 );
10067             }
10068         }
10069     }
10070 }
10071
10072 /* Save game in PGN style and close the file */
10073 int
10074 SaveGamePGN(f)
10075      FILE *f;
10076 {
10077     int i, offset, linelen, newblock;
10078     time_t tm;
10079 //    char *movetext;
10080     char numtext[32];
10081     int movelen, numlen, blank;
10082     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10083
10084     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10085     
10086     tm = time((time_t *) NULL);
10087     
10088     PrintPGNTags(f, &gameInfo);
10089     
10090     if (backwardMostMove > 0 || startedFromSetupPosition) {
10091         char *fen = PositionToFEN(backwardMostMove, NULL);
10092         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10093         fprintf(f, "\n{--------------\n");
10094         PrintPosition(f, backwardMostMove);
10095         fprintf(f, "--------------}\n");
10096         free(fen);
10097     }
10098     else {
10099         /* [AS] Out of book annotation */
10100         if( appData.saveOutOfBookInfo ) {
10101             char buf[64];
10102
10103             GetOutOfBookInfo( buf );
10104
10105             if( buf[0] != '\0' ) {
10106                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10107             }
10108         }
10109
10110         fprintf(f, "\n");
10111     }
10112
10113     i = backwardMostMove;
10114     linelen = 0;
10115     newblock = TRUE;
10116
10117     while (i < forwardMostMove) {
10118         /* Print comments preceding this move */
10119         if (commentList[i] != NULL) {
10120             if (linelen > 0) fprintf(f, "\n");
10121             fprintf(f, "%s", commentList[i]);
10122             linelen = 0;
10123             newblock = TRUE;
10124         }
10125
10126         /* Format move number */
10127         if ((i % 2) == 0) {
10128             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10129         } else {
10130             if (newblock) {
10131                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10132             } else {
10133                 numtext[0] = NULLCHAR;
10134             }
10135         }
10136         numlen = strlen(numtext);
10137         newblock = FALSE;
10138
10139         /* Print move number */
10140         blank = linelen > 0 && numlen > 0;
10141         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10142             fprintf(f, "\n");
10143             linelen = 0;
10144             blank = 0;
10145         }
10146         if (blank) {
10147             fprintf(f, " ");
10148             linelen++;
10149         }
10150         fprintf(f, "%s", numtext);
10151         linelen += numlen;
10152
10153         /* Get move */
10154         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10155         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10156
10157         /* Print move */
10158         blank = linelen > 0 && movelen > 0;
10159         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10160             fprintf(f, "\n");
10161             linelen = 0;
10162             blank = 0;
10163         }
10164         if (blank) {
10165             fprintf(f, " ");
10166             linelen++;
10167         }
10168         fprintf(f, "%s", move_buffer);
10169         linelen += movelen;
10170
10171         /* [AS] Add PV info if present */
10172         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10173             /* [HGM] add time */
10174             char buf[MSG_SIZ]; int seconds;
10175
10176             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10177
10178             if( seconds <= 0) buf[0] = 0; else
10179             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10180                 seconds = (seconds + 4)/10; // round to full seconds
10181                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10182                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10183             }
10184
10185             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10186                 pvInfoList[i].score >= 0 ? "+" : "",
10187                 pvInfoList[i].score / 100.0,
10188                 pvInfoList[i].depth,
10189                 buf );
10190
10191             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10192
10193             /* Print score/depth */
10194             blank = linelen > 0 && movelen > 0;
10195             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10196                 fprintf(f, "\n");
10197                 linelen = 0;
10198                 blank = 0;
10199             }
10200             if (blank) {
10201                 fprintf(f, " ");
10202                 linelen++;
10203             }
10204             fprintf(f, "%s", move_buffer);
10205             linelen += movelen;
10206         }
10207
10208         i++;
10209     }
10210     
10211     /* Start a new line */
10212     if (linelen > 0) fprintf(f, "\n");
10213
10214     /* Print comments after last move */
10215     if (commentList[i] != NULL) {
10216         fprintf(f, "%s\n", commentList[i]);
10217     }
10218
10219     /* Print result */
10220     if (gameInfo.resultDetails != NULL &&
10221         gameInfo.resultDetails[0] != NULLCHAR) {
10222         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10223                 PGNResult(gameInfo.result));
10224     } else {
10225         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10226     }
10227
10228     fclose(f);
10229     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10230     return TRUE;
10231 }
10232
10233 /* Save game in old style and close the file */
10234 int
10235 SaveGameOldStyle(f)
10236      FILE *f;
10237 {
10238     int i, offset;
10239     time_t tm;
10240     
10241     tm = time((time_t *) NULL);
10242     
10243     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10244     PrintOpponents(f);
10245     
10246     if (backwardMostMove > 0 || startedFromSetupPosition) {
10247         fprintf(f, "\n[--------------\n");
10248         PrintPosition(f, backwardMostMove);
10249         fprintf(f, "--------------]\n");
10250     } else {
10251         fprintf(f, "\n");
10252     }
10253
10254     i = backwardMostMove;
10255     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10256
10257     while (i < forwardMostMove) {
10258         if (commentList[i] != NULL) {
10259             fprintf(f, "[%s]\n", commentList[i]);
10260         }
10261
10262         if ((i % 2) == 1) {
10263             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10264             i++;
10265         } else {
10266             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10267             i++;
10268             if (commentList[i] != NULL) {
10269                 fprintf(f, "\n");
10270                 continue;
10271             }
10272             if (i >= forwardMostMove) {
10273                 fprintf(f, "\n");
10274                 break;
10275             }
10276             fprintf(f, "%s\n", parseList[i]);
10277             i++;
10278         }
10279     }
10280     
10281     if (commentList[i] != NULL) {
10282         fprintf(f, "[%s]\n", commentList[i]);
10283     }
10284
10285     /* This isn't really the old style, but it's close enough */
10286     if (gameInfo.resultDetails != NULL &&
10287         gameInfo.resultDetails[0] != NULLCHAR) {
10288         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10289                 gameInfo.resultDetails);
10290     } else {
10291         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10292     }
10293
10294     fclose(f);
10295     return TRUE;
10296 }
10297
10298 /* Save the current game to open file f and close the file */
10299 int
10300 SaveGame(f, dummy, dummy2)
10301      FILE *f;
10302      int dummy;
10303      char *dummy2;
10304 {
10305     if (gameMode == EditPosition) EditPositionDone(TRUE);
10306     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10307     if (appData.oldSaveStyle)
10308       return SaveGameOldStyle(f);
10309     else
10310       return SaveGamePGN(f);
10311 }
10312
10313 /* Save the current position to the given file */
10314 int
10315 SavePositionToFile(filename)
10316      char *filename;
10317 {
10318     FILE *f;
10319     char buf[MSG_SIZ];
10320
10321     if (strcmp(filename, "-") == 0) {
10322         return SavePosition(stdout, 0, NULL);
10323     } else {
10324         f = fopen(filename, "a");
10325         if (f == NULL) {
10326             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10327             DisplayError(buf, errno);
10328             return FALSE;
10329         } else {
10330             SavePosition(f, 0, NULL);
10331             return TRUE;
10332         }
10333     }
10334 }
10335
10336 /* Save the current position to the given open file and close the file */
10337 int
10338 SavePosition(f, dummy, dummy2)
10339      FILE *f;
10340      int dummy;
10341      char *dummy2;
10342 {
10343     time_t tm;
10344     char *fen;
10345     
10346     if (gameMode == EditPosition) EditPositionDone(TRUE);
10347     if (appData.oldSaveStyle) {
10348         tm = time((time_t *) NULL);
10349     
10350         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10351         PrintOpponents(f);
10352         fprintf(f, "[--------------\n");
10353         PrintPosition(f, currentMove);
10354         fprintf(f, "--------------]\n");
10355     } else {
10356         fen = PositionToFEN(currentMove, NULL);
10357         fprintf(f, "%s\n", fen);
10358         free(fen);
10359     }
10360     fclose(f);
10361     return TRUE;
10362 }
10363
10364 void
10365 ReloadCmailMsgEvent(unregister)
10366      int unregister;
10367 {
10368 #if !WIN32
10369     static char *inFilename = NULL;
10370     static char *outFilename;
10371     int i;
10372     struct stat inbuf, outbuf;
10373     int status;
10374     
10375     /* Any registered moves are unregistered if unregister is set, */
10376     /* i.e. invoked by the signal handler */
10377     if (unregister) {
10378         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10379             cmailMoveRegistered[i] = FALSE;
10380             if (cmailCommentList[i] != NULL) {
10381                 free(cmailCommentList[i]);
10382                 cmailCommentList[i] = NULL;
10383             }
10384         }
10385         nCmailMovesRegistered = 0;
10386     }
10387
10388     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10389         cmailResult[i] = CMAIL_NOT_RESULT;
10390     }
10391     nCmailResults = 0;
10392
10393     if (inFilename == NULL) {
10394         /* Because the filenames are static they only get malloced once  */
10395         /* and they never get freed                                      */
10396         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10397         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10398
10399         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10400         sprintf(outFilename, "%s.out", appData.cmailGameName);
10401     }
10402     
10403     status = stat(outFilename, &outbuf);
10404     if (status < 0) {
10405         cmailMailedMove = FALSE;
10406     } else {
10407         status = stat(inFilename, &inbuf);
10408         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10409     }
10410     
10411     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10412        counts the games, notes how each one terminated, etc.
10413        
10414        It would be nice to remove this kludge and instead gather all
10415        the information while building the game list.  (And to keep it
10416        in the game list nodes instead of having a bunch of fixed-size
10417        parallel arrays.)  Note this will require getting each game's
10418        termination from the PGN tags, as the game list builder does
10419        not process the game moves.  --mann
10420        */
10421     cmailMsgLoaded = TRUE;
10422     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10423     
10424     /* Load first game in the file or popup game menu */
10425     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10426
10427 #endif /* !WIN32 */
10428     return;
10429 }
10430
10431 int
10432 RegisterMove()
10433 {
10434     FILE *f;
10435     char string[MSG_SIZ];
10436
10437     if (   cmailMailedMove
10438         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10439         return TRUE;            /* Allow free viewing  */
10440     }
10441
10442     /* Unregister move to ensure that we don't leave RegisterMove        */
10443     /* with the move registered when the conditions for registering no   */
10444     /* longer hold                                                       */
10445     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10446         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10447         nCmailMovesRegistered --;
10448
10449         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10450           {
10451               free(cmailCommentList[lastLoadGameNumber - 1]);
10452               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10453           }
10454     }
10455
10456     if (cmailOldMove == -1) {
10457         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10458         return FALSE;
10459     }
10460
10461     if (currentMove > cmailOldMove + 1) {
10462         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10463         return FALSE;
10464     }
10465
10466     if (currentMove < cmailOldMove) {
10467         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10468         return FALSE;
10469     }
10470
10471     if (forwardMostMove > currentMove) {
10472         /* Silently truncate extra moves */
10473         TruncateGame();
10474     }
10475
10476     if (   (currentMove == cmailOldMove + 1)
10477         || (   (currentMove == cmailOldMove)
10478             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10479                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10480         if (gameInfo.result != GameUnfinished) {
10481             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10482         }
10483
10484         if (commentList[currentMove] != NULL) {
10485             cmailCommentList[lastLoadGameNumber - 1]
10486               = StrSave(commentList[currentMove]);
10487         }
10488         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10489
10490         if (appData.debugMode)
10491           fprintf(debugFP, "Saving %s for game %d\n",
10492                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10493
10494         sprintf(string,
10495                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10496         
10497         f = fopen(string, "w");
10498         if (appData.oldSaveStyle) {
10499             SaveGameOldStyle(f); /* also closes the file */
10500             
10501             sprintf(string, "%s.pos.out", appData.cmailGameName);
10502             f = fopen(string, "w");
10503             SavePosition(f, 0, NULL); /* also closes the file */
10504         } else {
10505             fprintf(f, "{--------------\n");
10506             PrintPosition(f, currentMove);
10507             fprintf(f, "--------------}\n\n");
10508             
10509             SaveGame(f, 0, NULL); /* also closes the file*/
10510         }
10511         
10512         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10513         nCmailMovesRegistered ++;
10514     } else if (nCmailGames == 1) {
10515         DisplayError(_("You have not made a move yet"), 0);
10516         return FALSE;
10517     }
10518
10519     return TRUE;
10520 }
10521
10522 void
10523 MailMoveEvent()
10524 {
10525 #if !WIN32
10526     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10527     FILE *commandOutput;
10528     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10529     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10530     int nBuffers;
10531     int i;
10532     int archived;
10533     char *arcDir;
10534
10535     if (! cmailMsgLoaded) {
10536         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10537         return;
10538     }
10539
10540     if (nCmailGames == nCmailResults) {
10541         DisplayError(_("No unfinished games"), 0);
10542         return;
10543     }
10544
10545 #if CMAIL_PROHIBIT_REMAIL
10546     if (cmailMailedMove) {
10547         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);
10548         DisplayError(msg, 0);
10549         return;
10550     }
10551 #endif
10552
10553     if (! (cmailMailedMove || RegisterMove())) return;
10554     
10555     if (   cmailMailedMove
10556         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10557         sprintf(string, partCommandString,
10558                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10559         commandOutput = popen(string, "r");
10560
10561         if (commandOutput == NULL) {
10562             DisplayError(_("Failed to invoke cmail"), 0);
10563         } else {
10564             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10565                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10566             }
10567             if (nBuffers > 1) {
10568                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10569                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10570                 nBytes = MSG_SIZ - 1;
10571             } else {
10572                 (void) memcpy(msg, buffer, nBytes);
10573             }
10574             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10575
10576             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10577                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10578
10579                 archived = TRUE;
10580                 for (i = 0; i < nCmailGames; i ++) {
10581                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10582                         archived = FALSE;
10583                     }
10584                 }
10585                 if (   archived
10586                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10587                         != NULL)) {
10588                     sprintf(buffer, "%s/%s.%s.archive",
10589                             arcDir,
10590                             appData.cmailGameName,
10591                             gameInfo.date);
10592                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10593                     cmailMsgLoaded = FALSE;
10594                 }
10595             }
10596
10597             DisplayInformation(msg);
10598             pclose(commandOutput);
10599         }
10600     } else {
10601         if ((*cmailMsg) != '\0') {
10602             DisplayInformation(cmailMsg);
10603         }
10604     }
10605
10606     return;
10607 #endif /* !WIN32 */
10608 }
10609
10610 char *
10611 CmailMsg()
10612 {
10613 #if WIN32
10614     return NULL;
10615 #else
10616     int  prependComma = 0;
10617     char number[5];
10618     char string[MSG_SIZ];       /* Space for game-list */
10619     int  i;
10620     
10621     if (!cmailMsgLoaded) return "";
10622
10623     if (cmailMailedMove) {
10624         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10625     } else {
10626         /* Create a list of games left */
10627         sprintf(string, "[");
10628         for (i = 0; i < nCmailGames; i ++) {
10629             if (! (   cmailMoveRegistered[i]
10630                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10631                 if (prependComma) {
10632                     sprintf(number, ",%d", i + 1);
10633                 } else {
10634                     sprintf(number, "%d", i + 1);
10635                     prependComma = 1;
10636                 }
10637                 
10638                 strcat(string, number);
10639             }
10640         }
10641         strcat(string, "]");
10642
10643         if (nCmailMovesRegistered + nCmailResults == 0) {
10644             switch (nCmailGames) {
10645               case 1:
10646                 sprintf(cmailMsg,
10647                         _("Still need to make move for game\n"));
10648                 break;
10649                 
10650               case 2:
10651                 sprintf(cmailMsg,
10652                         _("Still need to make moves for both games\n"));
10653                 break;
10654                 
10655               default:
10656                 sprintf(cmailMsg,
10657                         _("Still need to make moves for all %d games\n"),
10658                         nCmailGames);
10659                 break;
10660             }
10661         } else {
10662             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10663               case 1:
10664                 sprintf(cmailMsg,
10665                         _("Still need to make a move for game %s\n"),
10666                         string);
10667                 break;
10668                 
10669               case 0:
10670                 if (nCmailResults == nCmailGames) {
10671                     sprintf(cmailMsg, _("No unfinished games\n"));
10672                 } else {
10673                     sprintf(cmailMsg, _("Ready to send mail\n"));
10674                 }
10675                 break;
10676                 
10677               default:
10678                 sprintf(cmailMsg,
10679                         _("Still need to make moves for games %s\n"),
10680                         string);
10681             }
10682         }
10683     }
10684     return cmailMsg;
10685 #endif /* WIN32 */
10686 }
10687
10688 void
10689 ResetGameEvent()
10690 {
10691     if (gameMode == Training)
10692       SetTrainingModeOff();
10693
10694     Reset(TRUE, TRUE);
10695     cmailMsgLoaded = FALSE;
10696     if (appData.icsActive) {
10697       SendToICS(ics_prefix);
10698       SendToICS("refresh\n");
10699     }
10700 }
10701
10702 void
10703 ExitEvent(status)
10704      int status;
10705 {
10706     exiting++;
10707     if (exiting > 2) {
10708       /* Give up on clean exit */
10709       exit(status);
10710     }
10711     if (exiting > 1) {
10712       /* Keep trying for clean exit */
10713       return;
10714     }
10715
10716     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10717
10718     if (telnetISR != NULL) {
10719       RemoveInputSource(telnetISR);
10720     }
10721     if (icsPR != NoProc) {
10722       DestroyChildProcess(icsPR, TRUE);
10723     }
10724
10725     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10726     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10727
10728     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10729     /* make sure this other one finishes before killing it!                  */
10730     if(endingGame) { int count = 0;
10731         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10732         while(endingGame && count++ < 10) DoSleep(1);
10733         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10734     }
10735
10736     /* Kill off chess programs */
10737     if (first.pr != NoProc) {
10738         ExitAnalyzeMode();
10739         
10740         DoSleep( appData.delayBeforeQuit );
10741         SendToProgram("quit\n", &first);
10742         DoSleep( appData.delayAfterQuit );
10743         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10744     }
10745     if (second.pr != NoProc) {
10746         DoSleep( appData.delayBeforeQuit );
10747         SendToProgram("quit\n", &second);
10748         DoSleep( appData.delayAfterQuit );
10749         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10750     }
10751     if (first.isr != NULL) {
10752         RemoveInputSource(first.isr);
10753     }
10754     if (second.isr != NULL) {
10755         RemoveInputSource(second.isr);
10756     }
10757
10758     ShutDownFrontEnd();
10759     exit(status);
10760 }
10761
10762 void
10763 PauseEvent()
10764 {
10765     if (appData.debugMode)
10766         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10767     if (pausing) {
10768         pausing = FALSE;
10769         ModeHighlight();
10770         if (gameMode == MachinePlaysWhite ||
10771             gameMode == MachinePlaysBlack) {
10772             StartClocks();
10773         } else {
10774             DisplayBothClocks();
10775         }
10776         if (gameMode == PlayFromGameFile) {
10777             if (appData.timeDelay >= 0) 
10778                 AutoPlayGameLoop();
10779         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10780             Reset(FALSE, TRUE);
10781             SendToICS(ics_prefix);
10782             SendToICS("refresh\n");
10783         } else if (currentMove < forwardMostMove) {
10784             ForwardInner(forwardMostMove);
10785         }
10786         pauseExamInvalid = FALSE;
10787     } else {
10788         switch (gameMode) {
10789           default:
10790             return;
10791           case IcsExamining:
10792             pauseExamForwardMostMove = forwardMostMove;
10793             pauseExamInvalid = FALSE;
10794             /* fall through */
10795           case IcsObserving:
10796           case IcsPlayingWhite:
10797           case IcsPlayingBlack:
10798             pausing = TRUE;
10799             ModeHighlight();
10800             return;
10801           case PlayFromGameFile:
10802             (void) StopLoadGameTimer();
10803             pausing = TRUE;
10804             ModeHighlight();
10805             break;
10806           case BeginningOfGame:
10807             if (appData.icsActive) return;
10808             /* else fall through */
10809           case MachinePlaysWhite:
10810           case MachinePlaysBlack:
10811           case TwoMachinesPlay:
10812             if (forwardMostMove == 0)
10813               return;           /* don't pause if no one has moved */
10814             if ((gameMode == MachinePlaysWhite &&
10815                  !WhiteOnMove(forwardMostMove)) ||
10816                 (gameMode == MachinePlaysBlack &&
10817                  WhiteOnMove(forwardMostMove))) {
10818                 StopClocks();
10819             }
10820             pausing = TRUE;
10821             ModeHighlight();
10822             break;
10823         }
10824     }
10825 }
10826
10827 void
10828 EditCommentEvent()
10829 {
10830     char title[MSG_SIZ];
10831
10832     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10833         strcpy(title, _("Edit comment"));
10834     } else {
10835         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10836                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10837                 parseList[currentMove - 1]);
10838     }
10839
10840     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10841 }
10842
10843
10844 void
10845 EditTagsEvent()
10846 {
10847     char *tags = PGNTags(&gameInfo);
10848     EditTagsPopUp(tags);
10849     free(tags);
10850 }
10851
10852 void
10853 AnalyzeModeEvent()
10854 {
10855     if (appData.noChessProgram || gameMode == AnalyzeMode)
10856       return;
10857
10858     if (gameMode != AnalyzeFile) {
10859         if (!appData.icsEngineAnalyze) {
10860                EditGameEvent();
10861                if (gameMode != EditGame) return;
10862         }
10863         ResurrectChessProgram();
10864         SendToProgram("analyze\n", &first);
10865         first.analyzing = TRUE;
10866         /*first.maybeThinking = TRUE;*/
10867         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10868         EngineOutputPopUp();
10869     }
10870     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10871     pausing = FALSE;
10872     ModeHighlight();
10873     SetGameInfo();
10874
10875     StartAnalysisClock();
10876     GetTimeMark(&lastNodeCountTime);
10877     lastNodeCount = 0;
10878 }
10879
10880 void
10881 AnalyzeFileEvent()
10882 {
10883     if (appData.noChessProgram || gameMode == AnalyzeFile)
10884       return;
10885
10886     if (gameMode != AnalyzeMode) {
10887         EditGameEvent();
10888         if (gameMode != EditGame) return;
10889         ResurrectChessProgram();
10890         SendToProgram("analyze\n", &first);
10891         first.analyzing = TRUE;
10892         /*first.maybeThinking = TRUE;*/
10893         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10894         EngineOutputPopUp();
10895     }
10896     gameMode = AnalyzeFile;
10897     pausing = FALSE;
10898     ModeHighlight();
10899     SetGameInfo();
10900
10901     StartAnalysisClock();
10902     GetTimeMark(&lastNodeCountTime);
10903     lastNodeCount = 0;
10904 }
10905
10906 void
10907 MachineWhiteEvent()
10908 {
10909     char buf[MSG_SIZ];
10910     char *bookHit = NULL;
10911
10912     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10913       return;
10914
10915
10916     if (gameMode == PlayFromGameFile || 
10917         gameMode == TwoMachinesPlay  || 
10918         gameMode == Training         || 
10919         gameMode == AnalyzeMode      || 
10920         gameMode == EndOfGame)
10921         EditGameEvent();
10922
10923     if (gameMode == EditPosition) 
10924         EditPositionDone(TRUE);
10925
10926     if (!WhiteOnMove(currentMove)) {
10927         DisplayError(_("It is not White's turn"), 0);
10928         return;
10929     }
10930   
10931     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10932       ExitAnalyzeMode();
10933
10934     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10935         gameMode == AnalyzeFile)
10936         TruncateGame();
10937
10938     ResurrectChessProgram();    /* in case it isn't running */
10939     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10940         gameMode = MachinePlaysWhite;
10941         ResetClocks();
10942     } else
10943     gameMode = MachinePlaysWhite;
10944     pausing = FALSE;
10945     ModeHighlight();
10946     SetGameInfo();
10947     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10948     DisplayTitle(buf);
10949     if (first.sendName) {
10950       sprintf(buf, "name %s\n", gameInfo.black);
10951       SendToProgram(buf, &first);
10952     }
10953     if (first.sendTime) {
10954       if (first.useColors) {
10955         SendToProgram("black\n", &first); /*gnu kludge*/
10956       }
10957       SendTimeRemaining(&first, TRUE);
10958     }
10959     if (first.useColors) {
10960       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10961     }
10962     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10963     SetMachineThinkingEnables();
10964     first.maybeThinking = TRUE;
10965     StartClocks();
10966     firstMove = FALSE;
10967
10968     if (appData.autoFlipView && !flipView) {
10969       flipView = !flipView;
10970       DrawPosition(FALSE, NULL);
10971       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10972     }
10973
10974     if(bookHit) { // [HGM] book: simulate book reply
10975         static char bookMove[MSG_SIZ]; // a bit generous?
10976
10977         programStats.nodes = programStats.depth = programStats.time = 
10978         programStats.score = programStats.got_only_move = 0;
10979         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10980
10981         strcpy(bookMove, "move ");
10982         strcat(bookMove, bookHit);
10983         HandleMachineMove(bookMove, &first);
10984     }
10985 }
10986
10987 void
10988 MachineBlackEvent()
10989 {
10990     char buf[MSG_SIZ];
10991    char *bookHit = NULL;
10992
10993     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10994         return;
10995
10996
10997     if (gameMode == PlayFromGameFile || 
10998         gameMode == TwoMachinesPlay  || 
10999         gameMode == Training         || 
11000         gameMode == AnalyzeMode      || 
11001         gameMode == EndOfGame)
11002         EditGameEvent();
11003
11004     if (gameMode == EditPosition) 
11005         EditPositionDone(TRUE);
11006
11007     if (WhiteOnMove(currentMove)) {
11008         DisplayError(_("It is not Black's turn"), 0);
11009         return;
11010     }
11011     
11012     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11013       ExitAnalyzeMode();
11014
11015     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11016         gameMode == AnalyzeFile)
11017         TruncateGame();
11018
11019     ResurrectChessProgram();    /* in case it isn't running */
11020     gameMode = MachinePlaysBlack;
11021     pausing = FALSE;
11022     ModeHighlight();
11023     SetGameInfo();
11024     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11025     DisplayTitle(buf);
11026     if (first.sendName) {
11027       sprintf(buf, "name %s\n", gameInfo.white);
11028       SendToProgram(buf, &first);
11029     }
11030     if (first.sendTime) {
11031       if (first.useColors) {
11032         SendToProgram("white\n", &first); /*gnu kludge*/
11033       }
11034       SendTimeRemaining(&first, FALSE);
11035     }
11036     if (first.useColors) {
11037       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11038     }
11039     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11040     SetMachineThinkingEnables();
11041     first.maybeThinking = TRUE;
11042     StartClocks();
11043
11044     if (appData.autoFlipView && flipView) {
11045       flipView = !flipView;
11046       DrawPosition(FALSE, NULL);
11047       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11048     }
11049     if(bookHit) { // [HGM] book: simulate book reply
11050         static char bookMove[MSG_SIZ]; // a bit generous?
11051
11052         programStats.nodes = programStats.depth = programStats.time = 
11053         programStats.score = programStats.got_only_move = 0;
11054         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11055
11056         strcpy(bookMove, "move ");
11057         strcat(bookMove, bookHit);
11058         HandleMachineMove(bookMove, &first);
11059     }
11060 }
11061
11062
11063 void
11064 DisplayTwoMachinesTitle()
11065 {
11066     char buf[MSG_SIZ];
11067     if (appData.matchGames > 0) {
11068         if (first.twoMachinesColor[0] == 'w') {
11069             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11070                     gameInfo.white, gameInfo.black,
11071                     first.matchWins, second.matchWins,
11072                     matchGame - 1 - (first.matchWins + second.matchWins));
11073         } else {
11074             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11075                     gameInfo.white, gameInfo.black,
11076                     second.matchWins, first.matchWins,
11077                     matchGame - 1 - (first.matchWins + second.matchWins));
11078         }
11079     } else {
11080         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11081     }
11082     DisplayTitle(buf);
11083 }
11084
11085 void
11086 TwoMachinesEvent P((void))
11087 {
11088     int i;
11089     char buf[MSG_SIZ];
11090     ChessProgramState *onmove;
11091     char *bookHit = NULL;
11092     
11093     if (appData.noChessProgram) return;
11094
11095     switch (gameMode) {
11096       case TwoMachinesPlay:
11097         return;
11098       case MachinePlaysWhite:
11099       case MachinePlaysBlack:
11100         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11101             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11102             return;
11103         }
11104         /* fall through */
11105       case BeginningOfGame:
11106       case PlayFromGameFile:
11107       case EndOfGame:
11108         EditGameEvent();
11109         if (gameMode != EditGame) return;
11110         break;
11111       case EditPosition:
11112         EditPositionDone(TRUE);
11113         break;
11114       case AnalyzeMode:
11115       case AnalyzeFile:
11116         ExitAnalyzeMode();
11117         break;
11118       case EditGame:
11119       default:
11120         break;
11121     }
11122
11123 //    forwardMostMove = currentMove;
11124     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11125     ResurrectChessProgram();    /* in case first program isn't running */
11126
11127     if (second.pr == NULL) {
11128         StartChessProgram(&second);
11129         if (second.protocolVersion == 1) {
11130           TwoMachinesEventIfReady();
11131         } else {
11132           /* kludge: allow timeout for initial "feature" command */
11133           FreezeUI();
11134           DisplayMessage("", _("Starting second chess program"));
11135           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11136         }
11137         return;
11138     }
11139     DisplayMessage("", "");
11140     InitChessProgram(&second, FALSE);
11141     SendToProgram("force\n", &second);
11142     if (startedFromSetupPosition) {
11143         SendBoard(&second, backwardMostMove);
11144     if (appData.debugMode) {
11145         fprintf(debugFP, "Two Machines\n");
11146     }
11147     }
11148     for (i = backwardMostMove; i < forwardMostMove; i++) {
11149         SendMoveToProgram(i, &second);
11150     }
11151
11152     gameMode = TwoMachinesPlay;
11153     pausing = FALSE;
11154     ModeHighlight();
11155     SetGameInfo();
11156     DisplayTwoMachinesTitle();
11157     firstMove = TRUE;
11158     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11159         onmove = &first;
11160     } else {
11161         onmove = &second;
11162     }
11163
11164     SendToProgram(first.computerString, &first);
11165     if (first.sendName) {
11166       sprintf(buf, "name %s\n", second.tidy);
11167       SendToProgram(buf, &first);
11168     }
11169     SendToProgram(second.computerString, &second);
11170     if (second.sendName) {
11171       sprintf(buf, "name %s\n", first.tidy);
11172       SendToProgram(buf, &second);
11173     }
11174
11175     ResetClocks();
11176     if (!first.sendTime || !second.sendTime) {
11177         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11178         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11179     }
11180     if (onmove->sendTime) {
11181       if (onmove->useColors) {
11182         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11183       }
11184       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11185     }
11186     if (onmove->useColors) {
11187       SendToProgram(onmove->twoMachinesColor, onmove);
11188     }
11189     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11190 //    SendToProgram("go\n", onmove);
11191     onmove->maybeThinking = TRUE;
11192     SetMachineThinkingEnables();
11193
11194     StartClocks();
11195
11196     if(bookHit) { // [HGM] book: simulate book reply
11197         static char bookMove[MSG_SIZ]; // a bit generous?
11198
11199         programStats.nodes = programStats.depth = programStats.time = 
11200         programStats.score = programStats.got_only_move = 0;
11201         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11202
11203         strcpy(bookMove, "move ");
11204         strcat(bookMove, bookHit);
11205         savedMessage = bookMove; // args for deferred call
11206         savedState = onmove;
11207         ScheduleDelayedEvent(DeferredBookMove, 1);
11208     }
11209 }
11210
11211 void
11212 TrainingEvent()
11213 {
11214     if (gameMode == Training) {
11215       SetTrainingModeOff();
11216       gameMode = PlayFromGameFile;
11217       DisplayMessage("", _("Training mode off"));
11218     } else {
11219       gameMode = Training;
11220       animateTraining = appData.animate;
11221
11222       /* make sure we are not already at the end of the game */
11223       if (currentMove < forwardMostMove) {
11224         SetTrainingModeOn();
11225         DisplayMessage("", _("Training mode on"));
11226       } else {
11227         gameMode = PlayFromGameFile;
11228         DisplayError(_("Already at end of game"), 0);
11229       }
11230     }
11231     ModeHighlight();
11232 }
11233
11234 void
11235 IcsClientEvent()
11236 {
11237     if (!appData.icsActive) return;
11238     switch (gameMode) {
11239       case IcsPlayingWhite:
11240       case IcsPlayingBlack:
11241       case IcsObserving:
11242       case IcsIdle:
11243       case BeginningOfGame:
11244       case IcsExamining:
11245         return;
11246
11247       case EditGame:
11248         break;
11249
11250       case EditPosition:
11251         EditPositionDone(TRUE);
11252         break;
11253
11254       case AnalyzeMode:
11255       case AnalyzeFile:
11256         ExitAnalyzeMode();
11257         break;
11258         
11259       default:
11260         EditGameEvent();
11261         break;
11262     }
11263
11264     gameMode = IcsIdle;
11265     ModeHighlight();
11266     return;
11267 }
11268
11269
11270 void
11271 EditGameEvent()
11272 {
11273     int i;
11274
11275     switch (gameMode) {
11276       case Training:
11277         SetTrainingModeOff();
11278         break;
11279       case MachinePlaysWhite:
11280       case MachinePlaysBlack:
11281       case BeginningOfGame:
11282         SendToProgram("force\n", &first);
11283         SetUserThinkingEnables();
11284         break;
11285       case PlayFromGameFile:
11286         (void) StopLoadGameTimer();
11287         if (gameFileFP != NULL) {
11288             gameFileFP = NULL;
11289         }
11290         break;
11291       case EditPosition:
11292         EditPositionDone(TRUE);
11293         break;
11294       case AnalyzeMode:
11295       case AnalyzeFile:
11296         ExitAnalyzeMode();
11297         SendToProgram("force\n", &first);
11298         break;
11299       case TwoMachinesPlay:
11300         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11301         ResurrectChessProgram();
11302         SetUserThinkingEnables();
11303         break;
11304       case EndOfGame:
11305         ResurrectChessProgram();
11306         break;
11307       case IcsPlayingBlack:
11308       case IcsPlayingWhite:
11309         DisplayError(_("Warning: You are still playing a game"), 0);
11310         break;
11311       case IcsObserving:
11312         DisplayError(_("Warning: You are still observing a game"), 0);
11313         break;
11314       case IcsExamining:
11315         DisplayError(_("Warning: You are still examining a game"), 0);
11316         break;
11317       case IcsIdle:
11318         break;
11319       case EditGame:
11320       default:
11321         return;
11322     }
11323     
11324     pausing = FALSE;
11325     StopClocks();
11326     first.offeredDraw = second.offeredDraw = 0;
11327
11328     if (gameMode == PlayFromGameFile) {
11329         whiteTimeRemaining = timeRemaining[0][currentMove];
11330         blackTimeRemaining = timeRemaining[1][currentMove];
11331         DisplayTitle("");
11332     }
11333
11334     if (gameMode == MachinePlaysWhite ||
11335         gameMode == MachinePlaysBlack ||
11336         gameMode == TwoMachinesPlay ||
11337         gameMode == EndOfGame) {
11338         i = forwardMostMove;
11339         while (i > currentMove) {
11340             SendToProgram("undo\n", &first);
11341             i--;
11342         }
11343         whiteTimeRemaining = timeRemaining[0][currentMove];
11344         blackTimeRemaining = timeRemaining[1][currentMove];
11345         DisplayBothClocks();
11346         if (whiteFlag || blackFlag) {
11347             whiteFlag = blackFlag = 0;
11348         }
11349         DisplayTitle("");
11350     }           
11351     
11352     gameMode = EditGame;
11353     ModeHighlight();
11354     SetGameInfo();
11355 }
11356
11357
11358 void
11359 EditPositionEvent()
11360 {
11361     if (gameMode == EditPosition) {
11362         EditGameEvent();
11363         return;
11364     }
11365     
11366     EditGameEvent();
11367     if (gameMode != EditGame) return;
11368     
11369     gameMode = EditPosition;
11370     ModeHighlight();
11371     SetGameInfo();
11372     if (currentMove > 0)
11373       CopyBoard(boards[0], boards[currentMove]);
11374     
11375     blackPlaysFirst = !WhiteOnMove(currentMove);
11376     ResetClocks();
11377     currentMove = forwardMostMove = backwardMostMove = 0;
11378     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11379     DisplayMove(-1);
11380 }
11381
11382 void
11383 ExitAnalyzeMode()
11384 {
11385     /* [DM] icsEngineAnalyze - possible call from other functions */
11386     if (appData.icsEngineAnalyze) {
11387         appData.icsEngineAnalyze = FALSE;
11388
11389         DisplayMessage("",_("Close ICS engine analyze..."));
11390     }
11391     if (first.analysisSupport && first.analyzing) {
11392       SendToProgram("exit\n", &first);
11393       first.analyzing = FALSE;
11394     }
11395     thinkOutput[0] = NULLCHAR;
11396 }
11397
11398 void
11399 EditPositionDone(Boolean fakeRights)
11400 {
11401     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11402
11403     startedFromSetupPosition = TRUE;
11404     InitChessProgram(&first, FALSE);
11405     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11406       boards[0][EP_STATUS] = EP_NONE;
11407       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11408     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11409         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11410         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11411       } else boards[0][CASTLING][2] = NoRights;
11412     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11413         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11414         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11415       } else boards[0][CASTLING][5] = NoRights;
11416     }
11417     SendToProgram("force\n", &first);
11418     if (blackPlaysFirst) {
11419         strcpy(moveList[0], "");
11420         strcpy(parseList[0], "");
11421         currentMove = forwardMostMove = backwardMostMove = 1;
11422         CopyBoard(boards[1], boards[0]);
11423     } else {
11424         currentMove = forwardMostMove = backwardMostMove = 0;
11425     }
11426     SendBoard(&first, forwardMostMove);
11427     if (appData.debugMode) {
11428         fprintf(debugFP, "EditPosDone\n");
11429     }
11430     DisplayTitle("");
11431     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11432     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11433     gameMode = EditGame;
11434     ModeHighlight();
11435     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11436     ClearHighlights(); /* [AS] */
11437 }
11438
11439 /* Pause for `ms' milliseconds */
11440 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11441 void
11442 TimeDelay(ms)
11443      long ms;
11444 {
11445     TimeMark m1, m2;
11446
11447     GetTimeMark(&m1);
11448     do {
11449         GetTimeMark(&m2);
11450     } while (SubtractTimeMarks(&m2, &m1) < ms);
11451 }
11452
11453 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11454 void
11455 SendMultiLineToICS(buf)
11456      char *buf;
11457 {
11458     char temp[MSG_SIZ+1], *p;
11459     int len;
11460
11461     len = strlen(buf);
11462     if (len > MSG_SIZ)
11463       len = MSG_SIZ;
11464   
11465     strncpy(temp, buf, len);
11466     temp[len] = 0;
11467
11468     p = temp;
11469     while (*p) {
11470         if (*p == '\n' || *p == '\r')
11471           *p = ' ';
11472         ++p;
11473     }
11474
11475     strcat(temp, "\n");
11476     SendToICS(temp);
11477     SendToPlayer(temp, strlen(temp));
11478 }
11479
11480 void
11481 SetWhiteToPlayEvent()
11482 {
11483     if (gameMode == EditPosition) {
11484         blackPlaysFirst = FALSE;
11485         DisplayBothClocks();    /* works because currentMove is 0 */
11486     } else if (gameMode == IcsExamining) {
11487         SendToICS(ics_prefix);
11488         SendToICS("tomove white\n");
11489     }
11490 }
11491
11492 void
11493 SetBlackToPlayEvent()
11494 {
11495     if (gameMode == EditPosition) {
11496         blackPlaysFirst = TRUE;
11497         currentMove = 1;        /* kludge */
11498         DisplayBothClocks();
11499         currentMove = 0;
11500     } else if (gameMode == IcsExamining) {
11501         SendToICS(ics_prefix);
11502         SendToICS("tomove black\n");
11503     }
11504 }
11505
11506 void
11507 EditPositionMenuEvent(selection, x, y)
11508      ChessSquare selection;
11509      int x, y;
11510 {
11511     char buf[MSG_SIZ];
11512     ChessSquare piece = boards[0][y][x];
11513
11514     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11515
11516     switch (selection) {
11517       case ClearBoard:
11518         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11519             SendToICS(ics_prefix);
11520             SendToICS("bsetup clear\n");
11521         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11522             SendToICS(ics_prefix);
11523             SendToICS("clearboard\n");
11524         } else {
11525             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11526                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11527                 for (y = 0; y < BOARD_HEIGHT; y++) {
11528                     if (gameMode == IcsExamining) {
11529                         if (boards[currentMove][y][x] != EmptySquare) {
11530                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11531                                     AAA + x, ONE + y);
11532                             SendToICS(buf);
11533                         }
11534                     } else {
11535                         boards[0][y][x] = p;
11536                     }
11537                 }
11538             }
11539         }
11540         if (gameMode == EditPosition) {
11541             DrawPosition(FALSE, boards[0]);
11542         }
11543         break;
11544
11545       case WhitePlay:
11546         SetWhiteToPlayEvent();
11547         break;
11548
11549       case BlackPlay:
11550         SetBlackToPlayEvent();
11551         break;
11552
11553       case EmptySquare:
11554         if (gameMode == IcsExamining) {
11555             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11556             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11557             SendToICS(buf);
11558         } else {
11559             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11560                 if(x == BOARD_LEFT-2) {
11561                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11562                     boards[0][y][1] = 0;
11563                 } else
11564                 if(x == BOARD_RGHT+1) {
11565                     if(y >= gameInfo.holdingsSize) break;
11566                     boards[0][y][BOARD_WIDTH-2] = 0;
11567                 } else break;
11568             }
11569             boards[0][y][x] = EmptySquare;
11570             DrawPosition(FALSE, boards[0]);
11571         }
11572         break;
11573
11574       case PromotePiece:
11575         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11576            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11577             selection = (ChessSquare) (PROMOTED piece);
11578         } else if(piece == EmptySquare) selection = WhiteSilver;
11579         else selection = (ChessSquare)((int)piece - 1);
11580         goto defaultlabel;
11581
11582       case DemotePiece:
11583         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11584            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11585             selection = (ChessSquare) (DEMOTED piece);
11586         } else if(piece == EmptySquare) selection = BlackSilver;
11587         else selection = (ChessSquare)((int)piece + 1);       
11588         goto defaultlabel;
11589
11590       case WhiteQueen:
11591       case BlackQueen:
11592         if(gameInfo.variant == VariantShatranj ||
11593            gameInfo.variant == VariantXiangqi  ||
11594            gameInfo.variant == VariantCourier  ||
11595            gameInfo.variant == VariantMakruk     )
11596             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11597         goto defaultlabel;
11598
11599       case WhiteKing:
11600       case BlackKing:
11601         if(gameInfo.variant == VariantXiangqi)
11602             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11603         if(gameInfo.variant == VariantKnightmate)
11604             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11605       default:
11606         defaultlabel:
11607         if (gameMode == IcsExamining) {
11608             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11609             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11610                     PieceToChar(selection), AAA + x, ONE + y);
11611             SendToICS(buf);
11612         } else {
11613             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11614                 int n;
11615                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11616                     n = PieceToNumber(selection - BlackPawn);
11617                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11618                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11619                     boards[0][BOARD_HEIGHT-1-n][1]++;
11620                 } else
11621                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11622                     n = PieceToNumber(selection);
11623                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11624                     boards[0][n][BOARD_WIDTH-1] = selection;
11625                     boards[0][n][BOARD_WIDTH-2]++;
11626                 }
11627             } else
11628             boards[0][y][x] = selection;
11629             DrawPosition(TRUE, boards[0]);
11630         }
11631         break;
11632     }
11633 }
11634
11635
11636 void
11637 DropMenuEvent(selection, x, y)
11638      ChessSquare selection;
11639      int x, y;
11640 {
11641     ChessMove moveType;
11642
11643     switch (gameMode) {
11644       case IcsPlayingWhite:
11645       case MachinePlaysBlack:
11646         if (!WhiteOnMove(currentMove)) {
11647             DisplayMoveError(_("It is Black's turn"));
11648             return;
11649         }
11650         moveType = WhiteDrop;
11651         break;
11652       case IcsPlayingBlack:
11653       case MachinePlaysWhite:
11654         if (WhiteOnMove(currentMove)) {
11655             DisplayMoveError(_("It is White's turn"));
11656             return;
11657         }
11658         moveType = BlackDrop;
11659         break;
11660       case EditGame:
11661         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11662         break;
11663       default:
11664         return;
11665     }
11666
11667     if (moveType == BlackDrop && selection < BlackPawn) {
11668       selection = (ChessSquare) ((int) selection
11669                                  + (int) BlackPawn - (int) WhitePawn);
11670     }
11671     if (boards[currentMove][y][x] != EmptySquare) {
11672         DisplayMoveError(_("That square is occupied"));
11673         return;
11674     }
11675
11676     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11677 }
11678
11679 void
11680 AcceptEvent()
11681 {
11682     /* Accept a pending offer of any kind from opponent */
11683     
11684     if (appData.icsActive) {
11685         SendToICS(ics_prefix);
11686         SendToICS("accept\n");
11687     } else if (cmailMsgLoaded) {
11688         if (currentMove == cmailOldMove &&
11689             commentList[cmailOldMove] != NULL &&
11690             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11691                    "Black offers a draw" : "White offers a draw")) {
11692             TruncateGame();
11693             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11694             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11695         } else {
11696             DisplayError(_("There is no pending offer on this move"), 0);
11697             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11698         }
11699     } else {
11700         /* Not used for offers from chess program */
11701     }
11702 }
11703
11704 void
11705 DeclineEvent()
11706 {
11707     /* Decline a pending offer of any kind from opponent */
11708     
11709     if (appData.icsActive) {
11710         SendToICS(ics_prefix);
11711         SendToICS("decline\n");
11712     } else if (cmailMsgLoaded) {
11713         if (currentMove == cmailOldMove &&
11714             commentList[cmailOldMove] != NULL &&
11715             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11716                    "Black offers a draw" : "White offers a draw")) {
11717 #ifdef NOTDEF
11718             AppendComment(cmailOldMove, "Draw declined", TRUE);
11719             DisplayComment(cmailOldMove - 1, "Draw declined");
11720 #endif /*NOTDEF*/
11721         } else {
11722             DisplayError(_("There is no pending offer on this move"), 0);
11723         }
11724     } else {
11725         /* Not used for offers from chess program */
11726     }
11727 }
11728
11729 void
11730 RematchEvent()
11731 {
11732     /* Issue ICS rematch command */
11733     if (appData.icsActive) {
11734         SendToICS(ics_prefix);
11735         SendToICS("rematch\n");
11736     }
11737 }
11738
11739 void
11740 CallFlagEvent()
11741 {
11742     /* Call your opponent's flag (claim a win on time) */
11743     if (appData.icsActive) {
11744         SendToICS(ics_prefix);
11745         SendToICS("flag\n");
11746     } else {
11747         switch (gameMode) {
11748           default:
11749             return;
11750           case MachinePlaysWhite:
11751             if (whiteFlag) {
11752                 if (blackFlag)
11753                   GameEnds(GameIsDrawn, "Both players ran out of time",
11754                            GE_PLAYER);
11755                 else
11756                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11757             } else {
11758                 DisplayError(_("Your opponent is not out of time"), 0);
11759             }
11760             break;
11761           case MachinePlaysBlack:
11762             if (blackFlag) {
11763                 if (whiteFlag)
11764                   GameEnds(GameIsDrawn, "Both players ran out of time",
11765                            GE_PLAYER);
11766                 else
11767                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11768             } else {
11769                 DisplayError(_("Your opponent is not out of time"), 0);
11770             }
11771             break;
11772         }
11773     }
11774 }
11775
11776 void
11777 DrawEvent()
11778 {
11779     /* Offer draw or accept pending draw offer from opponent */
11780     
11781     if (appData.icsActive) {
11782         /* Note: tournament rules require draw offers to be
11783            made after you make your move but before you punch
11784            your clock.  Currently ICS doesn't let you do that;
11785            instead, you immediately punch your clock after making
11786            a move, but you can offer a draw at any time. */
11787         
11788         SendToICS(ics_prefix);
11789         SendToICS("draw\n");
11790     } else if (cmailMsgLoaded) {
11791         if (currentMove == cmailOldMove &&
11792             commentList[cmailOldMove] != NULL &&
11793             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11794                    "Black offers a draw" : "White offers a draw")) {
11795             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11796             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11797         } else if (currentMove == cmailOldMove + 1) {
11798             char *offer = WhiteOnMove(cmailOldMove) ?
11799               "White offers a draw" : "Black offers a draw";
11800             AppendComment(currentMove, offer, TRUE);
11801             DisplayComment(currentMove - 1, offer);
11802             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11803         } else {
11804             DisplayError(_("You must make your move before offering a draw"), 0);
11805             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11806         }
11807     } else if (first.offeredDraw) {
11808         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11809     } else {
11810         if (first.sendDrawOffers) {
11811             SendToProgram("draw\n", &first);
11812             userOfferedDraw = TRUE;
11813         }
11814     }
11815 }
11816
11817 void
11818 AdjournEvent()
11819 {
11820     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11821     
11822     if (appData.icsActive) {
11823         SendToICS(ics_prefix);
11824         SendToICS("adjourn\n");
11825     } else {
11826         /* Currently GNU Chess doesn't offer or accept Adjourns */
11827     }
11828 }
11829
11830
11831 void
11832 AbortEvent()
11833 {
11834     /* Offer Abort or accept pending Abort offer from opponent */
11835     
11836     if (appData.icsActive) {
11837         SendToICS(ics_prefix);
11838         SendToICS("abort\n");
11839     } else {
11840         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11841     }
11842 }
11843
11844 void
11845 ResignEvent()
11846 {
11847     /* Resign.  You can do this even if it's not your turn. */
11848     
11849     if (appData.icsActive) {
11850         SendToICS(ics_prefix);
11851         SendToICS("resign\n");
11852     } else {
11853         switch (gameMode) {
11854           case MachinePlaysWhite:
11855             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11856             break;
11857           case MachinePlaysBlack:
11858             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11859             break;
11860           case EditGame:
11861             if (cmailMsgLoaded) {
11862                 TruncateGame();
11863                 if (WhiteOnMove(cmailOldMove)) {
11864                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11865                 } else {
11866                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11867                 }
11868                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11869             }
11870             break;
11871           default:
11872             break;
11873         }
11874     }
11875 }
11876
11877
11878 void
11879 StopObservingEvent()
11880 {
11881     /* Stop observing current games */
11882     SendToICS(ics_prefix);
11883     SendToICS("unobserve\n");
11884 }
11885
11886 void
11887 StopExaminingEvent()
11888 {
11889     /* Stop observing current game */
11890     SendToICS(ics_prefix);
11891     SendToICS("unexamine\n");
11892 }
11893
11894 void
11895 ForwardInner(target)
11896      int target;
11897 {
11898     int limit;
11899
11900     if (appData.debugMode)
11901         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11902                 target, currentMove, forwardMostMove);
11903
11904     if (gameMode == EditPosition)
11905       return;
11906
11907     if (gameMode == PlayFromGameFile && !pausing)
11908       PauseEvent();
11909     
11910     if (gameMode == IcsExamining && pausing)
11911       limit = pauseExamForwardMostMove;
11912     else
11913       limit = forwardMostMove;
11914     
11915     if (target > limit) target = limit;
11916
11917     if (target > 0 && moveList[target - 1][0]) {
11918         int fromX, fromY, toX, toY;
11919         toX = moveList[target - 1][2] - AAA;
11920         toY = moveList[target - 1][3] - ONE;
11921         if (moveList[target - 1][1] == '@') {
11922             if (appData.highlightLastMove) {
11923                 SetHighlights(-1, -1, toX, toY);
11924             }
11925         } else {
11926             fromX = moveList[target - 1][0] - AAA;
11927             fromY = moveList[target - 1][1] - ONE;
11928             if (target == currentMove + 1) {
11929                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11930             }
11931             if (appData.highlightLastMove) {
11932                 SetHighlights(fromX, fromY, toX, toY);
11933             }
11934         }
11935     }
11936     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11937         gameMode == Training || gameMode == PlayFromGameFile || 
11938         gameMode == AnalyzeFile) {
11939         while (currentMove < target) {
11940             SendMoveToProgram(currentMove++, &first);
11941         }
11942     } else {
11943         currentMove = target;
11944     }
11945     
11946     if (gameMode == EditGame || gameMode == EndOfGame) {
11947         whiteTimeRemaining = timeRemaining[0][currentMove];
11948         blackTimeRemaining = timeRemaining[1][currentMove];
11949     }
11950     DisplayBothClocks();
11951     DisplayMove(currentMove - 1);
11952     DrawPosition(FALSE, boards[currentMove]);
11953     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11954     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11955         DisplayComment(currentMove - 1, commentList[currentMove]);
11956     }
11957 }
11958
11959
11960 void
11961 ForwardEvent()
11962 {
11963     if (gameMode == IcsExamining && !pausing) {
11964         SendToICS(ics_prefix);
11965         SendToICS("forward\n");
11966     } else {
11967         ForwardInner(currentMove + 1);
11968     }
11969 }
11970
11971 void
11972 ToEndEvent()
11973 {
11974     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11975         /* to optimze, we temporarily turn off analysis mode while we feed
11976          * the remaining moves to the engine. Otherwise we get analysis output
11977          * after each move.
11978          */ 
11979         if (first.analysisSupport) {
11980           SendToProgram("exit\nforce\n", &first);
11981           first.analyzing = FALSE;
11982         }
11983     }
11984         
11985     if (gameMode == IcsExamining && !pausing) {
11986         SendToICS(ics_prefix);
11987         SendToICS("forward 999999\n");
11988     } else {
11989         ForwardInner(forwardMostMove);
11990     }
11991
11992     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11993         /* we have fed all the moves, so reactivate analysis mode */
11994         SendToProgram("analyze\n", &first);
11995         first.analyzing = TRUE;
11996         /*first.maybeThinking = TRUE;*/
11997         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11998     }
11999 }
12000
12001 void
12002 BackwardInner(target)
12003      int target;
12004 {
12005     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12006
12007     if (appData.debugMode)
12008         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12009                 target, currentMove, forwardMostMove);
12010
12011     if (gameMode == EditPosition) return;
12012     if (currentMove <= backwardMostMove) {
12013         ClearHighlights();
12014         DrawPosition(full_redraw, boards[currentMove]);
12015         return;
12016     }
12017     if (gameMode == PlayFromGameFile && !pausing)
12018       PauseEvent();
12019     
12020     if (moveList[target][0]) {
12021         int fromX, fromY, toX, toY;
12022         toX = moveList[target][2] - AAA;
12023         toY = moveList[target][3] - ONE;
12024         if (moveList[target][1] == '@') {
12025             if (appData.highlightLastMove) {
12026                 SetHighlights(-1, -1, toX, toY);
12027             }
12028         } else {
12029             fromX = moveList[target][0] - AAA;
12030             fromY = moveList[target][1] - ONE;
12031             if (target == currentMove - 1) {
12032                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12033             }
12034             if (appData.highlightLastMove) {
12035                 SetHighlights(fromX, fromY, toX, toY);
12036             }
12037         }
12038     }
12039     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12040         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12041         while (currentMove > target) {
12042             SendToProgram("undo\n", &first);
12043             currentMove--;
12044         }
12045     } else {
12046         currentMove = target;
12047     }
12048     
12049     if (gameMode == EditGame || gameMode == EndOfGame) {
12050         whiteTimeRemaining = timeRemaining[0][currentMove];
12051         blackTimeRemaining = timeRemaining[1][currentMove];
12052     }
12053     DisplayBothClocks();
12054     DisplayMove(currentMove - 1);
12055     DrawPosition(full_redraw, boards[currentMove]);
12056     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12057     // [HGM] PV info: routine tests if comment empty
12058     DisplayComment(currentMove - 1, commentList[currentMove]);
12059 }
12060
12061 void
12062 BackwardEvent()
12063 {
12064     if (gameMode == IcsExamining && !pausing) {
12065         SendToICS(ics_prefix);
12066         SendToICS("backward\n");
12067     } else {
12068         BackwardInner(currentMove - 1);
12069     }
12070 }
12071
12072 void
12073 ToStartEvent()
12074 {
12075     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12076         /* to optimize, we temporarily turn off analysis mode while we undo
12077          * all the moves. Otherwise we get analysis output after each undo.
12078          */ 
12079         if (first.analysisSupport) {
12080           SendToProgram("exit\nforce\n", &first);
12081           first.analyzing = FALSE;
12082         }
12083     }
12084
12085     if (gameMode == IcsExamining && !pausing) {
12086         SendToICS(ics_prefix);
12087         SendToICS("backward 999999\n");
12088     } else {
12089         BackwardInner(backwardMostMove);
12090     }
12091
12092     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12093         /* we have fed all the moves, so reactivate analysis mode */
12094         SendToProgram("analyze\n", &first);
12095         first.analyzing = TRUE;
12096         /*first.maybeThinking = TRUE;*/
12097         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12098     }
12099 }
12100
12101 void
12102 ToNrEvent(int to)
12103 {
12104   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12105   if (to >= forwardMostMove) to = forwardMostMove;
12106   if (to <= backwardMostMove) to = backwardMostMove;
12107   if (to < currentMove) {
12108     BackwardInner(to);
12109   } else {
12110     ForwardInner(to);
12111   }
12112 }
12113
12114 void
12115 RevertEvent()
12116 {
12117     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12118         return;
12119     }
12120     if (gameMode != IcsExamining) {
12121         DisplayError(_("You are not examining a game"), 0);
12122         return;
12123     }
12124     if (pausing) {
12125         DisplayError(_("You can't revert while pausing"), 0);
12126         return;
12127     }
12128     SendToICS(ics_prefix);
12129     SendToICS("revert\n");
12130 }
12131
12132 void
12133 RetractMoveEvent()
12134 {
12135     switch (gameMode) {
12136       case MachinePlaysWhite:
12137       case MachinePlaysBlack:
12138         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12139             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12140             return;
12141         }
12142         if (forwardMostMove < 2) return;
12143         currentMove = forwardMostMove = forwardMostMove - 2;
12144         whiteTimeRemaining = timeRemaining[0][currentMove];
12145         blackTimeRemaining = timeRemaining[1][currentMove];
12146         DisplayBothClocks();
12147         DisplayMove(currentMove - 1);
12148         ClearHighlights();/*!! could figure this out*/
12149         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12150         SendToProgram("remove\n", &first);
12151         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12152         break;
12153
12154       case BeginningOfGame:
12155       default:
12156         break;
12157
12158       case IcsPlayingWhite:
12159       case IcsPlayingBlack:
12160         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12161             SendToICS(ics_prefix);
12162             SendToICS("takeback 2\n");
12163         } else {
12164             SendToICS(ics_prefix);
12165             SendToICS("takeback 1\n");
12166         }
12167         break;
12168     }
12169 }
12170
12171 void
12172 MoveNowEvent()
12173 {
12174     ChessProgramState *cps;
12175
12176     switch (gameMode) {
12177       case MachinePlaysWhite:
12178         if (!WhiteOnMove(forwardMostMove)) {
12179             DisplayError(_("It is your turn"), 0);
12180             return;
12181         }
12182         cps = &first;
12183         break;
12184       case MachinePlaysBlack:
12185         if (WhiteOnMove(forwardMostMove)) {
12186             DisplayError(_("It is your turn"), 0);
12187             return;
12188         }
12189         cps = &first;
12190         break;
12191       case TwoMachinesPlay:
12192         if (WhiteOnMove(forwardMostMove) ==
12193             (first.twoMachinesColor[0] == 'w')) {
12194             cps = &first;
12195         } else {
12196             cps = &second;
12197         }
12198         break;
12199       case BeginningOfGame:
12200       default:
12201         return;
12202     }
12203     SendToProgram("?\n", cps);
12204 }
12205
12206 void
12207 TruncateGameEvent()
12208 {
12209     EditGameEvent();
12210     if (gameMode != EditGame) return;
12211     TruncateGame();
12212 }
12213
12214 void
12215 TruncateGame()
12216 {
12217     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12218     if (forwardMostMove > currentMove) {
12219         if (gameInfo.resultDetails != NULL) {
12220             free(gameInfo.resultDetails);
12221             gameInfo.resultDetails = NULL;
12222             gameInfo.result = GameUnfinished;
12223         }
12224         forwardMostMove = currentMove;
12225         HistorySet(parseList, backwardMostMove, forwardMostMove,
12226                    currentMove-1);
12227     }
12228 }
12229
12230 void
12231 HintEvent()
12232 {
12233     if (appData.noChessProgram) return;
12234     switch (gameMode) {
12235       case MachinePlaysWhite:
12236         if (WhiteOnMove(forwardMostMove)) {
12237             DisplayError(_("Wait until your turn"), 0);
12238             return;
12239         }
12240         break;
12241       case BeginningOfGame:
12242       case MachinePlaysBlack:
12243         if (!WhiteOnMove(forwardMostMove)) {
12244             DisplayError(_("Wait until your turn"), 0);
12245             return;
12246         }
12247         break;
12248       default:
12249         DisplayError(_("No hint available"), 0);
12250         return;
12251     }
12252     SendToProgram("hint\n", &first);
12253     hintRequested = TRUE;
12254 }
12255
12256 void
12257 BookEvent()
12258 {
12259     if (appData.noChessProgram) return;
12260     switch (gameMode) {
12261       case MachinePlaysWhite:
12262         if (WhiteOnMove(forwardMostMove)) {
12263             DisplayError(_("Wait until your turn"), 0);
12264             return;
12265         }
12266         break;
12267       case BeginningOfGame:
12268       case MachinePlaysBlack:
12269         if (!WhiteOnMove(forwardMostMove)) {
12270             DisplayError(_("Wait until your turn"), 0);
12271             return;
12272         }
12273         break;
12274       case EditPosition:
12275         EditPositionDone(TRUE);
12276         break;
12277       case TwoMachinesPlay:
12278         return;
12279       default:
12280         break;
12281     }
12282     SendToProgram("bk\n", &first);
12283     bookOutput[0] = NULLCHAR;
12284     bookRequested = TRUE;
12285 }
12286
12287 void
12288 AboutGameEvent()
12289 {
12290     char *tags = PGNTags(&gameInfo);
12291     TagsPopUp(tags, CmailMsg());
12292     free(tags);
12293 }
12294
12295 /* end button procedures */
12296
12297 void
12298 PrintPosition(fp, move)
12299      FILE *fp;
12300      int move;
12301 {
12302     int i, j;
12303     
12304     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12305         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12306             char c = PieceToChar(boards[move][i][j]);
12307             fputc(c == 'x' ? '.' : c, fp);
12308             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12309         }
12310     }
12311     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12312       fprintf(fp, "white to play\n");
12313     else
12314       fprintf(fp, "black to play\n");
12315 }
12316
12317 void
12318 PrintOpponents(fp)
12319      FILE *fp;
12320 {
12321     if (gameInfo.white != NULL) {
12322         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12323     } else {
12324         fprintf(fp, "\n");
12325     }
12326 }
12327
12328 /* Find last component of program's own name, using some heuristics */
12329 void
12330 TidyProgramName(prog, host, buf)
12331      char *prog, *host, buf[MSG_SIZ];
12332 {
12333     char *p, *q;
12334     int local = (strcmp(host, "localhost") == 0);
12335     while (!local && (p = strchr(prog, ';')) != NULL) {
12336         p++;
12337         while (*p == ' ') p++;
12338         prog = p;
12339     }
12340     if (*prog == '"' || *prog == '\'') {
12341         q = strchr(prog + 1, *prog);
12342     } else {
12343         q = strchr(prog, ' ');
12344     }
12345     if (q == NULL) q = prog + strlen(prog);
12346     p = q;
12347     while (p >= prog && *p != '/' && *p != '\\') p--;
12348     p++;
12349     if(p == prog && *p == '"') p++;
12350     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12351     memcpy(buf, p, q - p);
12352     buf[q - p] = NULLCHAR;
12353     if (!local) {
12354         strcat(buf, "@");
12355         strcat(buf, host);
12356     }
12357 }
12358
12359 char *
12360 TimeControlTagValue()
12361 {
12362     char buf[MSG_SIZ];
12363     if (!appData.clockMode) {
12364         strcpy(buf, "-");
12365     } else if (movesPerSession > 0) {
12366         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12367     } else if (timeIncrement == 0) {
12368         sprintf(buf, "%ld", timeControl/1000);
12369     } else {
12370         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12371     }
12372     return StrSave(buf);
12373 }
12374
12375 void
12376 SetGameInfo()
12377 {
12378     /* This routine is used only for certain modes */
12379     VariantClass v = gameInfo.variant;
12380     ChessMove r = GameUnfinished;
12381     char *p = NULL;
12382
12383     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12384         r = gameInfo.result; 
12385         p = gameInfo.resultDetails; 
12386         gameInfo.resultDetails = NULL;
12387     }
12388     ClearGameInfo(&gameInfo);
12389     gameInfo.variant = v;
12390
12391     switch (gameMode) {
12392       case MachinePlaysWhite:
12393         gameInfo.event = StrSave( appData.pgnEventHeader );
12394         gameInfo.site = StrSave(HostName());
12395         gameInfo.date = PGNDate();
12396         gameInfo.round = StrSave("-");
12397         gameInfo.white = StrSave(first.tidy);
12398         gameInfo.black = StrSave(UserName());
12399         gameInfo.timeControl = TimeControlTagValue();
12400         break;
12401
12402       case MachinePlaysBlack:
12403         gameInfo.event = StrSave( appData.pgnEventHeader );
12404         gameInfo.site = StrSave(HostName());
12405         gameInfo.date = PGNDate();
12406         gameInfo.round = StrSave("-");
12407         gameInfo.white = StrSave(UserName());
12408         gameInfo.black = StrSave(first.tidy);
12409         gameInfo.timeControl = TimeControlTagValue();
12410         break;
12411
12412       case TwoMachinesPlay:
12413         gameInfo.event = StrSave( appData.pgnEventHeader );
12414         gameInfo.site = StrSave(HostName());
12415         gameInfo.date = PGNDate();
12416         if (matchGame > 0) {
12417             char buf[MSG_SIZ];
12418             sprintf(buf, "%d", matchGame);
12419             gameInfo.round = StrSave(buf);
12420         } else {
12421             gameInfo.round = StrSave("-");
12422         }
12423         if (first.twoMachinesColor[0] == 'w') {
12424             gameInfo.white = StrSave(first.tidy);
12425             gameInfo.black = StrSave(second.tidy);
12426         } else {
12427             gameInfo.white = StrSave(second.tidy);
12428             gameInfo.black = StrSave(first.tidy);
12429         }
12430         gameInfo.timeControl = TimeControlTagValue();
12431         break;
12432
12433       case EditGame:
12434         gameInfo.event = StrSave("Edited game");
12435         gameInfo.site = StrSave(HostName());
12436         gameInfo.date = PGNDate();
12437         gameInfo.round = StrSave("-");
12438         gameInfo.white = StrSave("-");
12439         gameInfo.black = StrSave("-");
12440         gameInfo.result = r;
12441         gameInfo.resultDetails = p;
12442         break;
12443
12444       case EditPosition:
12445         gameInfo.event = StrSave("Edited position");
12446         gameInfo.site = StrSave(HostName());
12447         gameInfo.date = PGNDate();
12448         gameInfo.round = StrSave("-");
12449         gameInfo.white = StrSave("-");
12450         gameInfo.black = StrSave("-");
12451         break;
12452
12453       case IcsPlayingWhite:
12454       case IcsPlayingBlack:
12455       case IcsObserving:
12456       case IcsExamining:
12457         break;
12458
12459       case PlayFromGameFile:
12460         gameInfo.event = StrSave("Game from non-PGN file");
12461         gameInfo.site = StrSave(HostName());
12462         gameInfo.date = PGNDate();
12463         gameInfo.round = StrSave("-");
12464         gameInfo.white = StrSave("?");
12465         gameInfo.black = StrSave("?");
12466         break;
12467
12468       default:
12469         break;
12470     }
12471 }
12472
12473 void
12474 ReplaceComment(index, text)
12475      int index;
12476      char *text;
12477 {
12478     int len;
12479
12480     while (*text == '\n') text++;
12481     len = strlen(text);
12482     while (len > 0 && text[len - 1] == '\n') len--;
12483
12484     if (commentList[index] != NULL)
12485       free(commentList[index]);
12486
12487     if (len == 0) {
12488         commentList[index] = NULL;
12489         return;
12490     }
12491   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12492       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12493       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12494     commentList[index] = (char *) malloc(len + 2);
12495     strncpy(commentList[index], text, len);
12496     commentList[index][len] = '\n';
12497     commentList[index][len + 1] = NULLCHAR;
12498   } else { 
12499     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12500     char *p;
12501     commentList[index] = (char *) malloc(len + 6);
12502     strcpy(commentList[index], "{\n");
12503     strncpy(commentList[index]+2, text, len);
12504     commentList[index][len+2] = NULLCHAR;
12505     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12506     strcat(commentList[index], "\n}\n");
12507   }
12508 }
12509
12510 void
12511 CrushCRs(text)
12512      char *text;
12513 {
12514   char *p = text;
12515   char *q = text;
12516   char ch;
12517
12518   do {
12519     ch = *p++;
12520     if (ch == '\r') continue;
12521     *q++ = ch;
12522   } while (ch != '\0');
12523 }
12524
12525 void
12526 AppendComment(index, text, addBraces)
12527      int index;
12528      char *text;
12529      Boolean addBraces; // [HGM] braces: tells if we should add {}
12530 {
12531     int oldlen, len;
12532     char *old;
12533
12534 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12535     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12536
12537     CrushCRs(text);
12538     while (*text == '\n') text++;
12539     len = strlen(text);
12540     while (len > 0 && text[len - 1] == '\n') len--;
12541
12542     if (len == 0) return;
12543
12544     if (commentList[index] != NULL) {
12545         old = commentList[index];
12546         oldlen = strlen(old);
12547         while(commentList[index][oldlen-1] ==  '\n')
12548           commentList[index][--oldlen] = NULLCHAR;
12549         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12550         strcpy(commentList[index], old);
12551         free(old);
12552         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12553         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12554           if(addBraces) addBraces = FALSE; else { text++; len--; }
12555           while (*text == '\n') { text++; len--; }
12556           commentList[index][--oldlen] = NULLCHAR;
12557       }
12558         if(addBraces) strcat(commentList[index], "\n{\n");
12559         else          strcat(commentList[index], "\n");
12560         strcat(commentList[index], text);
12561         if(addBraces) strcat(commentList[index], "\n}\n");
12562         else          strcat(commentList[index], "\n");
12563     } else {
12564         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12565         if(addBraces)
12566              strcpy(commentList[index], "{\n");
12567         else commentList[index][0] = NULLCHAR;
12568         strcat(commentList[index], text);
12569         strcat(commentList[index], "\n");
12570         if(addBraces) strcat(commentList[index], "}\n");
12571     }
12572 }
12573
12574 static char * FindStr( char * text, char * sub_text )
12575 {
12576     char * result = strstr( text, sub_text );
12577
12578     if( result != NULL ) {
12579         result += strlen( sub_text );
12580     }
12581
12582     return result;
12583 }
12584
12585 /* [AS] Try to extract PV info from PGN comment */
12586 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12587 char *GetInfoFromComment( int index, char * text )
12588 {
12589     char * sep = text;
12590
12591     if( text != NULL && index > 0 ) {
12592         int score = 0;
12593         int depth = 0;
12594         int time = -1, sec = 0, deci;
12595         char * s_eval = FindStr( text, "[%eval " );
12596         char * s_emt = FindStr( text, "[%emt " );
12597
12598         if( s_eval != NULL || s_emt != NULL ) {
12599             /* New style */
12600             char delim;
12601
12602             if( s_eval != NULL ) {
12603                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12604                     return text;
12605                 }
12606
12607                 if( delim != ']' ) {
12608                     return text;
12609                 }
12610             }
12611
12612             if( s_emt != NULL ) {
12613             }
12614                 return text;
12615         }
12616         else {
12617             /* We expect something like: [+|-]nnn.nn/dd */
12618             int score_lo = 0;
12619
12620             if(*text != '{') return text; // [HGM] braces: must be normal comment
12621
12622             sep = strchr( text, '/' );
12623             if( sep == NULL || sep < (text+4) ) {
12624                 return text;
12625             }
12626
12627             time = -1; sec = -1; deci = -1;
12628             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12629                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12630                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12631                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12632                 return text;
12633             }
12634
12635             if( score_lo < 0 || score_lo >= 100 ) {
12636                 return text;
12637             }
12638
12639             if(sec >= 0) time = 600*time + 10*sec; else
12640             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12641
12642             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12643
12644             /* [HGM] PV time: now locate end of PV info */
12645             while( *++sep >= '0' && *sep <= '9'); // strip depth
12646             if(time >= 0)
12647             while( *++sep >= '0' && *sep <= '9'); // strip time
12648             if(sec >= 0)
12649             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12650             if(deci >= 0)
12651             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12652             while(*sep == ' ') sep++;
12653         }
12654
12655         if( depth <= 0 ) {
12656             return text;
12657         }
12658
12659         if( time < 0 ) {
12660             time = -1;
12661         }
12662
12663         pvInfoList[index-1].depth = depth;
12664         pvInfoList[index-1].score = score;
12665         pvInfoList[index-1].time  = 10*time; // centi-sec
12666         if(*sep == '}') *sep = 0; else *--sep = '{';
12667     }
12668     return sep;
12669 }
12670
12671 void
12672 SendToProgram(message, cps)
12673      char *message;
12674      ChessProgramState *cps;
12675 {
12676     int count, outCount, error;
12677     char buf[MSG_SIZ];
12678
12679     if (cps->pr == NULL) return;
12680     Attention(cps);
12681     
12682     if (appData.debugMode) {
12683         TimeMark now;
12684         GetTimeMark(&now);
12685         fprintf(debugFP, "%ld >%-6s: %s", 
12686                 SubtractTimeMarks(&now, &programStartTime),
12687                 cps->which, message);
12688     }
12689     
12690     count = strlen(message);
12691     outCount = OutputToProcess(cps->pr, message, count, &error);
12692     if (outCount < count && !exiting 
12693                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12694         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12695         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12696             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12697                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12698                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12699             } else {
12700                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12701             }
12702             gameInfo.resultDetails = StrSave(buf);
12703         }
12704         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12705     }
12706 }
12707
12708 void
12709 ReceiveFromProgram(isr, closure, message, count, error)
12710      InputSourceRef isr;
12711      VOIDSTAR closure;
12712      char *message;
12713      int count;
12714      int error;
12715 {
12716     char *end_str;
12717     char buf[MSG_SIZ];
12718     ChessProgramState *cps = (ChessProgramState *)closure;
12719
12720     if (isr != cps->isr) return; /* Killed intentionally */
12721     if (count <= 0) {
12722         if (count == 0) {
12723             sprintf(buf,
12724                     _("Error: %s chess program (%s) exited unexpectedly"),
12725                     cps->which, cps->program);
12726         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12727                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12728                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12729                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12730                 } else {
12731                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12732                 }
12733                 gameInfo.resultDetails = StrSave(buf);
12734             }
12735             RemoveInputSource(cps->isr);
12736             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12737         } else {
12738             sprintf(buf,
12739                     _("Error reading from %s chess program (%s)"),
12740                     cps->which, cps->program);
12741             RemoveInputSource(cps->isr);
12742
12743             /* [AS] Program is misbehaving badly... kill it */
12744             if( count == -2 ) {
12745                 DestroyChildProcess( cps->pr, 9 );
12746                 cps->pr = NoProc;
12747             }
12748
12749             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12750         }
12751         return;
12752     }
12753     
12754     if ((end_str = strchr(message, '\r')) != NULL)
12755       *end_str = NULLCHAR;
12756     if ((end_str = strchr(message, '\n')) != NULL)
12757       *end_str = NULLCHAR;
12758     
12759     if (appData.debugMode) {
12760         TimeMark now; int print = 1;
12761         char *quote = ""; char c; int i;
12762
12763         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12764                 char start = message[0];
12765                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12766                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12767                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12768                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12769                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12770                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12771                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12772                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12773                         { quote = "# "; print = (appData.engineComments == 2); }
12774                 message[0] = start; // restore original message
12775         }
12776         if(print) {
12777                 GetTimeMark(&now);
12778                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12779                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12780                         quote,
12781                         message);
12782         }
12783     }
12784
12785     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12786     if (appData.icsEngineAnalyze) {
12787         if (strstr(message, "whisper") != NULL ||
12788              strstr(message, "kibitz") != NULL || 
12789             strstr(message, "tellics") != NULL) return;
12790     }
12791
12792     HandleMachineMove(message, cps);
12793 }
12794
12795
12796 void
12797 SendTimeControl(cps, mps, tc, inc, sd, st)
12798      ChessProgramState *cps;
12799      int mps, inc, sd, st;
12800      long tc;
12801 {
12802     char buf[MSG_SIZ];
12803     int seconds;
12804
12805     if( timeControl_2 > 0 ) {
12806         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12807             tc = timeControl_2;
12808         }
12809     }
12810     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12811     inc /= cps->timeOdds;
12812     st  /= cps->timeOdds;
12813
12814     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12815
12816     if (st > 0) {
12817       /* Set exact time per move, normally using st command */
12818       if (cps->stKludge) {
12819         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12820         seconds = st % 60;
12821         if (seconds == 0) {
12822           sprintf(buf, "level 1 %d\n", st/60);
12823         } else {
12824           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12825         }
12826       } else {
12827         sprintf(buf, "st %d\n", st);
12828       }
12829     } else {
12830       /* Set conventional or incremental time control, using level command */
12831       if (seconds == 0) {
12832         /* Note old gnuchess bug -- minutes:seconds used to not work.
12833            Fixed in later versions, but still avoid :seconds
12834            when seconds is 0. */
12835         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12836       } else {
12837         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12838                 seconds, inc/1000);
12839       }
12840     }
12841     SendToProgram(buf, cps);
12842
12843     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12844     /* Orthogonally, limit search to given depth */
12845     if (sd > 0) {
12846       if (cps->sdKludge) {
12847         sprintf(buf, "depth\n%d\n", sd);
12848       } else {
12849         sprintf(buf, "sd %d\n", sd);
12850       }
12851       SendToProgram(buf, cps);
12852     }
12853
12854     if(cps->nps > 0) { /* [HGM] nps */
12855         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12856         else {
12857                 sprintf(buf, "nps %d\n", cps->nps);
12858               SendToProgram(buf, cps);
12859         }
12860     }
12861 }
12862
12863 ChessProgramState *WhitePlayer()
12864 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12865 {
12866     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12867        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12868         return &second;
12869     return &first;
12870 }
12871
12872 void
12873 SendTimeRemaining(cps, machineWhite)
12874      ChessProgramState *cps;
12875      int /*boolean*/ machineWhite;
12876 {
12877     char message[MSG_SIZ];
12878     long time, otime;
12879
12880     /* Note: this routine must be called when the clocks are stopped
12881        or when they have *just* been set or switched; otherwise
12882        it will be off by the time since the current tick started.
12883     */
12884     if (machineWhite) {
12885         time = whiteTimeRemaining / 10;
12886         otime = blackTimeRemaining / 10;
12887     } else {
12888         time = blackTimeRemaining / 10;
12889         otime = whiteTimeRemaining / 10;
12890     }
12891     /* [HGM] translate opponent's time by time-odds factor */
12892     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12893     if (appData.debugMode) {
12894         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12895     }
12896
12897     if (time <= 0) time = 1;
12898     if (otime <= 0) otime = 1;
12899     
12900     sprintf(message, "time %ld\n", time);
12901     SendToProgram(message, cps);
12902
12903     sprintf(message, "otim %ld\n", otime);
12904     SendToProgram(message, cps);
12905 }
12906
12907 int
12908 BoolFeature(p, name, loc, cps)
12909      char **p;
12910      char *name;
12911      int *loc;
12912      ChessProgramState *cps;
12913 {
12914   char buf[MSG_SIZ];
12915   int len = strlen(name);
12916   int val;
12917   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12918     (*p) += len + 1;
12919     sscanf(*p, "%d", &val);
12920     *loc = (val != 0);
12921     while (**p && **p != ' ') (*p)++;
12922     sprintf(buf, "accepted %s\n", name);
12923     SendToProgram(buf, cps);
12924     return TRUE;
12925   }
12926   return FALSE;
12927 }
12928
12929 int
12930 IntFeature(p, name, loc, cps)
12931      char **p;
12932      char *name;
12933      int *loc;
12934      ChessProgramState *cps;
12935 {
12936   char buf[MSG_SIZ];
12937   int len = strlen(name);
12938   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12939     (*p) += len + 1;
12940     sscanf(*p, "%d", loc);
12941     while (**p && **p != ' ') (*p)++;
12942     sprintf(buf, "accepted %s\n", name);
12943     SendToProgram(buf, cps);
12944     return TRUE;
12945   }
12946   return FALSE;
12947 }
12948
12949 int
12950 StringFeature(p, name, loc, cps)
12951      char **p;
12952      char *name;
12953      char loc[];
12954      ChessProgramState *cps;
12955 {
12956   char buf[MSG_SIZ];
12957   int len = strlen(name);
12958   if (strncmp((*p), name, len) == 0
12959       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12960     (*p) += len + 2;
12961     sscanf(*p, "%[^\"]", loc);
12962     while (**p && **p != '\"') (*p)++;
12963     if (**p == '\"') (*p)++;
12964     sprintf(buf, "accepted %s\n", name);
12965     SendToProgram(buf, cps);
12966     return TRUE;
12967   }
12968   return FALSE;
12969 }
12970
12971 int 
12972 ParseOption(Option *opt, ChessProgramState *cps)
12973 // [HGM] options: process the string that defines an engine option, and determine
12974 // name, type, default value, and allowed value range
12975 {
12976         char *p, *q, buf[MSG_SIZ];
12977         int n, min = (-1)<<31, max = 1<<31, def;
12978
12979         if(p = strstr(opt->name, " -spin ")) {
12980             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12981             if(max < min) max = min; // enforce consistency
12982             if(def < min) def = min;
12983             if(def > max) def = max;
12984             opt->value = def;
12985             opt->min = min;
12986             opt->max = max;
12987             opt->type = Spin;
12988         } else if((p = strstr(opt->name, " -slider "))) {
12989             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12990             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12991             if(max < min) max = min; // enforce consistency
12992             if(def < min) def = min;
12993             if(def > max) def = max;
12994             opt->value = def;
12995             opt->min = min;
12996             opt->max = max;
12997             opt->type = Spin; // Slider;
12998         } else if((p = strstr(opt->name, " -string "))) {
12999             opt->textValue = p+9;
13000             opt->type = TextBox;
13001         } else if((p = strstr(opt->name, " -file "))) {
13002             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13003             opt->textValue = p+7;
13004             opt->type = TextBox; // FileName;
13005         } else if((p = strstr(opt->name, " -path "))) {
13006             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13007             opt->textValue = p+7;
13008             opt->type = TextBox; // PathName;
13009         } else if(p = strstr(opt->name, " -check ")) {
13010             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13011             opt->value = (def != 0);
13012             opt->type = CheckBox;
13013         } else if(p = strstr(opt->name, " -combo ")) {
13014             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13015             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13016             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13017             opt->value = n = 0;
13018             while(q = StrStr(q, " /// ")) {
13019                 n++; *q = 0;    // count choices, and null-terminate each of them
13020                 q += 5;
13021                 if(*q == '*') { // remember default, which is marked with * prefix
13022                     q++;
13023                     opt->value = n;
13024                 }
13025                 cps->comboList[cps->comboCnt++] = q;
13026             }
13027             cps->comboList[cps->comboCnt++] = NULL;
13028             opt->max = n + 1;
13029             opt->type = ComboBox;
13030         } else if(p = strstr(opt->name, " -button")) {
13031             opt->type = Button;
13032         } else if(p = strstr(opt->name, " -save")) {
13033             opt->type = SaveButton;
13034         } else return FALSE;
13035         *p = 0; // terminate option name
13036         // now look if the command-line options define a setting for this engine option.
13037         if(cps->optionSettings && cps->optionSettings[0])
13038             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13039         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13040                 sprintf(buf, "option %s", p);
13041                 if(p = strstr(buf, ",")) *p = 0;
13042                 strcat(buf, "\n");
13043                 SendToProgram(buf, cps);
13044         }
13045         return TRUE;
13046 }
13047
13048 void
13049 FeatureDone(cps, val)
13050      ChessProgramState* cps;
13051      int val;
13052 {
13053   DelayedEventCallback cb = GetDelayedEvent();
13054   if ((cb == InitBackEnd3 && cps == &first) ||
13055       (cb == TwoMachinesEventIfReady && cps == &second)) {
13056     CancelDelayedEvent();
13057     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13058   }
13059   cps->initDone = val;
13060 }
13061
13062 /* Parse feature command from engine */
13063 void
13064 ParseFeatures(args, cps)
13065      char* args;
13066      ChessProgramState *cps;  
13067 {
13068   char *p = args;
13069   char *q;
13070   int val;
13071   char buf[MSG_SIZ];
13072
13073   for (;;) {
13074     while (*p == ' ') p++;
13075     if (*p == NULLCHAR) return;
13076
13077     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13078     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13079     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13080     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13081     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13082     if (BoolFeature(&p, "reuse", &val, cps)) {
13083       /* Engine can disable reuse, but can't enable it if user said no */
13084       if (!val) cps->reuse = FALSE;
13085       continue;
13086     }
13087     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13088     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13089       if (gameMode == TwoMachinesPlay) {
13090         DisplayTwoMachinesTitle();
13091       } else {
13092         DisplayTitle("");
13093       }
13094       continue;
13095     }
13096     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13097     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13098     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13099     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13100     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13101     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13102     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13103     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13104     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13105     if (IntFeature(&p, "done", &val, cps)) {
13106       FeatureDone(cps, val);
13107       continue;
13108     }
13109     /* Added by Tord: */
13110     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13111     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13112     /* End of additions by Tord */
13113
13114     /* [HGM] added features: */
13115     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13116     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13117     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13118     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13119     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13120     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13121     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13122         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13123             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13124             SendToProgram(buf, cps);
13125             continue;
13126         }
13127         if(cps->nrOptions >= MAX_OPTIONS) {
13128             cps->nrOptions--;
13129             sprintf(buf, "%s engine has too many options\n", cps->which);
13130             DisplayError(buf, 0);
13131         }
13132         continue;
13133     }
13134     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13135     /* End of additions by HGM */
13136
13137     /* unknown feature: complain and skip */
13138     q = p;
13139     while (*q && *q != '=') q++;
13140     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13141     SendToProgram(buf, cps);
13142     p = q;
13143     if (*p == '=') {
13144       p++;
13145       if (*p == '\"') {
13146         p++;
13147         while (*p && *p != '\"') p++;
13148         if (*p == '\"') p++;
13149       } else {
13150         while (*p && *p != ' ') p++;
13151       }
13152     }
13153   }
13154
13155 }
13156
13157 void
13158 PeriodicUpdatesEvent(newState)
13159      int newState;
13160 {
13161     if (newState == appData.periodicUpdates)
13162       return;
13163
13164     appData.periodicUpdates=newState;
13165
13166     /* Display type changes, so update it now */
13167 //    DisplayAnalysis();
13168
13169     /* Get the ball rolling again... */
13170     if (newState) {
13171         AnalysisPeriodicEvent(1);
13172         StartAnalysisClock();
13173     }
13174 }
13175
13176 void
13177 PonderNextMoveEvent(newState)
13178      int newState;
13179 {
13180     if (newState == appData.ponderNextMove) return;
13181     if (gameMode == EditPosition) EditPositionDone(TRUE);
13182     if (newState) {
13183         SendToProgram("hard\n", &first);
13184         if (gameMode == TwoMachinesPlay) {
13185             SendToProgram("hard\n", &second);
13186         }
13187     } else {
13188         SendToProgram("easy\n", &first);
13189         thinkOutput[0] = NULLCHAR;
13190         if (gameMode == TwoMachinesPlay) {
13191             SendToProgram("easy\n", &second);
13192         }
13193     }
13194     appData.ponderNextMove = newState;
13195 }
13196
13197 void
13198 NewSettingEvent(option, command, value)
13199      char *command;
13200      int option, value;
13201 {
13202     char buf[MSG_SIZ];
13203
13204     if (gameMode == EditPosition) EditPositionDone(TRUE);
13205     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13206     SendToProgram(buf, &first);
13207     if (gameMode == TwoMachinesPlay) {
13208         SendToProgram(buf, &second);
13209     }
13210 }
13211
13212 void
13213 ShowThinkingEvent()
13214 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13215 {
13216     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13217     int newState = appData.showThinking
13218         // [HGM] thinking: other features now need thinking output as well
13219         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13220     
13221     if (oldState == newState) return;
13222     oldState = newState;
13223     if (gameMode == EditPosition) EditPositionDone(TRUE);
13224     if (oldState) {
13225         SendToProgram("post\n", &first);
13226         if (gameMode == TwoMachinesPlay) {
13227             SendToProgram("post\n", &second);
13228         }
13229     } else {
13230         SendToProgram("nopost\n", &first);
13231         thinkOutput[0] = NULLCHAR;
13232         if (gameMode == TwoMachinesPlay) {
13233             SendToProgram("nopost\n", &second);
13234         }
13235     }
13236 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13237 }
13238
13239 void
13240 AskQuestionEvent(title, question, replyPrefix, which)
13241      char *title; char *question; char *replyPrefix; char *which;
13242 {
13243   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13244   if (pr == NoProc) return;
13245   AskQuestion(title, question, replyPrefix, pr);
13246 }
13247
13248 void
13249 DisplayMove(moveNumber)
13250      int moveNumber;
13251 {
13252     char message[MSG_SIZ];
13253     char res[MSG_SIZ];
13254     char cpThinkOutput[MSG_SIZ];
13255
13256     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13257     
13258     if (moveNumber == forwardMostMove - 1 || 
13259         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13260
13261         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13262
13263         if (strchr(cpThinkOutput, '\n')) {
13264             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13265         }
13266     } else {
13267         *cpThinkOutput = NULLCHAR;
13268     }
13269
13270     /* [AS] Hide thinking from human user */
13271     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13272         *cpThinkOutput = NULLCHAR;
13273         if( thinkOutput[0] != NULLCHAR ) {
13274             int i;
13275
13276             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13277                 cpThinkOutput[i] = '.';
13278             }
13279             cpThinkOutput[i] = NULLCHAR;
13280             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13281         }
13282     }
13283
13284     if (moveNumber == forwardMostMove - 1 &&
13285         gameInfo.resultDetails != NULL) {
13286         if (gameInfo.resultDetails[0] == NULLCHAR) {
13287             sprintf(res, " %s", PGNResult(gameInfo.result));
13288         } else {
13289             sprintf(res, " {%s} %s",
13290                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13291         }
13292     } else {
13293         res[0] = NULLCHAR;
13294     }
13295
13296     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13297         DisplayMessage(res, cpThinkOutput);
13298     } else {
13299         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13300                 WhiteOnMove(moveNumber) ? " " : ".. ",
13301                 parseList[moveNumber], res);
13302         DisplayMessage(message, cpThinkOutput);
13303     }
13304 }
13305
13306 void
13307 DisplayComment(moveNumber, text)
13308      int moveNumber;
13309      char *text;
13310 {
13311     char title[MSG_SIZ];
13312     char buf[8000]; // comment can be long!
13313     int score, depth;
13314     
13315     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13316       strcpy(title, "Comment");
13317     } else {
13318       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13319               WhiteOnMove(moveNumber) ? " " : ".. ",
13320               parseList[moveNumber]);
13321     }
13322     // [HGM] PV info: display PV info together with (or as) comment
13323     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13324       if(text == NULL) text = "";                                           
13325       score = pvInfoList[moveNumber].score;
13326       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13327               depth, (pvInfoList[moveNumber].time+50)/100, text);
13328       text = buf;
13329     }
13330     if (text != NULL && (appData.autoDisplayComment || commentUp))
13331         CommentPopUp(title, text);
13332 }
13333
13334 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13335  * might be busy thinking or pondering.  It can be omitted if your
13336  * gnuchess is configured to stop thinking immediately on any user
13337  * input.  However, that gnuchess feature depends on the FIONREAD
13338  * ioctl, which does not work properly on some flavors of Unix.
13339  */
13340 void
13341 Attention(cps)
13342      ChessProgramState *cps;
13343 {
13344 #if ATTENTION
13345     if (!cps->useSigint) return;
13346     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13347     switch (gameMode) {
13348       case MachinePlaysWhite:
13349       case MachinePlaysBlack:
13350       case TwoMachinesPlay:
13351       case IcsPlayingWhite:
13352       case IcsPlayingBlack:
13353       case AnalyzeMode:
13354       case AnalyzeFile:
13355         /* Skip if we know it isn't thinking */
13356         if (!cps->maybeThinking) return;
13357         if (appData.debugMode)
13358           fprintf(debugFP, "Interrupting %s\n", cps->which);
13359         InterruptChildProcess(cps->pr);
13360         cps->maybeThinking = FALSE;
13361         break;
13362       default:
13363         break;
13364     }
13365 #endif /*ATTENTION*/
13366 }
13367
13368 int
13369 CheckFlags()
13370 {
13371     if (whiteTimeRemaining <= 0) {
13372         if (!whiteFlag) {
13373             whiteFlag = TRUE;
13374             if (appData.icsActive) {
13375                 if (appData.autoCallFlag &&
13376                     gameMode == IcsPlayingBlack && !blackFlag) {
13377                   SendToICS(ics_prefix);
13378                   SendToICS("flag\n");
13379                 }
13380             } else {
13381                 if (blackFlag) {
13382                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13383                 } else {
13384                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13385                     if (appData.autoCallFlag) {
13386                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13387                         return TRUE;
13388                     }
13389                 }
13390             }
13391         }
13392     }
13393     if (blackTimeRemaining <= 0) {
13394         if (!blackFlag) {
13395             blackFlag = TRUE;
13396             if (appData.icsActive) {
13397                 if (appData.autoCallFlag &&
13398                     gameMode == IcsPlayingWhite && !whiteFlag) {
13399                   SendToICS(ics_prefix);
13400                   SendToICS("flag\n");
13401                 }
13402             } else {
13403                 if (whiteFlag) {
13404                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13405                 } else {
13406                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13407                     if (appData.autoCallFlag) {
13408                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13409                         return TRUE;
13410                     }
13411                 }
13412             }
13413         }
13414     }
13415     return FALSE;
13416 }
13417
13418 void
13419 CheckTimeControl()
13420 {
13421     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13422         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13423
13424     /*
13425      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13426      */
13427     if ( !WhiteOnMove(forwardMostMove) )
13428         /* White made time control */
13429         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13430         /* [HGM] time odds: correct new time quota for time odds! */
13431                                             / WhitePlayer()->timeOdds;
13432       else
13433         /* Black made time control */
13434         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13435                                             / WhitePlayer()->other->timeOdds;
13436 }
13437
13438 void
13439 DisplayBothClocks()
13440 {
13441     int wom = gameMode == EditPosition ?
13442       !blackPlaysFirst : WhiteOnMove(currentMove);
13443     DisplayWhiteClock(whiteTimeRemaining, wom);
13444     DisplayBlackClock(blackTimeRemaining, !wom);
13445 }
13446
13447
13448 /* Timekeeping seems to be a portability nightmare.  I think everyone
13449    has ftime(), but I'm really not sure, so I'm including some ifdefs
13450    to use other calls if you don't.  Clocks will be less accurate if
13451    you have neither ftime nor gettimeofday.
13452 */
13453
13454 /* VS 2008 requires the #include outside of the function */
13455 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13456 #include <sys/timeb.h>
13457 #endif
13458
13459 /* Get the current time as a TimeMark */
13460 void
13461 GetTimeMark(tm)
13462      TimeMark *tm;
13463 {
13464 #if HAVE_GETTIMEOFDAY
13465
13466     struct timeval timeVal;
13467     struct timezone timeZone;
13468
13469     gettimeofday(&timeVal, &timeZone);
13470     tm->sec = (long) timeVal.tv_sec; 
13471     tm->ms = (int) (timeVal.tv_usec / 1000L);
13472
13473 #else /*!HAVE_GETTIMEOFDAY*/
13474 #if HAVE_FTIME
13475
13476 // include <sys/timeb.h> / moved to just above start of function
13477     struct timeb timeB;
13478
13479     ftime(&timeB);
13480     tm->sec = (long) timeB.time;
13481     tm->ms = (int) timeB.millitm;
13482
13483 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13484     tm->sec = (long) time(NULL);
13485     tm->ms = 0;
13486 #endif
13487 #endif
13488 }
13489
13490 /* Return the difference in milliseconds between two
13491    time marks.  We assume the difference will fit in a long!
13492 */
13493 long
13494 SubtractTimeMarks(tm2, tm1)
13495      TimeMark *tm2, *tm1;
13496 {
13497     return 1000L*(tm2->sec - tm1->sec) +
13498            (long) (tm2->ms - tm1->ms);
13499 }
13500
13501
13502 /*
13503  * Code to manage the game clocks.
13504  *
13505  * In tournament play, black starts the clock and then white makes a move.
13506  * We give the human user a slight advantage if he is playing white---the
13507  * clocks don't run until he makes his first move, so it takes zero time.
13508  * Also, we don't account for network lag, so we could get out of sync
13509  * with GNU Chess's clock -- but then, referees are always right.  
13510  */
13511
13512 static TimeMark tickStartTM;
13513 static long intendedTickLength;
13514
13515 long
13516 NextTickLength(timeRemaining)
13517      long timeRemaining;
13518 {
13519     long nominalTickLength, nextTickLength;
13520
13521     if (timeRemaining > 0L && timeRemaining <= 10000L)
13522       nominalTickLength = 100L;
13523     else
13524       nominalTickLength = 1000L;
13525     nextTickLength = timeRemaining % nominalTickLength;
13526     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13527
13528     return nextTickLength;
13529 }
13530
13531 /* Adjust clock one minute up or down */
13532 void
13533 AdjustClock(Boolean which, int dir)
13534 {
13535     if(which) blackTimeRemaining += 60000*dir;
13536     else      whiteTimeRemaining += 60000*dir;
13537     DisplayBothClocks();
13538 }
13539
13540 /* Stop clocks and reset to a fresh time control */
13541 void
13542 ResetClocks() 
13543 {
13544     (void) StopClockTimer();
13545     if (appData.icsActive) {
13546         whiteTimeRemaining = blackTimeRemaining = 0;
13547     } else if (searchTime) {
13548         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13549         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13550     } else { /* [HGM] correct new time quote for time odds */
13551         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13552         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13553     }
13554     if (whiteFlag || blackFlag) {
13555         DisplayTitle("");
13556         whiteFlag = blackFlag = FALSE;
13557     }
13558     DisplayBothClocks();
13559 }
13560
13561 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13562
13563 /* Decrement running clock by amount of time that has passed */
13564 void
13565 DecrementClocks()
13566 {
13567     long timeRemaining;
13568     long lastTickLength, fudge;
13569     TimeMark now;
13570
13571     if (!appData.clockMode) return;
13572     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13573         
13574     GetTimeMark(&now);
13575
13576     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13577
13578     /* Fudge if we woke up a little too soon */
13579     fudge = intendedTickLength - lastTickLength;
13580     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13581
13582     if (WhiteOnMove(forwardMostMove)) {
13583         if(whiteNPS >= 0) lastTickLength = 0;
13584         timeRemaining = whiteTimeRemaining -= lastTickLength;
13585         DisplayWhiteClock(whiteTimeRemaining - fudge,
13586                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13587     } else {
13588         if(blackNPS >= 0) lastTickLength = 0;
13589         timeRemaining = blackTimeRemaining -= lastTickLength;
13590         DisplayBlackClock(blackTimeRemaining - fudge,
13591                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13592     }
13593
13594     if (CheckFlags()) return;
13595         
13596     tickStartTM = now;
13597     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13598     StartClockTimer(intendedTickLength);
13599
13600     /* if the time remaining has fallen below the alarm threshold, sound the
13601      * alarm. if the alarm has sounded and (due to a takeback or time control
13602      * with increment) the time remaining has increased to a level above the
13603      * threshold, reset the alarm so it can sound again. 
13604      */
13605     
13606     if (appData.icsActive && appData.icsAlarm) {
13607
13608         /* make sure we are dealing with the user's clock */
13609         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13610                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13611            )) return;
13612
13613         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13614             alarmSounded = FALSE;
13615         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13616             PlayAlarmSound();
13617             alarmSounded = TRUE;
13618         }
13619     }
13620 }
13621
13622
13623 /* A player has just moved, so stop the previously running
13624    clock and (if in clock mode) start the other one.
13625    We redisplay both clocks in case we're in ICS mode, because
13626    ICS gives us an update to both clocks after every move.
13627    Note that this routine is called *after* forwardMostMove
13628    is updated, so the last fractional tick must be subtracted
13629    from the color that is *not* on move now.
13630 */
13631 void
13632 SwitchClocks()
13633 {
13634     long lastTickLength;
13635     TimeMark now;
13636     int flagged = FALSE;
13637
13638     GetTimeMark(&now);
13639
13640     if (StopClockTimer() && appData.clockMode) {
13641         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13642         if (WhiteOnMove(forwardMostMove)) {
13643             if(blackNPS >= 0) lastTickLength = 0;
13644             blackTimeRemaining -= lastTickLength;
13645            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13646 //         if(pvInfoList[forwardMostMove-1].time == -1)
13647                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13648                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13649         } else {
13650            if(whiteNPS >= 0) lastTickLength = 0;
13651            whiteTimeRemaining -= lastTickLength;
13652            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13653 //         if(pvInfoList[forwardMostMove-1].time == -1)
13654                  pvInfoList[forwardMostMove-1].time = 
13655                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13656         }
13657         flagged = CheckFlags();
13658     }
13659     CheckTimeControl();
13660
13661     if (flagged || !appData.clockMode) return;
13662
13663     switch (gameMode) {
13664       case MachinePlaysBlack:
13665       case MachinePlaysWhite:
13666       case BeginningOfGame:
13667         if (pausing) return;
13668         break;
13669
13670       case EditGame:
13671       case PlayFromGameFile:
13672       case IcsExamining:
13673         return;
13674
13675       default:
13676         break;
13677     }
13678
13679     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13680         if(WhiteOnMove(forwardMostMove))
13681              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13682         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13683     }
13684
13685     tickStartTM = now;
13686     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13687       whiteTimeRemaining : blackTimeRemaining);
13688     StartClockTimer(intendedTickLength);
13689 }
13690         
13691
13692 /* Stop both clocks */
13693 void
13694 StopClocks()
13695 {       
13696     long lastTickLength;
13697     TimeMark now;
13698
13699     if (!StopClockTimer()) return;
13700     if (!appData.clockMode) return;
13701
13702     GetTimeMark(&now);
13703
13704     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13705     if (WhiteOnMove(forwardMostMove)) {
13706         if(whiteNPS >= 0) lastTickLength = 0;
13707         whiteTimeRemaining -= lastTickLength;
13708         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13709     } else {
13710         if(blackNPS >= 0) lastTickLength = 0;
13711         blackTimeRemaining -= lastTickLength;
13712         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13713     }
13714     CheckFlags();
13715 }
13716         
13717 /* Start clock of player on move.  Time may have been reset, so
13718    if clock is already running, stop and restart it. */
13719 void
13720 StartClocks()
13721 {
13722     (void) StopClockTimer(); /* in case it was running already */
13723     DisplayBothClocks();
13724     if (CheckFlags()) return;
13725
13726     if (!appData.clockMode) return;
13727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13728
13729     GetTimeMark(&tickStartTM);
13730     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13731       whiteTimeRemaining : blackTimeRemaining);
13732
13733    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13734     whiteNPS = blackNPS = -1; 
13735     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13736        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13737         whiteNPS = first.nps;
13738     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13739        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13740         blackNPS = first.nps;
13741     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13742         whiteNPS = second.nps;
13743     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13744         blackNPS = second.nps;
13745     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13746
13747     StartClockTimer(intendedTickLength);
13748 }
13749
13750 char *
13751 TimeString(ms)
13752      long ms;
13753 {
13754     long second, minute, hour, day;
13755     char *sign = "";
13756     static char buf[32];
13757     
13758     if (ms > 0 && ms <= 9900) {
13759       /* convert milliseconds to tenths, rounding up */
13760       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13761
13762       sprintf(buf, " %03.1f ", tenths/10.0);
13763       return buf;
13764     }
13765
13766     /* convert milliseconds to seconds, rounding up */
13767     /* use floating point to avoid strangeness of integer division
13768        with negative dividends on many machines */
13769     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13770
13771     if (second < 0) {
13772         sign = "-";
13773         second = -second;
13774     }
13775     
13776     day = second / (60 * 60 * 24);
13777     second = second % (60 * 60 * 24);
13778     hour = second / (60 * 60);
13779     second = second % (60 * 60);
13780     minute = second / 60;
13781     second = second % 60;
13782     
13783     if (day > 0)
13784       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13785               sign, day, hour, minute, second);
13786     else if (hour > 0)
13787       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13788     else
13789       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13790     
13791     return buf;
13792 }
13793
13794
13795 /*
13796  * This is necessary because some C libraries aren't ANSI C compliant yet.
13797  */
13798 char *
13799 StrStr(string, match)
13800      char *string, *match;
13801 {
13802     int i, length;
13803     
13804     length = strlen(match);
13805     
13806     for (i = strlen(string) - length; i >= 0; i--, string++)
13807       if (!strncmp(match, string, length))
13808         return string;
13809     
13810     return NULL;
13811 }
13812
13813 char *
13814 StrCaseStr(string, match)
13815      char *string, *match;
13816 {
13817     int i, j, length;
13818     
13819     length = strlen(match);
13820     
13821     for (i = strlen(string) - length; i >= 0; i--, string++) {
13822         for (j = 0; j < length; j++) {
13823             if (ToLower(match[j]) != ToLower(string[j]))
13824               break;
13825         }
13826         if (j == length) return string;
13827     }
13828
13829     return NULL;
13830 }
13831
13832 #ifndef _amigados
13833 int
13834 StrCaseCmp(s1, s2)
13835      char *s1, *s2;
13836 {
13837     char c1, c2;
13838     
13839     for (;;) {
13840         c1 = ToLower(*s1++);
13841         c2 = ToLower(*s2++);
13842         if (c1 > c2) return 1;
13843         if (c1 < c2) return -1;
13844         if (c1 == NULLCHAR) return 0;
13845     }
13846 }
13847
13848
13849 int
13850 ToLower(c)
13851      int c;
13852 {
13853     return isupper(c) ? tolower(c) : c;
13854 }
13855
13856
13857 int
13858 ToUpper(c)
13859      int c;
13860 {
13861     return islower(c) ? toupper(c) : c;
13862 }
13863 #endif /* !_amigados    */
13864
13865 char *
13866 StrSave(s)
13867      char *s;
13868 {
13869     char *ret;
13870
13871     if ((ret = (char *) malloc(strlen(s) + 1))) {
13872         strcpy(ret, s);
13873     }
13874     return ret;
13875 }
13876
13877 char *
13878 StrSavePtr(s, savePtr)
13879      char *s, **savePtr;
13880 {
13881     if (*savePtr) {
13882         free(*savePtr);
13883     }
13884     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13885         strcpy(*savePtr, s);
13886     }
13887     return(*savePtr);
13888 }
13889
13890 char *
13891 PGNDate()
13892 {
13893     time_t clock;
13894     struct tm *tm;
13895     char buf[MSG_SIZ];
13896
13897     clock = time((time_t *)NULL);
13898     tm = localtime(&clock);
13899     sprintf(buf, "%04d.%02d.%02d",
13900             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13901     return StrSave(buf);
13902 }
13903
13904
13905 char *
13906 PositionToFEN(move, overrideCastling)
13907      int move;
13908      char *overrideCastling;
13909 {
13910     int i, j, fromX, fromY, toX, toY;
13911     int whiteToPlay;
13912     char buf[128];
13913     char *p, *q;
13914     int emptycount;
13915     ChessSquare piece;
13916
13917     whiteToPlay = (gameMode == EditPosition) ?
13918       !blackPlaysFirst : (move % 2 == 0);
13919     p = buf;
13920
13921     /* Piece placement data */
13922     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13923         emptycount = 0;
13924         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13925             if (boards[move][i][j] == EmptySquare) {
13926                 emptycount++;
13927             } else { ChessSquare piece = boards[move][i][j];
13928                 if (emptycount > 0) {
13929                     if(emptycount<10) /* [HGM] can be >= 10 */
13930                         *p++ = '0' + emptycount;
13931                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13932                     emptycount = 0;
13933                 }
13934                 if(PieceToChar(piece) == '+') {
13935                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13936                     *p++ = '+';
13937                     piece = (ChessSquare)(DEMOTED piece);
13938                 } 
13939                 *p++ = PieceToChar(piece);
13940                 if(p[-1] == '~') {
13941                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13942                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13943                     *p++ = '~';
13944                 }
13945             }
13946         }
13947         if (emptycount > 0) {
13948             if(emptycount<10) /* [HGM] can be >= 10 */
13949                 *p++ = '0' + emptycount;
13950             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13951             emptycount = 0;
13952         }
13953         *p++ = '/';
13954     }
13955     *(p - 1) = ' ';
13956
13957     /* [HGM] print Crazyhouse or Shogi holdings */
13958     if( gameInfo.holdingsWidth ) {
13959         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13960         q = p;
13961         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13962             piece = boards[move][i][BOARD_WIDTH-1];
13963             if( piece != EmptySquare )
13964               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13965                   *p++ = PieceToChar(piece);
13966         }
13967         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13968             piece = boards[move][BOARD_HEIGHT-i-1][0];
13969             if( piece != EmptySquare )
13970               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13971                   *p++ = PieceToChar(piece);
13972         }
13973
13974         if( q == p ) *p++ = '-';
13975         *p++ = ']';
13976         *p++ = ' ';
13977     }
13978
13979     /* Active color */
13980     *p++ = whiteToPlay ? 'w' : 'b';
13981     *p++ = ' ';
13982
13983   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13984     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13985   } else {
13986   if(nrCastlingRights) {
13987      q = p;
13988      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13989        /* [HGM] write directly from rights */
13990            if(boards[move][CASTLING][2] != NoRights &&
13991               boards[move][CASTLING][0] != NoRights   )
13992                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13993            if(boards[move][CASTLING][2] != NoRights &&
13994               boards[move][CASTLING][1] != NoRights   )
13995                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13996            if(boards[move][CASTLING][5] != NoRights &&
13997               boards[move][CASTLING][3] != NoRights   )
13998                 *p++ = boards[move][CASTLING][3] + AAA;
13999            if(boards[move][CASTLING][5] != NoRights &&
14000               boards[move][CASTLING][4] != NoRights   )
14001                 *p++ = boards[move][CASTLING][4] + AAA;
14002      } else {
14003
14004         /* [HGM] write true castling rights */
14005         if( nrCastlingRights == 6 ) {
14006             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14007                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14008             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14009                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14010             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14011                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14012             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14013                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14014         }
14015      }
14016      if (q == p) *p++ = '-'; /* No castling rights */
14017      *p++ = ' ';
14018   }
14019
14020   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14021      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14022     /* En passant target square */
14023     if (move > backwardMostMove) {
14024         fromX = moveList[move - 1][0] - AAA;
14025         fromY = moveList[move - 1][1] - ONE;
14026         toX = moveList[move - 1][2] - AAA;
14027         toY = moveList[move - 1][3] - ONE;
14028         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14029             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14030             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14031             fromX == toX) {
14032             /* 2-square pawn move just happened */
14033             *p++ = toX + AAA;
14034             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14035         } else {
14036             *p++ = '-';
14037         }
14038     } else if(move == backwardMostMove) {
14039         // [HGM] perhaps we should always do it like this, and forget the above?
14040         if((signed char)boards[move][EP_STATUS] >= 0) {
14041             *p++ = boards[move][EP_STATUS] + AAA;
14042             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14043         } else {
14044             *p++ = '-';
14045         }
14046     } else {
14047         *p++ = '-';
14048     }
14049     *p++ = ' ';
14050   }
14051   }
14052
14053     /* [HGM] find reversible plies */
14054     {   int i = 0, j=move;
14055
14056         if (appData.debugMode) { int k;
14057             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14058             for(k=backwardMostMove; k<=forwardMostMove; k++)
14059                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14060
14061         }
14062
14063         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14064         if( j == backwardMostMove ) i += initialRulePlies;
14065         sprintf(p, "%d ", i);
14066         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14067     }
14068     /* Fullmove number */
14069     sprintf(p, "%d", (move / 2) + 1);
14070     
14071     return StrSave(buf);
14072 }
14073
14074 Boolean
14075 ParseFEN(board, blackPlaysFirst, fen)
14076     Board board;
14077      int *blackPlaysFirst;
14078      char *fen;
14079 {
14080     int i, j;
14081     char *p;
14082     int emptycount;
14083     ChessSquare piece;
14084
14085     p = fen;
14086
14087     /* [HGM] by default clear Crazyhouse holdings, if present */
14088     if(gameInfo.holdingsWidth) {
14089        for(i=0; i<BOARD_HEIGHT; i++) {
14090            board[i][0]             = EmptySquare; /* black holdings */
14091            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14092            board[i][1]             = (ChessSquare) 0; /* black counts */
14093            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14094        }
14095     }
14096
14097     /* Piece placement data */
14098     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14099         j = 0;
14100         for (;;) {
14101             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14102                 if (*p == '/') p++;
14103                 emptycount = gameInfo.boardWidth - j;
14104                 while (emptycount--)
14105                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14106                 break;
14107 #if(BOARD_FILES >= 10)
14108             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14109                 p++; emptycount=10;
14110                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14111                 while (emptycount--)
14112                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14113 #endif
14114             } else if (isdigit(*p)) {
14115                 emptycount = *p++ - '0';
14116                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14117                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14118                 while (emptycount--)
14119                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14120             } else if (*p == '+' || isalpha(*p)) {
14121                 if (j >= gameInfo.boardWidth) return FALSE;
14122                 if(*p=='+') {
14123                     piece = CharToPiece(*++p);
14124                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14125                     piece = (ChessSquare) (PROMOTED piece ); p++;
14126                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14127                 } else piece = CharToPiece(*p++);
14128
14129                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14130                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14131                     piece = (ChessSquare) (PROMOTED piece);
14132                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14133                     p++;
14134                 }
14135                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14136             } else {
14137                 return FALSE;
14138             }
14139         }
14140     }
14141     while (*p == '/' || *p == ' ') p++;
14142
14143     /* [HGM] look for Crazyhouse holdings here */
14144     while(*p==' ') p++;
14145     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14146         if(*p == '[') p++;
14147         if(*p == '-' ) *p++; /* empty holdings */ else {
14148             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14149             /* if we would allow FEN reading to set board size, we would   */
14150             /* have to add holdings and shift the board read so far here   */
14151             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14152                 *p++;
14153                 if((int) piece >= (int) BlackPawn ) {
14154                     i = (int)piece - (int)BlackPawn;
14155                     i = PieceToNumber((ChessSquare)i);
14156                     if( i >= gameInfo.holdingsSize ) return FALSE;
14157                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14158                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14159                 } else {
14160                     i = (int)piece - (int)WhitePawn;
14161                     i = PieceToNumber((ChessSquare)i);
14162                     if( i >= gameInfo.holdingsSize ) return FALSE;
14163                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14164                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14165                 }
14166             }
14167         }
14168         if(*p == ']') *p++;
14169     }
14170
14171     while(*p == ' ') p++;
14172
14173     /* Active color */
14174     switch (*p++) {
14175       case 'w':
14176         *blackPlaysFirst = FALSE;
14177         break;
14178       case 'b': 
14179         *blackPlaysFirst = TRUE;
14180         break;
14181       default:
14182         return FALSE;
14183     }
14184
14185     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14186     /* return the extra info in global variiables             */
14187
14188     /* set defaults in case FEN is incomplete */
14189     board[EP_STATUS] = EP_UNKNOWN;
14190     for(i=0; i<nrCastlingRights; i++ ) {
14191         board[CASTLING][i] =
14192             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14193     }   /* assume possible unless obviously impossible */
14194     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14195     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14196     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14197                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14198     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14199     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14200     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14201                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14202     FENrulePlies = 0;
14203
14204     while(*p==' ') p++;
14205     if(nrCastlingRights) {
14206       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14207           /* castling indicator present, so default becomes no castlings */
14208           for(i=0; i<nrCastlingRights; i++ ) {
14209                  board[CASTLING][i] = NoRights;
14210           }
14211       }
14212       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14213              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14214              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14215              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14216         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14217
14218         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14219             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14220             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14221         }
14222         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14223             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14224         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14225                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14226         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14227                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14228         switch(c) {
14229           case'K':
14230               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14231               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14232               board[CASTLING][2] = whiteKingFile;
14233               break;
14234           case'Q':
14235               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14236               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14237               board[CASTLING][2] = whiteKingFile;
14238               break;
14239           case'k':
14240               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14241               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14242               board[CASTLING][5] = blackKingFile;
14243               break;
14244           case'q':
14245               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14246               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14247               board[CASTLING][5] = blackKingFile;
14248           case '-':
14249               break;
14250           default: /* FRC castlings */
14251               if(c >= 'a') { /* black rights */
14252                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14253                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14254                   if(i == BOARD_RGHT) break;
14255                   board[CASTLING][5] = i;
14256                   c -= AAA;
14257                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14258                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14259                   if(c > i)
14260                       board[CASTLING][3] = c;
14261                   else
14262                       board[CASTLING][4] = c;
14263               } else { /* white rights */
14264                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14265                     if(board[0][i] == WhiteKing) break;
14266                   if(i == BOARD_RGHT) break;
14267                   board[CASTLING][2] = i;
14268                   c -= AAA - 'a' + 'A';
14269                   if(board[0][c] >= WhiteKing) break;
14270                   if(c > i)
14271                       board[CASTLING][0] = c;
14272                   else
14273                       board[CASTLING][1] = c;
14274               }
14275         }
14276       }
14277       for(i=0; i<nrCastlingRights; i++)
14278         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14279     if (appData.debugMode) {
14280         fprintf(debugFP, "FEN castling rights:");
14281         for(i=0; i<nrCastlingRights; i++)
14282         fprintf(debugFP, " %d", board[CASTLING][i]);
14283         fprintf(debugFP, "\n");
14284     }
14285
14286       while(*p==' ') p++;
14287     }
14288
14289     /* read e.p. field in games that know e.p. capture */
14290     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14291        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14292       if(*p=='-') {
14293         p++; board[EP_STATUS] = EP_NONE;
14294       } else {
14295          char c = *p++ - AAA;
14296
14297          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14298          if(*p >= '0' && *p <='9') *p++;
14299          board[EP_STATUS] = c;
14300       }
14301     }
14302
14303
14304     if(sscanf(p, "%d", &i) == 1) {
14305         FENrulePlies = i; /* 50-move ply counter */
14306         /* (The move number is still ignored)    */
14307     }
14308
14309     return TRUE;
14310 }
14311       
14312 void
14313 EditPositionPasteFEN(char *fen)
14314 {
14315   if (fen != NULL) {
14316     Board initial_position;
14317
14318     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14319       DisplayError(_("Bad FEN position in clipboard"), 0);
14320       return ;
14321     } else {
14322       int savedBlackPlaysFirst = blackPlaysFirst;
14323       EditPositionEvent();
14324       blackPlaysFirst = savedBlackPlaysFirst;
14325       CopyBoard(boards[0], initial_position);
14326       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14327       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14328       DisplayBothClocks();
14329       DrawPosition(FALSE, boards[currentMove]);
14330     }
14331   }
14332 }
14333
14334 static char cseq[12] = "\\   ";
14335
14336 Boolean set_cont_sequence(char *new_seq)
14337 {
14338     int len;
14339     Boolean ret;
14340
14341     // handle bad attempts to set the sequence
14342         if (!new_seq)
14343                 return 0; // acceptable error - no debug
14344
14345     len = strlen(new_seq);
14346     ret = (len > 0) && (len < sizeof(cseq));
14347     if (ret)
14348         strcpy(cseq, new_seq);
14349     else if (appData.debugMode)
14350         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14351     return ret;
14352 }
14353
14354 /*
14355     reformat a source message so words don't cross the width boundary.  internal
14356     newlines are not removed.  returns the wrapped size (no null character unless
14357     included in source message).  If dest is NULL, only calculate the size required
14358     for the dest buffer.  lp argument indicats line position upon entry, and it's
14359     passed back upon exit.
14360 */
14361 int wrap(char *dest, char *src, int count, int width, int *lp)
14362 {
14363     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14364
14365     cseq_len = strlen(cseq);
14366     old_line = line = *lp;
14367     ansi = len = clen = 0;
14368
14369     for (i=0; i < count; i++)
14370     {
14371         if (src[i] == '\033')
14372             ansi = 1;
14373
14374         // if we hit the width, back up
14375         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14376         {
14377             // store i & len in case the word is too long
14378             old_i = i, old_len = len;
14379
14380             // find the end of the last word
14381             while (i && src[i] != ' ' && src[i] != '\n')
14382             {
14383                 i--;
14384                 len--;
14385             }
14386
14387             // word too long?  restore i & len before splitting it
14388             if ((old_i-i+clen) >= width)
14389             {
14390                 i = old_i;
14391                 len = old_len;
14392             }
14393
14394             // extra space?
14395             if (i && src[i-1] == ' ')
14396                 len--;
14397
14398             if (src[i] != ' ' && src[i] != '\n')
14399             {
14400                 i--;
14401                 if (len)
14402                     len--;
14403             }
14404
14405             // now append the newline and continuation sequence
14406             if (dest)
14407                 dest[len] = '\n';
14408             len++;
14409             if (dest)
14410                 strncpy(dest+len, cseq, cseq_len);
14411             len += cseq_len;
14412             line = cseq_len;
14413             clen = cseq_len;
14414             continue;
14415         }
14416
14417         if (dest)
14418             dest[len] = src[i];
14419         len++;
14420         if (!ansi)
14421             line++;
14422         if (src[i] == '\n')
14423             line = 0;
14424         if (src[i] == 'm')
14425             ansi = 0;
14426     }
14427     if (dest && appData.debugMode)
14428     {
14429         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14430             count, width, line, len, *lp);
14431         show_bytes(debugFP, src, count);
14432         fprintf(debugFP, "\ndest: ");
14433         show_bytes(debugFP, dest, len);
14434         fprintf(debugFP, "\n");
14435     }
14436     *lp = dest ? line : old_line;
14437
14438     return len;
14439 }
14440
14441 // [HGM] vari: routines for shelving variations
14442
14443 void 
14444 PushTail(int firstMove, int lastMove)
14445 {
14446         int i, j, nrMoves = lastMove - firstMove;
14447
14448         if(appData.icsActive) { // only in local mode
14449                 forwardMostMove = currentMove; // mimic old ICS behavior
14450                 return;
14451         }
14452         if(storedGames >= MAX_VARIATIONS-1) return;
14453
14454         // push current tail of game on stack
14455         savedResult[storedGames] = gameInfo.result;
14456         savedDetails[storedGames] = gameInfo.resultDetails;
14457         gameInfo.resultDetails = NULL;
14458         savedFirst[storedGames] = firstMove;
14459         savedLast [storedGames] = lastMove;
14460         savedFramePtr[storedGames] = framePtr;
14461         framePtr -= nrMoves; // reserve space for the boards
14462         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14463             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14464             for(j=0; j<MOVE_LEN; j++)
14465                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14466             for(j=0; j<2*MOVE_LEN; j++)
14467                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14468             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14469             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14470             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14471             pvInfoList[firstMove+i-1].depth = 0;
14472             commentList[framePtr+i] = commentList[firstMove+i];
14473             commentList[firstMove+i] = NULL;
14474         }
14475
14476         storedGames++;
14477         forwardMostMove = currentMove; // truncte game so we can start variation
14478         if(storedGames == 1) GreyRevert(FALSE);
14479 }
14480
14481 Boolean
14482 PopTail(Boolean annotate)
14483 {
14484         int i, j, nrMoves;
14485         char buf[8000], moveBuf[20];
14486
14487         if(appData.icsActive) return FALSE; // only in local mode
14488         if(!storedGames) return FALSE; // sanity
14489
14490         storedGames--;
14491         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14492         nrMoves = savedLast[storedGames] - currentMove;
14493         if(annotate) {
14494                 int cnt = 10;
14495                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14496                 else strcpy(buf, "(");
14497                 for(i=currentMove; i<forwardMostMove; i++) {
14498                         if(WhiteOnMove(i))
14499                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14500                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14501                         strcat(buf, moveBuf);
14502                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14503                 }
14504                 strcat(buf, ")");
14505         }
14506         for(i=1; i<nrMoves; i++) { // copy last variation back
14507             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14508             for(j=0; j<MOVE_LEN; j++)
14509                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14510             for(j=0; j<2*MOVE_LEN; j++)
14511                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14512             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14513             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14514             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14515             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14516             commentList[currentMove+i] = commentList[framePtr+i];
14517             commentList[framePtr+i] = NULL;
14518         }
14519         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14520         framePtr = savedFramePtr[storedGames];
14521         gameInfo.result = savedResult[storedGames];
14522         if(gameInfo.resultDetails != NULL) {
14523             free(gameInfo.resultDetails);
14524       }
14525         gameInfo.resultDetails = savedDetails[storedGames];
14526         forwardMostMove = currentMove + nrMoves;
14527         if(storedGames == 0) GreyRevert(TRUE);
14528         return TRUE;
14529 }
14530
14531 void 
14532 CleanupTail()
14533 {       // remove all shelved variations
14534         int i;
14535         for(i=0; i<storedGames; i++) {
14536             if(savedDetails[i])
14537                 free(savedDetails[i]);
14538             savedDetails[i] = NULL;
14539         }
14540         for(i=framePtr; i<MAX_MOVES; i++) {
14541                 if(commentList[i]) free(commentList[i]);
14542                 commentList[i] = NULL;
14543         }
14544         framePtr = MAX_MOVES-1;
14545         storedGames = 0;
14546 }