65fc44b58f41eda6d80bfbbf71cb7314a1d87704
[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 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5962 {   // [HGM] book: this routine intercepts moves to simulate book replies
5963     char *bookHit = NULL;
5964
5965     //first determine if the incoming move brings opponent into his book
5966     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5967         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5968     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5969     if(bookHit != NULL && !cps->bookSuspend) {
5970         // make sure opponent is not going to reply after receiving move to book position
5971         SendToProgram("force\n", cps);
5972         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5973     }
5974     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5975     // now arrange restart after book miss
5976     if(bookHit) {
5977         // after a book hit we never send 'go', and the code after the call to this routine
5978         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5979         char buf[MSG_SIZ];
5980         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5981         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5982         SendToProgram(buf, cps);
5983         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5984     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5985         SendToProgram("go\n", cps);
5986         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5987     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5988         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5989             SendToProgram("go\n", cps); 
5990         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5991     }
5992     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5993 }
5994
5995 char *savedMessage;
5996 ChessProgramState *savedState;
5997 void DeferredBookMove(void)
5998 {
5999         if(savedState->lastPing != savedState->lastPong)
6000                     ScheduleDelayedEvent(DeferredBookMove, 10);
6001         else
6002         HandleMachineMove(savedMessage, savedState);
6003 }
6004
6005 void
6006 HandleMachineMove(message, cps)
6007      char *message;
6008      ChessProgramState *cps;
6009 {
6010     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6011     char realname[MSG_SIZ];
6012     int fromX, fromY, toX, toY;
6013     ChessMove moveType;
6014     char promoChar;
6015     char *p;
6016     int machineWhite;
6017     char *bookHit;
6018
6019     cps->userError = 0;
6020
6021 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6022     /*
6023      * Kludge to ignore BEL characters
6024      */
6025     while (*message == '\007') message++;
6026
6027     /*
6028      * [HGM] engine debug message: ignore lines starting with '#' character
6029      */
6030     if(cps->debug && *message == '#') return;
6031
6032     /*
6033      * Look for book output
6034      */
6035     if (cps == &first && bookRequested) {
6036         if (message[0] == '\t' || message[0] == ' ') {
6037             /* Part of the book output is here; append it */
6038             strcat(bookOutput, message);
6039             strcat(bookOutput, "  \n");
6040             return;
6041         } else if (bookOutput[0] != NULLCHAR) {
6042             /* All of book output has arrived; display it */
6043             char *p = bookOutput;
6044             while (*p != NULLCHAR) {
6045                 if (*p == '\t') *p = ' ';
6046                 p++;
6047             }
6048             DisplayInformation(bookOutput);
6049             bookRequested = FALSE;
6050             /* Fall through to parse the current output */
6051         }
6052     }
6053
6054     /*
6055      * Look for machine move.
6056      */
6057     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6058         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6059     {
6060         /* This method is only useful on engines that support ping */
6061         if (cps->lastPing != cps->lastPong) {
6062           if (gameMode == BeginningOfGame) {
6063             /* Extra move from before last new; ignore */
6064             if (appData.debugMode) {
6065                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6066             }
6067           } else {
6068             if (appData.debugMode) {
6069                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6070                         cps->which, gameMode);
6071             }
6072
6073             SendToProgram("undo\n", cps);
6074           }
6075           return;
6076         }
6077
6078         switch (gameMode) {
6079           case BeginningOfGame:
6080             /* Extra move from before last reset; ignore */
6081             if (appData.debugMode) {
6082                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6083             }
6084             return;
6085
6086           case EndOfGame:
6087           case IcsIdle:
6088           default:
6089             /* Extra move after we tried to stop.  The mode test is
6090                not a reliable way of detecting this problem, but it's
6091                the best we can do on engines that don't support ping.
6092             */
6093             if (appData.debugMode) {
6094                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6095                         cps->which, gameMode);
6096             }
6097             SendToProgram("undo\n", cps);
6098             return;
6099
6100           case MachinePlaysWhite:
6101           case IcsPlayingWhite:
6102             machineWhite = TRUE;
6103             break;
6104
6105           case MachinePlaysBlack:
6106           case IcsPlayingBlack:
6107             machineWhite = FALSE;
6108             break;
6109
6110           case TwoMachinesPlay:
6111             machineWhite = (cps->twoMachinesColor[0] == 'w');
6112             break;
6113         }
6114         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6115             if (appData.debugMode) {
6116                 fprintf(debugFP,
6117                         "Ignoring move out of turn by %s, gameMode %d"
6118                         ", forwardMost %d\n",
6119                         cps->which, gameMode, forwardMostMove);
6120             }
6121             return;
6122         }
6123
6124     if (appData.debugMode) { int f = forwardMostMove;
6125         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6126                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6127                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6128     }
6129         if(cps->alphaRank) AlphaRank(machineMove, 4);
6130         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6131                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6132             /* Machine move could not be parsed; ignore it. */
6133             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6134                     machineMove, cps->which);
6135             DisplayError(buf1, 0);
6136             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6137                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6138             if (gameMode == TwoMachinesPlay) {
6139               GameEnds(machineWhite ? BlackWins : WhiteWins,
6140                        buf1, GE_XBOARD);
6141             }
6142             return;
6143         }
6144
6145         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6146         /* So we have to redo legality test with true e.p. status here,  */
6147         /* to make sure an illegal e.p. capture does not slip through,   */
6148         /* to cause a forfeit on a justified illegal-move complaint      */
6149         /* of the opponent.                                              */
6150         if( gameMode==TwoMachinesPlay && appData.testLegality
6151             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6152                                                               ) {
6153            ChessMove moveType;
6154            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6155                              fromY, fromX, toY, toX, promoChar);
6156             if (appData.debugMode) {
6157                 int i;
6158                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6159                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6160                 fprintf(debugFP, "castling rights\n");
6161             }
6162             if(moveType == IllegalMove) {
6163                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6164                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6165                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6166                            buf1, GE_XBOARD);
6167                 return;
6168            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6169            /* [HGM] Kludge to handle engines that send FRC-style castling
6170               when they shouldn't (like TSCP-Gothic) */
6171            switch(moveType) {
6172              case WhiteASideCastleFR:
6173              case BlackASideCastleFR:
6174                toX+=2;
6175                currentMoveString[2]++;
6176                break;
6177              case WhiteHSideCastleFR:
6178              case BlackHSideCastleFR:
6179                toX--;
6180                currentMoveString[2]--;
6181                break;
6182              default: ; // nothing to do, but suppresses warning of pedantic compilers
6183            }
6184         }
6185         hintRequested = FALSE;
6186         lastHint[0] = NULLCHAR;
6187         bookRequested = FALSE;
6188         /* Program may be pondering now */
6189         cps->maybeThinking = TRUE;
6190         if (cps->sendTime == 2) cps->sendTime = 1;
6191         if (cps->offeredDraw) cps->offeredDraw--;
6192
6193 #if ZIPPY
6194         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6195             first.initDone) {
6196           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6197           ics_user_moved = 1;
6198           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6199                 char buf[3*MSG_SIZ];
6200
6201                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6202                         programStats.score / 100.,
6203                         programStats.depth,
6204                         programStats.time / 100.,
6205                         (unsigned int)programStats.nodes,
6206                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6207                         programStats.movelist);
6208                 SendToICS(buf);
6209 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6210           }
6211         }
6212 #endif
6213         /* currentMoveString is set as a side-effect of ParseOneMove */
6214         strcpy(machineMove, currentMoveString);
6215         strcat(machineMove, "\n");
6216         strcpy(moveList[forwardMostMove], machineMove);
6217
6218         /* [AS] Save move info and clear stats for next move */
6219         pvInfoList[ forwardMostMove ].score = programStats.score;
6220         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6221         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6222         ClearProgramStats();
6223         thinkOutput[0] = NULLCHAR;
6224         hiddenThinkOutputState = 0;
6225
6226         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6227
6228         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6229         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6230             int count = 0;
6231
6232             while( count < adjudicateLossPlies ) {
6233                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6234
6235                 if( count & 1 ) {
6236                     score = -score; /* Flip score for winning side */
6237                 }
6238
6239                 if( score > adjudicateLossThreshold ) {
6240                     break;
6241                 }
6242
6243                 count++;
6244             }
6245
6246             if( count >= adjudicateLossPlies ) {
6247                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6248
6249                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6250                     "Xboard adjudication", 
6251                     GE_XBOARD );
6252
6253                 return;
6254             }
6255         }
6256
6257         if( gameMode == TwoMachinesPlay ) {
6258           // [HGM] some adjudications useful with buggy engines
6259             int k, count = 0; static int bare = 1;
6260           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6261
6262
6263             if( appData.testLegality )
6264             {   /* [HGM] Some more adjudications for obstinate engines */
6265                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6266                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6267                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6268                 static int moveCount = 6;
6269                 ChessMove result;
6270                 char *reason = NULL;
6271
6272                 /* Count what is on board. */
6273                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6274                 {   ChessSquare p = boards[forwardMostMove][i][j];
6275                     int m=i;
6276
6277                     switch((int) p)
6278                     {   /* count B,N,R and other of each side */
6279                         case WhiteKing:
6280                         case BlackKing:
6281                              NrK++; break; // [HGM] atomic: count Kings
6282                         case WhiteKnight:
6283                              NrWN++; break;
6284                         case WhiteBishop:
6285                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6286                              bishopsColor |= 1 << ((i^j)&1);
6287                              NrWB++; break;
6288                         case BlackKnight:
6289                              NrBN++; break;
6290                         case BlackBishop:
6291                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6292                              bishopsColor |= 1 << ((i^j)&1);
6293                              NrBB++; break;
6294                         case WhiteRook:
6295                              NrWR++; break;
6296                         case BlackRook:
6297                              NrBR++; break;
6298                         case WhiteQueen:
6299                              NrWQ++; break;
6300                         case BlackQueen:
6301                              NrBQ++; break;
6302                         case EmptySquare: 
6303                              break;
6304                         case BlackPawn:
6305                              m = 7-i;
6306                         case WhitePawn:
6307                              PawnAdvance += m; NrPawns++;
6308                     }
6309                     NrPieces += (p != EmptySquare);
6310                     NrW += ((int)p < (int)BlackPawn);
6311                     if(gameInfo.variant == VariantXiangqi && 
6312                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6313                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6314                         NrW -= ((int)p < (int)BlackPawn);
6315                     }
6316                 }
6317
6318                 /* Some material-based adjudications that have to be made before stalemate test */
6319                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6320                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6321                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6322                      if(appData.checkMates) {
6323                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6324                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6325                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6326                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6327                          return;
6328                      }
6329                 }
6330
6331                 /* Bare King in Shatranj (loses) or Losers (wins) */
6332                 if( NrW == 1 || NrPieces - NrW == 1) {
6333                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6334                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6335                      if(appData.checkMates) {
6336                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6337                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6339                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6340                          return;
6341                      }
6342                   } else
6343                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6344                   {    /* bare King */
6345                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6346                         if(appData.checkMates) {
6347                             /* but only adjudicate if adjudication enabled */
6348                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6349                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6351                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6352                             return;
6353                         }
6354                   }
6355                 } else bare = 1;
6356
6357
6358             // don't wait for engine to announce game end if we can judge ourselves
6359             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6360               case MT_CHECK:
6361                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6362                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6363                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6364                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6365                             checkCnt++;
6366                         if(checkCnt >= 2) {
6367                             reason = "Xboard adjudication: 3rd check";
6368                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6369                             break;
6370                         }
6371                     }
6372                 }
6373               case MT_NONE:
6374               default:
6375                 break;
6376               case MT_STALEMATE:
6377               case MT_STAINMATE:
6378                 reason = "Xboard adjudication: Stalemate";
6379                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6380                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6381                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6382                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6383                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6384                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6385                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6386                                                                         EP_CHECKMATE : EP_WINS);
6387                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6388                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6389                 }
6390                 break;
6391               case MT_CHECKMATE:
6392                 reason = "Xboard adjudication: Checkmate";
6393                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6394                 break;
6395             }
6396
6397                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6398                     case EP_STALEMATE:
6399                         result = GameIsDrawn; break;
6400                     case EP_CHECKMATE:
6401                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6402                     case EP_WINS:
6403                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6404                     default:
6405                         result = (ChessMove) 0;
6406                 }
6407                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6408                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6409                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6410                     GameEnds( result, reason, GE_XBOARD );
6411                     return;
6412                 }
6413
6414                 /* Next absolutely insufficient mating material. */
6415                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6416                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6417                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6418                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6419                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6420
6421                      /* always flag draws, for judging claims */
6422                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6423
6424                      if(appData.materialDraws) {
6425                          /* but only adjudicate them if adjudication enabled */
6426                          SendToProgram("force\n", cps->other); // suppress reply
6427                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6428                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6429                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6430                          return;
6431                      }
6432                 }
6433
6434                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6435                 if(NrPieces == 4 && 
6436                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6437                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6438                    || NrWN==2 || NrBN==2     /* KNNK */
6439                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6440                   ) ) {
6441                      if(--moveCount < 0 && appData.trivialDraws)
6442                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6443                           SendToProgram("force\n", cps->other); // suppress reply
6444                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6445                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6446                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6447                           return;
6448                      }
6449                 } else moveCount = 6;
6450             }
6451           }
6452           
6453           if (appData.debugMode) { int i;
6454             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6455                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6456                     appData.drawRepeats);
6457             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6458               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6459             
6460           }
6461
6462                 /* Check for rep-draws */
6463                 count = 0;
6464                 for(k = forwardMostMove-2;
6465                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6466                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6467                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6468                     k-=2)
6469                 {   int rights=0;
6470                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6471                         /* compare castling rights */
6472                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6473                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6474                                 rights++; /* King lost rights, while rook still had them */
6475                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6476                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6477                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6478                                    rights++; /* but at least one rook lost them */
6479                         }
6480                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6481                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6482                                 rights++; 
6483                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6484                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6485                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6486                                    rights++;
6487                         }
6488                         if( rights == 0 && ++count > appData.drawRepeats-2
6489                             && appData.drawRepeats > 1) {
6490                              /* adjudicate after user-specified nr of repeats */
6491                              SendToProgram("force\n", cps->other); // suppress reply
6492                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6493                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6494                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6495                                 // [HGM] xiangqi: check for forbidden perpetuals
6496                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6497                                 for(m=forwardMostMove; m>k; m-=2) {
6498                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6499                                         ourPerpetual = 0; // the current mover did not always check
6500                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6501                                         hisPerpetual = 0; // the opponent did not always check
6502                                 }
6503                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6504                                                                         ourPerpetual, hisPerpetual);
6505                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6506                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6507                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6508                                     return;
6509                                 }
6510                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6511                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6512                                 // Now check for perpetual chases
6513                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6514                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6515                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6516                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6517                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6518                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6519                                         return;
6520                                     }
6521                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6522                                         break; // Abort repetition-checking loop.
6523                                 }
6524                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6525                              }
6526                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6527                              return;
6528                         }
6529                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6530                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6531                     }
6532                 }
6533
6534                 /* Now we test for 50-move draws. Determine ply count */
6535                 count = forwardMostMove;
6536                 /* look for last irreversble move */
6537                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6538                     count--;
6539                 /* if we hit starting position, add initial plies */
6540                 if( count == backwardMostMove )
6541                     count -= initialRulePlies;
6542                 count = forwardMostMove - count; 
6543                 if( count >= 100)
6544                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6545                          /* this is used to judge if draw claims are legal */
6546                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6547                          SendToProgram("force\n", cps->other); // suppress reply
6548                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6549                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6550                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6551                          return;
6552                 }
6553
6554                 /* if draw offer is pending, treat it as a draw claim
6555                  * when draw condition present, to allow engines a way to
6556                  * claim draws before making their move to avoid a race
6557                  * condition occurring after their move
6558                  */
6559                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6560                          char *p = NULL;
6561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6562                              p = "Draw claim: 50-move rule";
6563                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6564                              p = "Draw claim: 3-fold repetition";
6565                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6566                              p = "Draw claim: insufficient mating material";
6567                          if( p != NULL ) {
6568                              SendToProgram("force\n", cps->other); // suppress reply
6569                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6570                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6571                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6572                              return;
6573                          }
6574                 }
6575
6576
6577                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6578                     SendToProgram("force\n", cps->other); // suppress reply
6579                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6580                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6581
6582                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6583
6584                     return;
6585                 }
6586         }
6587
6588         bookHit = NULL;
6589         if (gameMode == TwoMachinesPlay) {
6590             /* [HGM] relaying draw offers moved to after reception of move */
6591             /* and interpreting offer as claim if it brings draw condition */
6592             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6593                 SendToProgram("draw\n", cps->other);
6594             }
6595             if (cps->other->sendTime) {
6596                 SendTimeRemaining(cps->other,
6597                                   cps->other->twoMachinesColor[0] == 'w');
6598             }
6599             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6600             if (firstMove && !bookHit) {
6601                 firstMove = FALSE;
6602                 if (cps->other->useColors) {
6603                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6604                 }
6605                 SendToProgram("go\n", cps->other);
6606             }
6607             cps->other->maybeThinking = TRUE;
6608         }
6609
6610         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6611         
6612         if (!pausing && appData.ringBellAfterMoves) {
6613             RingBell();
6614         }
6615
6616         /* 
6617          * Reenable menu items that were disabled while
6618          * machine was thinking
6619          */
6620         if (gameMode != TwoMachinesPlay)
6621             SetUserThinkingEnables();
6622
6623         // [HGM] book: after book hit opponent has received move and is now in force mode
6624         // force the book reply into it, and then fake that it outputted this move by jumping
6625         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6626         if(bookHit) {
6627                 static char bookMove[MSG_SIZ]; // a bit generous?
6628
6629                 strcpy(bookMove, "move ");
6630                 strcat(bookMove, bookHit);
6631                 message = bookMove;
6632                 cps = cps->other;
6633                 programStats.nodes = programStats.depth = programStats.time = 
6634                 programStats.score = programStats.got_only_move = 0;
6635                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6636
6637                 if(cps->lastPing != cps->lastPong) {
6638                     savedMessage = message; // args for deferred call
6639                     savedState = cps;
6640                     ScheduleDelayedEvent(DeferredBookMove, 10);
6641                     return;
6642                 }
6643                 goto FakeBookMove;
6644         }
6645
6646         return;
6647     }
6648
6649     /* Set special modes for chess engines.  Later something general
6650      *  could be added here; for now there is just one kludge feature,
6651      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6652      *  when "xboard" is given as an interactive command.
6653      */
6654     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6655         cps->useSigint = FALSE;
6656         cps->useSigterm = FALSE;
6657     }
6658     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6659       ParseFeatures(message+8, cps);
6660       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6661     }
6662
6663     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6664      * want this, I was asked to put it in, and obliged.
6665      */
6666     if (!strncmp(message, "setboard ", 9)) {
6667         Board initial_position;
6668
6669         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6670
6671         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6672             DisplayError(_("Bad FEN received from engine"), 0);
6673             return ;
6674         } else {
6675            Reset(TRUE, FALSE);
6676            CopyBoard(boards[0], initial_position);
6677            initialRulePlies = FENrulePlies;
6678            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6679            else gameMode = MachinePlaysBlack;                 
6680            DrawPosition(FALSE, boards[currentMove]);
6681         }
6682         return;
6683     }
6684
6685     /*
6686      * Look for communication commands
6687      */
6688     if (!strncmp(message, "telluser ", 9)) {
6689         DisplayNote(message + 9);
6690         return;
6691     }
6692     if (!strncmp(message, "tellusererror ", 14)) {
6693         cps->userError = 1;
6694         DisplayError(message + 14, 0);
6695         return;
6696     }
6697     if (!strncmp(message, "tellopponent ", 13)) {
6698       if (appData.icsActive) {
6699         if (loggedOn) {
6700           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6701           SendToICS(buf1);
6702         }
6703       } else {
6704         DisplayNote(message + 13);
6705       }
6706       return;
6707     }
6708     if (!strncmp(message, "tellothers ", 11)) {
6709       if (appData.icsActive) {
6710         if (loggedOn) {
6711           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6712           SendToICS(buf1);
6713         }
6714       }
6715       return;
6716     }
6717     if (!strncmp(message, "tellall ", 8)) {
6718       if (appData.icsActive) {
6719         if (loggedOn) {
6720           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6721           SendToICS(buf1);
6722         }
6723       } else {
6724         DisplayNote(message + 8);
6725       }
6726       return;
6727     }
6728     if (strncmp(message, "warning", 7) == 0) {
6729         /* Undocumented feature, use tellusererror in new code */
6730         DisplayError(message, 0);
6731         return;
6732     }
6733     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6734         strcpy(realname, cps->tidy);
6735         strcat(realname, " query");
6736         AskQuestion(realname, buf2, buf1, cps->pr);
6737         return;
6738     }
6739     /* Commands from the engine directly to ICS.  We don't allow these to be 
6740      *  sent until we are logged on. Crafty kibitzes have been known to 
6741      *  interfere with the login process.
6742      */
6743     if (loggedOn) {
6744         if (!strncmp(message, "tellics ", 8)) {
6745             SendToICS(message + 8);
6746             SendToICS("\n");
6747             return;
6748         }
6749         if (!strncmp(message, "tellicsnoalias ", 15)) {
6750             SendToICS(ics_prefix);
6751             SendToICS(message + 15);
6752             SendToICS("\n");
6753             return;
6754         }
6755         /* The following are for backward compatibility only */
6756         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6757             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6758             SendToICS(ics_prefix);
6759             SendToICS(message);
6760             SendToICS("\n");
6761             return;
6762         }
6763     }
6764     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6765         return;
6766     }
6767     /*
6768      * If the move is illegal, cancel it and redraw the board.
6769      * Also deal with other error cases.  Matching is rather loose
6770      * here to accommodate engines written before the spec.
6771      */
6772     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6773         strncmp(message, "Error", 5) == 0) {
6774         if (StrStr(message, "name") || 
6775             StrStr(message, "rating") || StrStr(message, "?") ||
6776             StrStr(message, "result") || StrStr(message, "board") ||
6777             StrStr(message, "bk") || StrStr(message, "computer") ||
6778             StrStr(message, "variant") || StrStr(message, "hint") ||
6779             StrStr(message, "random") || StrStr(message, "depth") ||
6780             StrStr(message, "accepted")) {
6781             return;
6782         }
6783         if (StrStr(message, "protover")) {
6784           /* Program is responding to input, so it's apparently done
6785              initializing, and this error message indicates it is
6786              protocol version 1.  So we don't need to wait any longer
6787              for it to initialize and send feature commands. */
6788           FeatureDone(cps, 1);
6789           cps->protocolVersion = 1;
6790           return;
6791         }
6792         cps->maybeThinking = FALSE;
6793
6794         if (StrStr(message, "draw")) {
6795             /* Program doesn't have "draw" command */
6796             cps->sendDrawOffers = 0;
6797             return;
6798         }
6799         if (cps->sendTime != 1 &&
6800             (StrStr(message, "time") || StrStr(message, "otim"))) {
6801           /* Program apparently doesn't have "time" or "otim" command */
6802           cps->sendTime = 0;
6803           return;
6804         }
6805         if (StrStr(message, "analyze")) {
6806             cps->analysisSupport = FALSE;
6807             cps->analyzing = FALSE;
6808             Reset(FALSE, TRUE);
6809             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6810             DisplayError(buf2, 0);
6811             return;
6812         }
6813         if (StrStr(message, "(no matching move)st")) {
6814           /* Special kludge for GNU Chess 4 only */
6815           cps->stKludge = TRUE;
6816           SendTimeControl(cps, movesPerSession, timeControl,
6817                           timeIncrement, appData.searchDepth,
6818                           searchTime);
6819           return;
6820         }
6821         if (StrStr(message, "(no matching move)sd")) {
6822           /* Special kludge for GNU Chess 4 only */
6823           cps->sdKludge = TRUE;
6824           SendTimeControl(cps, movesPerSession, timeControl,
6825                           timeIncrement, appData.searchDepth,
6826                           searchTime);
6827           return;
6828         }
6829         if (!StrStr(message, "llegal")) {
6830             return;
6831         }
6832         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6833             gameMode == IcsIdle) return;
6834         if (forwardMostMove <= backwardMostMove) return;
6835         if (pausing) PauseEvent();
6836       if(appData.forceIllegal) {
6837             // [HGM] illegal: machine refused move; force position after move into it
6838           SendToProgram("force\n", cps);
6839           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6840                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6841                 // when black is to move, while there might be nothing on a2 or black
6842                 // might already have the move. So send the board as if white has the move.
6843                 // But first we must change the stm of the engine, as it refused the last move
6844                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6845                 if(WhiteOnMove(forwardMostMove)) {
6846                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6847                     SendBoard(cps, forwardMostMove); // kludgeless board
6848                 } else {
6849                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6850                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6851                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6852                 }
6853           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6854             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6855                  gameMode == TwoMachinesPlay)
6856               SendToProgram("go\n", cps);
6857             return;
6858       } else
6859         if (gameMode == PlayFromGameFile) {
6860             /* Stop reading this game file */
6861             gameMode = EditGame;
6862             ModeHighlight();
6863         }
6864         currentMove = --forwardMostMove;
6865         DisplayMove(currentMove-1); /* before DisplayMoveError */
6866         SwitchClocks();
6867         DisplayBothClocks();
6868         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6869                 parseList[currentMove], cps->which);
6870         DisplayMoveError(buf1);
6871         DrawPosition(FALSE, boards[currentMove]);
6872
6873         /* [HGM] illegal-move claim should forfeit game when Xboard */
6874         /* only passes fully legal moves                            */
6875         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6876             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6877                                 "False illegal-move claim", GE_XBOARD );
6878         }
6879         return;
6880     }
6881     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6882         /* Program has a broken "time" command that
6883            outputs a string not ending in newline.
6884            Don't use it. */
6885         cps->sendTime = 0;
6886     }
6887     
6888     /*
6889      * If chess program startup fails, exit with an error message.
6890      * Attempts to recover here are futile.
6891      */
6892     if ((StrStr(message, "unknown host") != NULL)
6893         || (StrStr(message, "No remote directory") != NULL)
6894         || (StrStr(message, "not found") != NULL)
6895         || (StrStr(message, "No such file") != NULL)
6896         || (StrStr(message, "can't alloc") != NULL)
6897         || (StrStr(message, "Permission denied") != NULL)) {
6898
6899         cps->maybeThinking = FALSE;
6900         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6901                 cps->which, cps->program, cps->host, message);
6902         RemoveInputSource(cps->isr);
6903         DisplayFatalError(buf1, 0, 1);
6904         return;
6905     }
6906     
6907     /* 
6908      * Look for hint output
6909      */
6910     if (sscanf(message, "Hint: %s", buf1) == 1) {
6911         if (cps == &first && hintRequested) {
6912             hintRequested = FALSE;
6913             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6914                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6915                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6916                                     PosFlags(forwardMostMove),
6917                                     fromY, fromX, toY, toX, promoChar, buf1);
6918                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6919                 DisplayInformation(buf2);
6920             } else {
6921                 /* Hint move could not be parsed!? */
6922               snprintf(buf2, sizeof(buf2),
6923                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6924                         buf1, cps->which);
6925                 DisplayError(buf2, 0);
6926             }
6927         } else {
6928             strcpy(lastHint, buf1);
6929         }
6930         return;
6931     }
6932
6933     /*
6934      * Ignore other messages if game is not in progress
6935      */
6936     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6937         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6938
6939     /*
6940      * look for win, lose, draw, or draw offer
6941      */
6942     if (strncmp(message, "1-0", 3) == 0) {
6943         char *p, *q, *r = "";
6944         p = strchr(message, '{');
6945         if (p) {
6946             q = strchr(p, '}');
6947             if (q) {
6948                 *q = NULLCHAR;
6949                 r = p + 1;
6950             }
6951         }
6952         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6953         return;
6954     } else if (strncmp(message, "0-1", 3) == 0) {
6955         char *p, *q, *r = "";
6956         p = strchr(message, '{');
6957         if (p) {
6958             q = strchr(p, '}');
6959             if (q) {
6960                 *q = NULLCHAR;
6961                 r = p + 1;
6962             }
6963         }
6964         /* Kludge for Arasan 4.1 bug */
6965         if (strcmp(r, "Black resigns") == 0) {
6966             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6967             return;
6968         }
6969         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6970         return;
6971     } else if (strncmp(message, "1/2", 3) == 0) {
6972         char *p, *q, *r = "";
6973         p = strchr(message, '{');
6974         if (p) {
6975             q = strchr(p, '}');
6976             if (q) {
6977                 *q = NULLCHAR;
6978                 r = p + 1;
6979             }
6980         }
6981             
6982         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6983         return;
6984
6985     } else if (strncmp(message, "White resign", 12) == 0) {
6986         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6987         return;
6988     } else if (strncmp(message, "Black resign", 12) == 0) {
6989         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6990         return;
6991     } else if (strncmp(message, "White matches", 13) == 0 ||
6992                strncmp(message, "Black matches", 13) == 0   ) {
6993         /* [HGM] ignore GNUShogi noises */
6994         return;
6995     } else if (strncmp(message, "White", 5) == 0 &&
6996                message[5] != '(' &&
6997                StrStr(message, "Black") == NULL) {
6998         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6999         return;
7000     } else if (strncmp(message, "Black", 5) == 0 &&
7001                message[5] != '(') {
7002         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7003         return;
7004     } else if (strcmp(message, "resign") == 0 ||
7005                strcmp(message, "computer resigns") == 0) {
7006         switch (gameMode) {
7007           case MachinePlaysBlack:
7008           case IcsPlayingBlack:
7009             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7010             break;
7011           case MachinePlaysWhite:
7012           case IcsPlayingWhite:
7013             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7014             break;
7015           case TwoMachinesPlay:
7016             if (cps->twoMachinesColor[0] == 'w')
7017               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7018             else
7019               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7020             break;
7021           default:
7022             /* can't happen */
7023             break;
7024         }
7025         return;
7026     } else if (strncmp(message, "opponent mates", 14) == 0) {
7027         switch (gameMode) {
7028           case MachinePlaysBlack:
7029           case IcsPlayingBlack:
7030             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7031             break;
7032           case MachinePlaysWhite:
7033           case IcsPlayingWhite:
7034             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7035             break;
7036           case TwoMachinesPlay:
7037             if (cps->twoMachinesColor[0] == 'w')
7038               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7039             else
7040               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7041             break;
7042           default:
7043             /* can't happen */
7044             break;
7045         }
7046         return;
7047     } else if (strncmp(message, "computer mates", 14) == 0) {
7048         switch (gameMode) {
7049           case MachinePlaysBlack:
7050           case IcsPlayingBlack:
7051             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7052             break;
7053           case MachinePlaysWhite:
7054           case IcsPlayingWhite:
7055             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7056             break;
7057           case TwoMachinesPlay:
7058             if (cps->twoMachinesColor[0] == 'w')
7059               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7060             else
7061               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7062             break;
7063           default:
7064             /* can't happen */
7065             break;
7066         }
7067         return;
7068     } else if (strncmp(message, "checkmate", 9) == 0) {
7069         if (WhiteOnMove(forwardMostMove)) {
7070             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7071         } else {
7072             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7073         }
7074         return;
7075     } else if (strstr(message, "Draw") != NULL ||
7076                strstr(message, "game is a draw") != NULL) {
7077         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7078         return;
7079     } else if (strstr(message, "offer") != NULL &&
7080                strstr(message, "draw") != NULL) {
7081 #if ZIPPY
7082         if (appData.zippyPlay && first.initDone) {
7083             /* Relay offer to ICS */
7084             SendToICS(ics_prefix);
7085             SendToICS("draw\n");
7086         }
7087 #endif
7088         cps->offeredDraw = 2; /* valid until this engine moves twice */
7089         if (gameMode == TwoMachinesPlay) {
7090             if (cps->other->offeredDraw) {
7091                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7092             /* [HGM] in two-machine mode we delay relaying draw offer      */
7093             /* until after we also have move, to see if it is really claim */
7094             }
7095         } else if (gameMode == MachinePlaysWhite ||
7096                    gameMode == MachinePlaysBlack) {
7097           if (userOfferedDraw) {
7098             DisplayInformation(_("Machine accepts your draw offer"));
7099             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7100           } else {
7101             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7102           }
7103         }
7104     }
7105
7106     
7107     /*
7108      * Look for thinking output
7109      */
7110     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7111           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7112                                 ) {
7113         int plylev, mvleft, mvtot, curscore, time;
7114         char mvname[MOVE_LEN];
7115         u64 nodes; // [DM]
7116         char plyext;
7117         int ignore = FALSE;
7118         int prefixHint = FALSE;
7119         mvname[0] = NULLCHAR;
7120
7121         switch (gameMode) {
7122           case MachinePlaysBlack:
7123           case IcsPlayingBlack:
7124             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7125             break;
7126           case MachinePlaysWhite:
7127           case IcsPlayingWhite:
7128             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7129             break;
7130           case AnalyzeMode:
7131           case AnalyzeFile:
7132             break;
7133           case IcsObserving: /* [DM] icsEngineAnalyze */
7134             if (!appData.icsEngineAnalyze) ignore = TRUE;
7135             break;
7136           case TwoMachinesPlay:
7137             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7138                 ignore = TRUE;
7139             }
7140             break;
7141           default:
7142             ignore = TRUE;
7143             break;
7144         }
7145
7146         if (!ignore) {
7147             buf1[0] = NULLCHAR;
7148             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7149                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7150
7151                 if (plyext != ' ' && plyext != '\t') {
7152                     time *= 100;
7153                 }
7154
7155                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7156                 if( cps->scoreIsAbsolute && 
7157                     ( gameMode == MachinePlaysBlack ||
7158                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7159                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7160                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7161                      !WhiteOnMove(currentMove)
7162                     ) )
7163                 {
7164                     curscore = -curscore;
7165                 }
7166
7167
7168                 programStats.depth = plylev;
7169                 programStats.nodes = nodes;
7170                 programStats.time = time;
7171                 programStats.score = curscore;
7172                 programStats.got_only_move = 0;
7173
7174                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7175                         int ticklen;
7176
7177                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7178                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7179                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7180                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7181                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7182                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7183                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7184                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7185                 }
7186
7187                 /* Buffer overflow protection */
7188                 if (buf1[0] != NULLCHAR) {
7189                     if (strlen(buf1) >= sizeof(programStats.movelist)
7190                         && appData.debugMode) {
7191                         fprintf(debugFP,
7192                                 "PV is too long; using the first %u bytes.\n",
7193                                 (unsigned) sizeof(programStats.movelist) - 1);
7194                     }
7195
7196                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7197                 } else {
7198                     sprintf(programStats.movelist, " no PV\n");
7199                 }
7200
7201                 if (programStats.seen_stat) {
7202                     programStats.ok_to_send = 1;
7203                 }
7204
7205                 if (strchr(programStats.movelist, '(') != NULL) {
7206                     programStats.line_is_book = 1;
7207                     programStats.nr_moves = 0;
7208                     programStats.moves_left = 0;
7209                 } else {
7210                     programStats.line_is_book = 0;
7211                 }
7212
7213                 SendProgramStatsToFrontend( cps, &programStats );
7214
7215                 /* 
7216                     [AS] Protect the thinkOutput buffer from overflow... this
7217                     is only useful if buf1 hasn't overflowed first!
7218                 */
7219                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7220                         plylev, 
7221                         (gameMode == TwoMachinesPlay ?
7222                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7223                         ((double) curscore) / 100.0,
7224                         prefixHint ? lastHint : "",
7225                         prefixHint ? " " : "" );
7226
7227                 if( buf1[0] != NULLCHAR ) {
7228                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7229
7230                     if( strlen(buf1) > max_len ) {
7231                         if( appData.debugMode) {
7232                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7233                         }
7234                         buf1[max_len+1] = '\0';
7235                     }
7236
7237                     strcat( thinkOutput, buf1 );
7238                 }
7239
7240                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7241                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7242                     DisplayMove(currentMove - 1);
7243                 }
7244                 return;
7245
7246             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7247                 /* crafty (9.25+) says "(only move) <move>"
7248                  * if there is only 1 legal move
7249                  */
7250                 sscanf(p, "(only move) %s", buf1);
7251                 sprintf(thinkOutput, "%s (only move)", buf1);
7252                 sprintf(programStats.movelist, "%s (only move)", buf1);
7253                 programStats.depth = 1;
7254                 programStats.nr_moves = 1;
7255                 programStats.moves_left = 1;
7256                 programStats.nodes = 1;
7257                 programStats.time = 1;
7258                 programStats.got_only_move = 1;
7259
7260                 /* Not really, but we also use this member to
7261                    mean "line isn't going to change" (Crafty
7262                    isn't searching, so stats won't change) */
7263                 programStats.line_is_book = 1;
7264
7265                 SendProgramStatsToFrontend( cps, &programStats );
7266                 
7267                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7268                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7269                     DisplayMove(currentMove - 1);
7270                 }
7271                 return;
7272             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7273                               &time, &nodes, &plylev, &mvleft,
7274                               &mvtot, mvname) >= 5) {
7275                 /* The stat01: line is from Crafty (9.29+) in response
7276                    to the "." command */
7277                 programStats.seen_stat = 1;
7278                 cps->maybeThinking = TRUE;
7279
7280                 if (programStats.got_only_move || !appData.periodicUpdates)
7281                   return;
7282
7283                 programStats.depth = plylev;
7284                 programStats.time = time;
7285                 programStats.nodes = nodes;
7286                 programStats.moves_left = mvleft;
7287                 programStats.nr_moves = mvtot;
7288                 strcpy(programStats.move_name, mvname);
7289                 programStats.ok_to_send = 1;
7290                 programStats.movelist[0] = '\0';
7291
7292                 SendProgramStatsToFrontend( cps, &programStats );
7293
7294                 return;
7295
7296             } else if (strncmp(message,"++",2) == 0) {
7297                 /* Crafty 9.29+ outputs this */
7298                 programStats.got_fail = 2;
7299                 return;
7300
7301             } else if (strncmp(message,"--",2) == 0) {
7302                 /* Crafty 9.29+ outputs this */
7303                 programStats.got_fail = 1;
7304                 return;
7305
7306             } else if (thinkOutput[0] != NULLCHAR &&
7307                        strncmp(message, "    ", 4) == 0) {
7308                 unsigned message_len;
7309
7310                 p = message;
7311                 while (*p && *p == ' ') p++;
7312
7313                 message_len = strlen( p );
7314
7315                 /* [AS] Avoid buffer overflow */
7316                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7317                     strcat(thinkOutput, " ");
7318                     strcat(thinkOutput, p);
7319                 }
7320
7321                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7322                     strcat(programStats.movelist, " ");
7323                     strcat(programStats.movelist, p);
7324                 }
7325
7326                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7327                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7328                     DisplayMove(currentMove - 1);
7329                 }
7330                 return;
7331             }
7332         }
7333         else {
7334             buf1[0] = NULLCHAR;
7335
7336             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7337                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7338             {
7339                 ChessProgramStats cpstats;
7340
7341                 if (plyext != ' ' && plyext != '\t') {
7342                     time *= 100;
7343                 }
7344
7345                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7346                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7347                     curscore = -curscore;
7348                 }
7349
7350                 cpstats.depth = plylev;
7351                 cpstats.nodes = nodes;
7352                 cpstats.time = time;
7353                 cpstats.score = curscore;
7354                 cpstats.got_only_move = 0;
7355                 cpstats.movelist[0] = '\0';
7356
7357                 if (buf1[0] != NULLCHAR) {
7358                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7359                 }
7360
7361                 cpstats.ok_to_send = 0;
7362                 cpstats.line_is_book = 0;
7363                 cpstats.nr_moves = 0;
7364                 cpstats.moves_left = 0;
7365
7366                 SendProgramStatsToFrontend( cps, &cpstats );
7367             }
7368         }
7369     }
7370 }
7371
7372
7373 /* Parse a game score from the character string "game", and
7374    record it as the history of the current game.  The game
7375    score is NOT assumed to start from the standard position. 
7376    The display is not updated in any way.
7377    */
7378 void
7379 ParseGameHistory(game)
7380      char *game;
7381 {
7382     ChessMove moveType;
7383     int fromX, fromY, toX, toY, boardIndex;
7384     char promoChar;
7385     char *p, *q;
7386     char buf[MSG_SIZ];
7387
7388     if (appData.debugMode)
7389       fprintf(debugFP, "Parsing game history: %s\n", game);
7390
7391     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7392     gameInfo.site = StrSave(appData.icsHost);
7393     gameInfo.date = PGNDate();
7394     gameInfo.round = StrSave("-");
7395
7396     /* Parse out names of players */
7397     while (*game == ' ') game++;
7398     p = buf;
7399     while (*game != ' ') *p++ = *game++;
7400     *p = NULLCHAR;
7401     gameInfo.white = StrSave(buf);
7402     while (*game == ' ') game++;
7403     p = buf;
7404     while (*game != ' ' && *game != '\n') *p++ = *game++;
7405     *p = NULLCHAR;
7406     gameInfo.black = StrSave(buf);
7407
7408     /* Parse moves */
7409     boardIndex = blackPlaysFirst ? 1 : 0;
7410     yynewstr(game);
7411     for (;;) {
7412         yyboardindex = boardIndex;
7413         moveType = (ChessMove) yylex();
7414         switch (moveType) {
7415           case IllegalMove:             /* maybe suicide chess, etc. */
7416   if (appData.debugMode) {
7417     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7418     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7419     setbuf(debugFP, NULL);
7420   }
7421           case WhitePromotionChancellor:
7422           case BlackPromotionChancellor:
7423           case WhitePromotionArchbishop:
7424           case BlackPromotionArchbishop:
7425           case WhitePromotionQueen:
7426           case BlackPromotionQueen:
7427           case WhitePromotionRook:
7428           case BlackPromotionRook:
7429           case WhitePromotionBishop:
7430           case BlackPromotionBishop:
7431           case WhitePromotionKnight:
7432           case BlackPromotionKnight:
7433           case WhitePromotionKing:
7434           case BlackPromotionKing:
7435           case NormalMove:
7436           case WhiteCapturesEnPassant:
7437           case BlackCapturesEnPassant:
7438           case WhiteKingSideCastle:
7439           case WhiteQueenSideCastle:
7440           case BlackKingSideCastle:
7441           case BlackQueenSideCastle:
7442           case WhiteKingSideCastleWild:
7443           case WhiteQueenSideCastleWild:
7444           case BlackKingSideCastleWild:
7445           case BlackQueenSideCastleWild:
7446           /* PUSH Fabien */
7447           case WhiteHSideCastleFR:
7448           case WhiteASideCastleFR:
7449           case BlackHSideCastleFR:
7450           case BlackASideCastleFR:
7451           /* POP Fabien */
7452             fromX = currentMoveString[0] - AAA;
7453             fromY = currentMoveString[1] - ONE;
7454             toX = currentMoveString[2] - AAA;
7455             toY = currentMoveString[3] - ONE;
7456             promoChar = currentMoveString[4];
7457             break;
7458           case WhiteDrop:
7459           case BlackDrop:
7460             fromX = moveType == WhiteDrop ?
7461               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7462             (int) CharToPiece(ToLower(currentMoveString[0]));
7463             fromY = DROP_RANK;
7464             toX = currentMoveString[2] - AAA;
7465             toY = currentMoveString[3] - ONE;
7466             promoChar = NULLCHAR;
7467             break;
7468           case AmbiguousMove:
7469             /* bug? */
7470             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7471   if (appData.debugMode) {
7472     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7473     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7474     setbuf(debugFP, NULL);
7475   }
7476             DisplayError(buf, 0);
7477             return;
7478           case ImpossibleMove:
7479             /* bug? */
7480             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7481   if (appData.debugMode) {
7482     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7483     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7484     setbuf(debugFP, NULL);
7485   }
7486             DisplayError(buf, 0);
7487             return;
7488           case (ChessMove) 0:   /* end of file */
7489             if (boardIndex < backwardMostMove) {
7490                 /* Oops, gap.  How did that happen? */
7491                 DisplayError(_("Gap in move list"), 0);
7492                 return;
7493             }
7494             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7495             if (boardIndex > forwardMostMove) {
7496                 forwardMostMove = boardIndex;
7497             }
7498             return;
7499           case ElapsedTime:
7500             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7501                 strcat(parseList[boardIndex-1], " ");
7502                 strcat(parseList[boardIndex-1], yy_text);
7503             }
7504             continue;
7505           case Comment:
7506           case PGNTag:
7507           case NAG:
7508           default:
7509             /* ignore */
7510             continue;
7511           case WhiteWins:
7512           case BlackWins:
7513           case GameIsDrawn:
7514           case GameUnfinished:
7515             if (gameMode == IcsExamining) {
7516                 if (boardIndex < backwardMostMove) {
7517                     /* Oops, gap.  How did that happen? */
7518                     return;
7519                 }
7520                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7521                 return;
7522             }
7523             gameInfo.result = moveType;
7524             p = strchr(yy_text, '{');
7525             if (p == NULL) p = strchr(yy_text, '(');
7526             if (p == NULL) {
7527                 p = yy_text;
7528                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7529             } else {
7530                 q = strchr(p, *p == '{' ? '}' : ')');
7531                 if (q != NULL) *q = NULLCHAR;
7532                 p++;
7533             }
7534             gameInfo.resultDetails = StrSave(p);
7535             continue;
7536         }
7537         if (boardIndex >= forwardMostMove &&
7538             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7539             backwardMostMove = blackPlaysFirst ? 1 : 0;
7540             return;
7541         }
7542         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7543                                  fromY, fromX, toY, toX, promoChar,
7544                                  parseList[boardIndex]);
7545         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7546         /* currentMoveString is set as a side-effect of yylex */
7547         strcpy(moveList[boardIndex], currentMoveString);
7548         strcat(moveList[boardIndex], "\n");
7549         boardIndex++;
7550         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7551         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7552           case MT_NONE:
7553           case MT_STALEMATE:
7554           default:
7555             break;
7556           case MT_CHECK:
7557             if(gameInfo.variant != VariantShogi)
7558                 strcat(parseList[boardIndex - 1], "+");
7559             break;
7560           case MT_CHECKMATE:
7561           case MT_STAINMATE:
7562             strcat(parseList[boardIndex - 1], "#");
7563             break;
7564         }
7565     }
7566 }
7567
7568
7569 /* Apply a move to the given board  */
7570 void
7571 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7572      int fromX, fromY, toX, toY;
7573      int promoChar;
7574      Board board;
7575 {
7576   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7577   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7578
7579     /* [HGM] compute & store e.p. status and castling rights for new position */
7580     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7581     { int i;
7582
7583       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7584       oldEP = (signed char)board[EP_STATUS];
7585       board[EP_STATUS] = EP_NONE;
7586
7587       if( board[toY][toX] != EmptySquare ) 
7588            board[EP_STATUS] = EP_CAPTURE;  
7589
7590       if( board[fromY][fromX] == WhitePawn ) {
7591            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7592                board[EP_STATUS] = EP_PAWN_MOVE;
7593            if( toY-fromY==2) {
7594                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7595                         gameInfo.variant != VariantBerolina || toX < fromX)
7596                       board[EP_STATUS] = toX | berolina;
7597                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7598                         gameInfo.variant != VariantBerolina || toX > fromX) 
7599                       board[EP_STATUS] = toX;
7600            }
7601       } else 
7602       if( board[fromY][fromX] == BlackPawn ) {
7603            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7604                board[EP_STATUS] = EP_PAWN_MOVE; 
7605            if( toY-fromY== -2) {
7606                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7607                         gameInfo.variant != VariantBerolina || toX < fromX)
7608                       board[EP_STATUS] = toX | berolina;
7609                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7610                         gameInfo.variant != VariantBerolina || toX > fromX) 
7611                       board[EP_STATUS] = toX;
7612            }
7613        }
7614
7615        for(i=0; i<nrCastlingRights; i++) {
7616            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7617               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7618              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7619        }
7620
7621     }
7622
7623   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7624   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7625        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7626          
7627   if (fromX == toX && fromY == toY) return;
7628
7629   if (fromY == DROP_RANK) {
7630         /* must be first */
7631         piece = board[toY][toX] = (ChessSquare) fromX;
7632   } else {
7633      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7634      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7635      if(gameInfo.variant == VariantKnightmate)
7636          king += (int) WhiteUnicorn - (int) WhiteKing;
7637
7638     /* Code added by Tord: */
7639     /* FRC castling assumed when king captures friendly rook. */
7640     if (board[fromY][fromX] == WhiteKing &&
7641              board[toY][toX] == WhiteRook) {
7642       board[fromY][fromX] = EmptySquare;
7643       board[toY][toX] = EmptySquare;
7644       if(toX > fromX) {
7645         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7646       } else {
7647         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7648       }
7649     } else if (board[fromY][fromX] == BlackKing &&
7650                board[toY][toX] == BlackRook) {
7651       board[fromY][fromX] = EmptySquare;
7652       board[toY][toX] = EmptySquare;
7653       if(toX > fromX) {
7654         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7655       } else {
7656         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7657       }
7658     /* End of code added by Tord */
7659
7660     } else if (board[fromY][fromX] == king
7661         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7662         && toY == fromY && toX > fromX+1) {
7663         board[fromY][fromX] = EmptySquare;
7664         board[toY][toX] = king;
7665         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7666         board[fromY][BOARD_RGHT-1] = EmptySquare;
7667     } else if (board[fromY][fromX] == king
7668         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7669                && toY == fromY && toX < fromX-1) {
7670         board[fromY][fromX] = EmptySquare;
7671         board[toY][toX] = king;
7672         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7673         board[fromY][BOARD_LEFT] = EmptySquare;
7674     } else if (board[fromY][fromX] == WhitePawn
7675                && toY >= BOARD_HEIGHT-promoRank
7676                && gameInfo.variant != VariantXiangqi
7677                ) {
7678         /* white pawn promotion */
7679         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7680         if (board[toY][toX] == EmptySquare) {
7681             board[toY][toX] = WhiteQueen;
7682         }
7683         if(gameInfo.variant==VariantBughouse ||
7684            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7685             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7686         board[fromY][fromX] = EmptySquare;
7687     } else if ((fromY == BOARD_HEIGHT-4)
7688                && (toX != fromX)
7689                && gameInfo.variant != VariantXiangqi
7690                && gameInfo.variant != VariantBerolina
7691                && (board[fromY][fromX] == WhitePawn)
7692                && (board[toY][toX] == EmptySquare)) {
7693         board[fromY][fromX] = EmptySquare;
7694         board[toY][toX] = WhitePawn;
7695         captured = board[toY - 1][toX];
7696         board[toY - 1][toX] = EmptySquare;
7697     } else if ((fromY == BOARD_HEIGHT-4)
7698                && (toX == fromX)
7699                && gameInfo.variant == VariantBerolina
7700                && (board[fromY][fromX] == WhitePawn)
7701                && (board[toY][toX] == EmptySquare)) {
7702         board[fromY][fromX] = EmptySquare;
7703         board[toY][toX] = WhitePawn;
7704         if(oldEP & EP_BEROLIN_A) {
7705                 captured = board[fromY][fromX-1];
7706                 board[fromY][fromX-1] = EmptySquare;
7707         }else{  captured = board[fromY][fromX+1];
7708                 board[fromY][fromX+1] = EmptySquare;
7709         }
7710     } else if (board[fromY][fromX] == king
7711         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7712                && toY == fromY && toX > fromX+1) {
7713         board[fromY][fromX] = EmptySquare;
7714         board[toY][toX] = king;
7715         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7716         board[fromY][BOARD_RGHT-1] = EmptySquare;
7717     } else if (board[fromY][fromX] == king
7718         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7719                && toY == fromY && toX < fromX-1) {
7720         board[fromY][fromX] = EmptySquare;
7721         board[toY][toX] = king;
7722         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7723         board[fromY][BOARD_LEFT] = EmptySquare;
7724     } else if (fromY == 7 && fromX == 3
7725                && board[fromY][fromX] == BlackKing
7726                && toY == 7 && toX == 5) {
7727         board[fromY][fromX] = EmptySquare;
7728         board[toY][toX] = BlackKing;
7729         board[fromY][7] = EmptySquare;
7730         board[toY][4] = BlackRook;
7731     } else if (fromY == 7 && fromX == 3
7732                && board[fromY][fromX] == BlackKing
7733                && toY == 7 && toX == 1) {
7734         board[fromY][fromX] = EmptySquare;
7735         board[toY][toX] = BlackKing;
7736         board[fromY][0] = EmptySquare;
7737         board[toY][2] = BlackRook;
7738     } else if (board[fromY][fromX] == BlackPawn
7739                && toY < promoRank
7740                && gameInfo.variant != VariantXiangqi
7741                ) {
7742         /* black pawn promotion */
7743         board[toY][toX] = CharToPiece(ToLower(promoChar));
7744         if (board[toY][toX] == EmptySquare) {
7745             board[toY][toX] = BlackQueen;
7746         }
7747         if(gameInfo.variant==VariantBughouse ||
7748            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7749             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7750         board[fromY][fromX] = EmptySquare;
7751     } else if ((fromY == 3)
7752                && (toX != fromX)
7753                && gameInfo.variant != VariantXiangqi
7754                && gameInfo.variant != VariantBerolina
7755                && (board[fromY][fromX] == BlackPawn)
7756                && (board[toY][toX] == EmptySquare)) {
7757         board[fromY][fromX] = EmptySquare;
7758         board[toY][toX] = BlackPawn;
7759         captured = board[toY + 1][toX];
7760         board[toY + 1][toX] = EmptySquare;
7761     } else if ((fromY == 3)
7762                && (toX == fromX)
7763                && gameInfo.variant == VariantBerolina
7764                && (board[fromY][fromX] == BlackPawn)
7765                && (board[toY][toX] == EmptySquare)) {
7766         board[fromY][fromX] = EmptySquare;
7767         board[toY][toX] = BlackPawn;
7768         if(oldEP & EP_BEROLIN_A) {
7769                 captured = board[fromY][fromX-1];
7770                 board[fromY][fromX-1] = EmptySquare;
7771         }else{  captured = board[fromY][fromX+1];
7772                 board[fromY][fromX+1] = EmptySquare;
7773         }
7774     } else {
7775         board[toY][toX] = board[fromY][fromX];
7776         board[fromY][fromX] = EmptySquare;
7777     }
7778
7779     /* [HGM] now we promote for Shogi, if needed */
7780     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7781         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7782   }
7783
7784     if (gameInfo.holdingsWidth != 0) {
7785
7786       /* !!A lot more code needs to be written to support holdings  */
7787       /* [HGM] OK, so I have written it. Holdings are stored in the */
7788       /* penultimate board files, so they are automaticlly stored   */
7789       /* in the game history.                                       */
7790       if (fromY == DROP_RANK) {
7791         /* Delete from holdings, by decreasing count */
7792         /* and erasing image if necessary            */
7793         p = (int) fromX;
7794         if(p < (int) BlackPawn) { /* white drop */
7795              p -= (int)WhitePawn;
7796                  p = PieceToNumber((ChessSquare)p);
7797              if(p >= gameInfo.holdingsSize) p = 0;
7798              if(--board[p][BOARD_WIDTH-2] <= 0)
7799                   board[p][BOARD_WIDTH-1] = EmptySquare;
7800              if((int)board[p][BOARD_WIDTH-2] < 0)
7801                         board[p][BOARD_WIDTH-2] = 0;
7802         } else {                  /* black drop */
7803              p -= (int)BlackPawn;
7804                  p = PieceToNumber((ChessSquare)p);
7805              if(p >= gameInfo.holdingsSize) p = 0;
7806              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7807                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7808              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7809                         board[BOARD_HEIGHT-1-p][1] = 0;
7810         }
7811       }
7812       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7813           && gameInfo.variant != VariantBughouse        ) {
7814         /* [HGM] holdings: Add to holdings, if holdings exist */
7815         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7816                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7817                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7818         }
7819         p = (int) captured;
7820         if (p >= (int) BlackPawn) {
7821           p -= (int)BlackPawn;
7822           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7823                   /* in Shogi restore piece to its original  first */
7824                   captured = (ChessSquare) (DEMOTED captured);
7825                   p = DEMOTED p;
7826           }
7827           p = PieceToNumber((ChessSquare)p);
7828           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7829           board[p][BOARD_WIDTH-2]++;
7830           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7831         } else {
7832           p -= (int)WhitePawn;
7833           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7834                   captured = (ChessSquare) (DEMOTED captured);
7835                   p = DEMOTED p;
7836           }
7837           p = PieceToNumber((ChessSquare)p);
7838           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7839           board[BOARD_HEIGHT-1-p][1]++;
7840           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7841         }
7842       }
7843     } else if (gameInfo.variant == VariantAtomic) {
7844       if (captured != EmptySquare) {
7845         int y, x;
7846         for (y = toY-1; y <= toY+1; y++) {
7847           for (x = toX-1; x <= toX+1; x++) {
7848             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7849                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7850               board[y][x] = EmptySquare;
7851             }
7852           }
7853         }
7854         board[toY][toX] = EmptySquare;
7855       }
7856     }
7857     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7858         /* [HGM] Shogi promotions */
7859         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7860     }
7861
7862     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7863                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7864         // [HGM] superchess: take promotion piece out of holdings
7865         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7866         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7867             if(!--board[k][BOARD_WIDTH-2])
7868                 board[k][BOARD_WIDTH-1] = EmptySquare;
7869         } else {
7870             if(!--board[BOARD_HEIGHT-1-k][1])
7871                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7872         }
7873     }
7874
7875 }
7876
7877 /* Updates forwardMostMove */
7878 void
7879 MakeMove(fromX, fromY, toX, toY, promoChar)
7880      int fromX, fromY, toX, toY;
7881      int promoChar;
7882 {
7883 //    forwardMostMove++; // [HGM] bare: moved downstream
7884
7885     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7886         int timeLeft; static int lastLoadFlag=0; int king, piece;
7887         piece = boards[forwardMostMove][fromY][fromX];
7888         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7889         if(gameInfo.variant == VariantKnightmate)
7890             king += (int) WhiteUnicorn - (int) WhiteKing;
7891         if(forwardMostMove == 0) {
7892             if(blackPlaysFirst) 
7893                 fprintf(serverMoves, "%s;", second.tidy);
7894             fprintf(serverMoves, "%s;", first.tidy);
7895             if(!blackPlaysFirst) 
7896                 fprintf(serverMoves, "%s;", second.tidy);
7897         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7898         lastLoadFlag = loadFlag;
7899         // print base move
7900         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7901         // print castling suffix
7902         if( toY == fromY && piece == king ) {
7903             if(toX-fromX > 1)
7904                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7905             if(fromX-toX >1)
7906                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7907         }
7908         // e.p. suffix
7909         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7910              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7911              boards[forwardMostMove][toY][toX] == EmptySquare
7912              && fromX != toX )
7913                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7914         // promotion suffix
7915         if(promoChar != NULLCHAR)
7916                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7917         if(!loadFlag) {
7918             fprintf(serverMoves, "/%d/%d",
7919                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7920             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7921             else                      timeLeft = blackTimeRemaining/1000;
7922             fprintf(serverMoves, "/%d", timeLeft);
7923         }
7924         fflush(serverMoves);
7925     }
7926
7927     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7928       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7929                         0, 1);
7930       return;
7931     }
7932     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7933     if (commentList[forwardMostMove+1] != NULL) {
7934         free(commentList[forwardMostMove+1]);
7935         commentList[forwardMostMove+1] = NULL;
7936     }
7937     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7938     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7939     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7940     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7941     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7942     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7943     gameInfo.result = GameUnfinished;
7944     if (gameInfo.resultDetails != NULL) {
7945         free(gameInfo.resultDetails);
7946         gameInfo.resultDetails = NULL;
7947     }
7948     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7949                               moveList[forwardMostMove - 1]);
7950     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7951                              PosFlags(forwardMostMove - 1),
7952                              fromY, fromX, toY, toX, promoChar,
7953                              parseList[forwardMostMove - 1]);
7954     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7955       case MT_NONE:
7956       case MT_STALEMATE:
7957       default:
7958         break;
7959       case MT_CHECK:
7960         if(gameInfo.variant != VariantShogi)
7961             strcat(parseList[forwardMostMove - 1], "+");
7962         break;
7963       case MT_CHECKMATE:
7964       case MT_STAINMATE:
7965         strcat(parseList[forwardMostMove - 1], "#");
7966         break;
7967     }
7968     if (appData.debugMode) {
7969         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7970     }
7971
7972 }
7973
7974 /* Updates currentMove if not pausing */
7975 void
7976 ShowMove(fromX, fromY, toX, toY)
7977 {
7978     int instant = (gameMode == PlayFromGameFile) ?
7979         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7980     if(appData.noGUI) return;
7981     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7982         if (!instant) {
7983             if (forwardMostMove == currentMove + 1) {
7984                 AnimateMove(boards[forwardMostMove - 1],
7985                             fromX, fromY, toX, toY);
7986             }
7987             if (appData.highlightLastMove) {
7988                 SetHighlights(fromX, fromY, toX, toY);
7989             }
7990         }
7991         currentMove = forwardMostMove;
7992     }
7993
7994     if (instant) return;
7995
7996     DisplayMove(currentMove - 1);
7997     DrawPosition(FALSE, boards[currentMove]);
7998     DisplayBothClocks();
7999     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8000 }
8001
8002 void SendEgtPath(ChessProgramState *cps)
8003 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8004         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8005
8006         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8007
8008         while(*p) {
8009             char c, *q = name+1, *r, *s;
8010
8011             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8012             while(*p && *p != ',') *q++ = *p++;
8013             *q++ = ':'; *q = 0;
8014             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8015                 strcmp(name, ",nalimov:") == 0 ) {
8016                 // take nalimov path from the menu-changeable option first, if it is defined
8017                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8018                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8019             } else
8020             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8021                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8022                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8023                 s = r = StrStr(s, ":") + 1; // beginning of path info
8024                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8025                 c = *r; *r = 0;             // temporarily null-terminate path info
8026                     *--q = 0;               // strip of trailig ':' from name
8027                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8028                 *r = c;
8029                 SendToProgram(buf,cps);     // send egtbpath command for this format
8030             }
8031             if(*p == ',') p++; // read away comma to position for next format name
8032         }
8033 }
8034
8035 void
8036 InitChessProgram(cps, setup)
8037      ChessProgramState *cps;
8038      int setup; /* [HGM] needed to setup FRC opening position */
8039 {
8040     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8041     if (appData.noChessProgram) return;
8042     hintRequested = FALSE;
8043     bookRequested = FALSE;
8044
8045     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8046     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8047     if(cps->memSize) { /* [HGM] memory */
8048         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8049         SendToProgram(buf, cps);
8050     }
8051     SendEgtPath(cps); /* [HGM] EGT */
8052     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8053         sprintf(buf, "cores %d\n", appData.smpCores);
8054         SendToProgram(buf, cps);
8055     }
8056
8057     SendToProgram(cps->initString, cps);
8058     if (gameInfo.variant != VariantNormal &&
8059         gameInfo.variant != VariantLoadable
8060         /* [HGM] also send variant if board size non-standard */
8061         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8062                                             ) {
8063       char *v = VariantName(gameInfo.variant);
8064       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8065         /* [HGM] in protocol 1 we have to assume all variants valid */
8066         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8067         DisplayFatalError(buf, 0, 1);
8068         return;
8069       }
8070
8071       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8072       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8073       if( gameInfo.variant == VariantXiangqi )
8074            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8075       if( gameInfo.variant == VariantShogi )
8076            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8077       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8078            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8079       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8080                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8081            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8082       if( gameInfo.variant == VariantCourier )
8083            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8084       if( gameInfo.variant == VariantSuper )
8085            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8086       if( gameInfo.variant == VariantGreat )
8087            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8088
8089       if(overruled) {
8090            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8091                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8092            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8093            if(StrStr(cps->variants, b) == NULL) { 
8094                // specific sized variant not known, check if general sizing allowed
8095                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8096                    if(StrStr(cps->variants, "boardsize") == NULL) {
8097                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8098                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8099                        DisplayFatalError(buf, 0, 1);
8100                        return;
8101                    }
8102                    /* [HGM] here we really should compare with the maximum supported board size */
8103                }
8104            }
8105       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8106       sprintf(buf, "variant %s\n", b);
8107       SendToProgram(buf, cps);
8108     }
8109     currentlyInitializedVariant = gameInfo.variant;
8110
8111     /* [HGM] send opening position in FRC to first engine */
8112     if(setup) {
8113           SendToProgram("force\n", cps);
8114           SendBoard(cps, 0);
8115           /* engine is now in force mode! Set flag to wake it up after first move. */
8116           setboardSpoiledMachineBlack = 1;
8117     }
8118
8119     if (cps->sendICS) {
8120       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8121       SendToProgram(buf, cps);
8122     }
8123     cps->maybeThinking = FALSE;
8124     cps->offeredDraw = 0;
8125     if (!appData.icsActive) {
8126         SendTimeControl(cps, movesPerSession, timeControl,
8127                         timeIncrement, appData.searchDepth,
8128                         searchTime);
8129     }
8130     if (appData.showThinking 
8131         // [HGM] thinking: four options require thinking output to be sent
8132         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8133                                 ) {
8134         SendToProgram("post\n", cps);
8135     }
8136     SendToProgram("hard\n", cps);
8137     if (!appData.ponderNextMove) {
8138         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8139            it without being sure what state we are in first.  "hard"
8140            is not a toggle, so that one is OK.
8141          */
8142         SendToProgram("easy\n", cps);
8143     }
8144     if (cps->usePing) {
8145       sprintf(buf, "ping %d\n", ++cps->lastPing);
8146       SendToProgram(buf, cps);
8147     }
8148     cps->initDone = TRUE;
8149 }   
8150
8151
8152 void
8153 StartChessProgram(cps)
8154      ChessProgramState *cps;
8155 {
8156     char buf[MSG_SIZ];
8157     int err;
8158
8159     if (appData.noChessProgram) return;
8160     cps->initDone = FALSE;
8161
8162     if (strcmp(cps->host, "localhost") == 0) {
8163         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8164     } else if (*appData.remoteShell == NULLCHAR) {
8165         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8166     } else {
8167         if (*appData.remoteUser == NULLCHAR) {
8168           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8169                     cps->program);
8170         } else {
8171           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8172                     cps->host, appData.remoteUser, cps->program);
8173         }
8174         err = StartChildProcess(buf, "", &cps->pr);
8175     }
8176     
8177     if (err != 0) {
8178         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8179         DisplayFatalError(buf, err, 1);
8180         cps->pr = NoProc;
8181         cps->isr = NULL;
8182         return;
8183     }
8184     
8185     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8186     if (cps->protocolVersion > 1) {
8187       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8188       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8189       cps->comboCnt = 0;  //                and values of combo boxes
8190       SendToProgram(buf, cps);
8191     } else {
8192       SendToProgram("xboard\n", cps);
8193     }
8194 }
8195
8196
8197 void
8198 TwoMachinesEventIfReady P((void))
8199 {
8200   if (first.lastPing != first.lastPong) {
8201     DisplayMessage("", _("Waiting for first chess program"));
8202     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8203     return;
8204   }
8205   if (second.lastPing != second.lastPong) {
8206     DisplayMessage("", _("Waiting for second chess program"));
8207     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8208     return;
8209   }
8210   ThawUI();
8211   TwoMachinesEvent();
8212 }
8213
8214 void
8215 NextMatchGame P((void))
8216 {
8217     int index; /* [HGM] autoinc: step load index during match */
8218     Reset(FALSE, TRUE);
8219     if (*appData.loadGameFile != NULLCHAR) {
8220         index = appData.loadGameIndex;
8221         if(index < 0) { // [HGM] autoinc
8222             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8223             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8224         } 
8225         LoadGameFromFile(appData.loadGameFile,
8226                          index,
8227                          appData.loadGameFile, FALSE);
8228     } else if (*appData.loadPositionFile != NULLCHAR) {
8229         index = appData.loadPositionIndex;
8230         if(index < 0) { // [HGM] autoinc
8231             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8232             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8233         } 
8234         LoadPositionFromFile(appData.loadPositionFile,
8235                              index,
8236                              appData.loadPositionFile);
8237     }
8238     TwoMachinesEventIfReady();
8239 }
8240
8241 void UserAdjudicationEvent( int result )
8242 {
8243     ChessMove gameResult = GameIsDrawn;
8244
8245     if( result > 0 ) {
8246         gameResult = WhiteWins;
8247     }
8248     else if( result < 0 ) {
8249         gameResult = BlackWins;
8250     }
8251
8252     if( gameMode == TwoMachinesPlay ) {
8253         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8254     }
8255 }
8256
8257
8258 // [HGM] save: calculate checksum of game to make games easily identifiable
8259 int StringCheckSum(char *s)
8260 {
8261         int i = 0;
8262         if(s==NULL) return 0;
8263         while(*s) i = i*259 + *s++;
8264         return i;
8265 }
8266
8267 int GameCheckSum()
8268 {
8269         int i, sum=0;
8270         for(i=backwardMostMove; i<forwardMostMove; i++) {
8271                 sum += pvInfoList[i].depth;
8272                 sum += StringCheckSum(parseList[i]);
8273                 sum += StringCheckSum(commentList[i]);
8274                 sum *= 261;
8275         }
8276         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8277         return sum + StringCheckSum(commentList[i]);
8278 } // end of save patch
8279
8280 void
8281 GameEnds(result, resultDetails, whosays)
8282      ChessMove result;
8283      char *resultDetails;
8284      int whosays;
8285 {
8286     GameMode nextGameMode;
8287     int isIcsGame;
8288     char buf[MSG_SIZ];
8289
8290     if(endingGame) return; /* [HGM] crash: forbid recursion */
8291     endingGame = 1;
8292
8293     if (appData.debugMode) {
8294       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8295               result, resultDetails ? resultDetails : "(null)", whosays);
8296     }
8297
8298     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8299         /* If we are playing on ICS, the server decides when the
8300            game is over, but the engine can offer to draw, claim 
8301            a draw, or resign. 
8302          */
8303 #if ZIPPY
8304         if (appData.zippyPlay && first.initDone) {
8305             if (result == GameIsDrawn) {
8306                 /* In case draw still needs to be claimed */
8307                 SendToICS(ics_prefix);
8308                 SendToICS("draw\n");
8309             } else if (StrCaseStr(resultDetails, "resign")) {
8310                 SendToICS(ics_prefix);
8311                 SendToICS("resign\n");
8312             }
8313         }
8314 #endif
8315         endingGame = 0; /* [HGM] crash */
8316         return;
8317     }
8318
8319     /* If we're loading the game from a file, stop */
8320     if (whosays == GE_FILE) {
8321       (void) StopLoadGameTimer();
8322       gameFileFP = NULL;
8323     }
8324
8325     /* Cancel draw offers */
8326     first.offeredDraw = second.offeredDraw = 0;
8327
8328     /* If this is an ICS game, only ICS can really say it's done;
8329        if not, anyone can. */
8330     isIcsGame = (gameMode == IcsPlayingWhite || 
8331                  gameMode == IcsPlayingBlack || 
8332                  gameMode == IcsObserving    || 
8333                  gameMode == IcsExamining);
8334
8335     if (!isIcsGame || whosays == GE_ICS) {
8336         /* OK -- not an ICS game, or ICS said it was done */
8337         StopClocks();
8338         if (!isIcsGame && !appData.noChessProgram) 
8339           SetUserThinkingEnables();
8340     
8341         /* [HGM] if a machine claims the game end we verify this claim */
8342         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8343             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8344                 char claimer;
8345                 ChessMove trueResult = (ChessMove) -1;
8346
8347                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8348                                             first.twoMachinesColor[0] :
8349                                             second.twoMachinesColor[0] ;
8350
8351                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8352                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8353                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8354                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8355                 } else
8356                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8357                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8358                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8359                 } else
8360                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8361                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8362                 }
8363
8364                 // now verify win claims, but not in drop games, as we don't understand those yet
8365                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8366                                                  || gameInfo.variant == VariantGreat) &&
8367                     (result == WhiteWins && claimer == 'w' ||
8368                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8369                       if (appData.debugMode) {
8370                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8371                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8372                       }
8373                       if(result != trueResult) {
8374                               sprintf(buf, "False win claim: '%s'", resultDetails);
8375                               result = claimer == 'w' ? BlackWins : WhiteWins;
8376                               resultDetails = buf;
8377                       }
8378                 } else
8379                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8380                     && (forwardMostMove <= backwardMostMove ||
8381                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8382                         (claimer=='b')==(forwardMostMove&1))
8383                                                                                   ) {
8384                       /* [HGM] verify: draws that were not flagged are false claims */
8385                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8386                       result = claimer == 'w' ? BlackWins : WhiteWins;
8387                       resultDetails = buf;
8388                 }
8389                 /* (Claiming a loss is accepted no questions asked!) */
8390             }
8391             /* [HGM] bare: don't allow bare King to win */
8392             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8393                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8394                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8395                && result != GameIsDrawn)
8396             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8397                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8398                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8399                         if(p >= 0 && p <= (int)WhiteKing) k++;
8400                 }
8401                 if (appData.debugMode) {
8402                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8403                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8404                 }
8405                 if(k <= 1) {
8406                         result = GameIsDrawn;
8407                         sprintf(buf, "%s but bare king", resultDetails);
8408                         resultDetails = buf;
8409                 }
8410             }
8411         }
8412
8413
8414         if(serverMoves != NULL && !loadFlag) { char c = '=';
8415             if(result==WhiteWins) c = '+';
8416             if(result==BlackWins) c = '-';
8417             if(resultDetails != NULL)
8418                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8419         }
8420         if (resultDetails != NULL) {
8421             gameInfo.result = result;
8422             gameInfo.resultDetails = StrSave(resultDetails);
8423
8424             /* display last move only if game was not loaded from file */
8425             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8426                 DisplayMove(currentMove - 1);
8427     
8428             if (forwardMostMove != 0) {
8429                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8430                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8431                                                                 ) {
8432                     if (*appData.saveGameFile != NULLCHAR) {
8433                         SaveGameToFile(appData.saveGameFile, TRUE);
8434                     } else if (appData.autoSaveGames) {
8435                         AutoSaveGame();
8436                     }
8437                     if (*appData.savePositionFile != NULLCHAR) {
8438                         SavePositionToFile(appData.savePositionFile);
8439                     }
8440                 }
8441             }
8442
8443             /* Tell program how game ended in case it is learning */
8444             /* [HGM] Moved this to after saving the PGN, just in case */
8445             /* engine died and we got here through time loss. In that */
8446             /* case we will get a fatal error writing the pipe, which */
8447             /* would otherwise lose us the PGN.                       */
8448             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8449             /* output during GameEnds should never be fatal anymore   */
8450             if (gameMode == MachinePlaysWhite ||
8451                 gameMode == MachinePlaysBlack ||
8452                 gameMode == TwoMachinesPlay ||
8453                 gameMode == IcsPlayingWhite ||
8454                 gameMode == IcsPlayingBlack ||
8455                 gameMode == BeginningOfGame) {
8456                 char buf[MSG_SIZ];
8457                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8458                         resultDetails);
8459                 if (first.pr != NoProc) {
8460                     SendToProgram(buf, &first);
8461                 }
8462                 if (second.pr != NoProc &&
8463                     gameMode == TwoMachinesPlay) {
8464                     SendToProgram(buf, &second);
8465                 }
8466             }
8467         }
8468
8469         if (appData.icsActive) {
8470             if (appData.quietPlay &&
8471                 (gameMode == IcsPlayingWhite ||
8472                  gameMode == IcsPlayingBlack)) {
8473                 SendToICS(ics_prefix);
8474                 SendToICS("set shout 1\n");
8475             }
8476             nextGameMode = IcsIdle;
8477             ics_user_moved = FALSE;
8478             /* clean up premove.  It's ugly when the game has ended and the
8479              * premove highlights are still on the board.
8480              */
8481             if (gotPremove) {
8482               gotPremove = FALSE;
8483               ClearPremoveHighlights();
8484               DrawPosition(FALSE, boards[currentMove]);
8485             }
8486             if (whosays == GE_ICS) {
8487                 switch (result) {
8488                 case WhiteWins:
8489                     if (gameMode == IcsPlayingWhite)
8490                         PlayIcsWinSound();
8491                     else if(gameMode == IcsPlayingBlack)
8492                         PlayIcsLossSound();
8493                     break;
8494                 case BlackWins:
8495                     if (gameMode == IcsPlayingBlack)
8496                         PlayIcsWinSound();
8497                     else if(gameMode == IcsPlayingWhite)
8498                         PlayIcsLossSound();
8499                     break;
8500                 case GameIsDrawn:
8501                     PlayIcsDrawSound();
8502                     break;
8503                 default:
8504                     PlayIcsUnfinishedSound();
8505                 }
8506             }
8507         } else if (gameMode == EditGame ||
8508                    gameMode == PlayFromGameFile || 
8509                    gameMode == AnalyzeMode || 
8510                    gameMode == AnalyzeFile) {
8511             nextGameMode = gameMode;
8512         } else {
8513             nextGameMode = EndOfGame;
8514         }
8515         pausing = FALSE;
8516         ModeHighlight();
8517     } else {
8518         nextGameMode = gameMode;
8519     }
8520
8521     if (appData.noChessProgram) {
8522         gameMode = nextGameMode;
8523         ModeHighlight();
8524         endingGame = 0; /* [HGM] crash */
8525         return;
8526     }
8527
8528     if (first.reuse) {
8529         /* Put first chess program into idle state */
8530         if (first.pr != NoProc &&
8531             (gameMode == MachinePlaysWhite ||
8532              gameMode == MachinePlaysBlack ||
8533              gameMode == TwoMachinesPlay ||
8534              gameMode == IcsPlayingWhite ||
8535              gameMode == IcsPlayingBlack ||
8536              gameMode == BeginningOfGame)) {
8537             SendToProgram("force\n", &first);
8538             if (first.usePing) {
8539               char buf[MSG_SIZ];
8540               sprintf(buf, "ping %d\n", ++first.lastPing);
8541               SendToProgram(buf, &first);
8542             }
8543         }
8544     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8545         /* Kill off first chess program */
8546         if (first.isr != NULL)
8547           RemoveInputSource(first.isr);
8548         first.isr = NULL;
8549     
8550         if (first.pr != NoProc) {
8551             ExitAnalyzeMode();
8552             DoSleep( appData.delayBeforeQuit );
8553             SendToProgram("quit\n", &first);
8554             DoSleep( appData.delayAfterQuit );
8555             DestroyChildProcess(first.pr, first.useSigterm);
8556         }
8557         first.pr = NoProc;
8558     }
8559     if (second.reuse) {
8560         /* Put second chess program into idle state */
8561         if (second.pr != NoProc &&
8562             gameMode == TwoMachinesPlay) {
8563             SendToProgram("force\n", &second);
8564             if (second.usePing) {
8565               char buf[MSG_SIZ];
8566               sprintf(buf, "ping %d\n", ++second.lastPing);
8567               SendToProgram(buf, &second);
8568             }
8569         }
8570     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8571         /* Kill off second chess program */
8572         if (second.isr != NULL)
8573           RemoveInputSource(second.isr);
8574         second.isr = NULL;
8575     
8576         if (second.pr != NoProc) {
8577             DoSleep( appData.delayBeforeQuit );
8578             SendToProgram("quit\n", &second);
8579             DoSleep( appData.delayAfterQuit );
8580             DestroyChildProcess(second.pr, second.useSigterm);
8581         }
8582         second.pr = NoProc;
8583     }
8584
8585     if (matchMode && gameMode == TwoMachinesPlay) {
8586         switch (result) {
8587         case WhiteWins:
8588           if (first.twoMachinesColor[0] == 'w') {
8589             first.matchWins++;
8590           } else {
8591             second.matchWins++;
8592           }
8593           break;
8594         case BlackWins:
8595           if (first.twoMachinesColor[0] == 'b') {
8596             first.matchWins++;
8597           } else {
8598             second.matchWins++;
8599           }
8600           break;
8601         default:
8602           break;
8603         }
8604         if (matchGame < appData.matchGames) {
8605             char *tmp;
8606             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8607                 tmp = first.twoMachinesColor;
8608                 first.twoMachinesColor = second.twoMachinesColor;
8609                 second.twoMachinesColor = tmp;
8610             }
8611             gameMode = nextGameMode;
8612             matchGame++;
8613             if(appData.matchPause>10000 || appData.matchPause<10)
8614                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8615             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8616             endingGame = 0; /* [HGM] crash */
8617             return;
8618         } else {
8619             char buf[MSG_SIZ];
8620             gameMode = nextGameMode;
8621             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8622                     first.tidy, second.tidy,
8623                     first.matchWins, second.matchWins,
8624                     appData.matchGames - (first.matchWins + second.matchWins));
8625             DisplayFatalError(buf, 0, 0);
8626         }
8627     }
8628     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8629         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8630       ExitAnalyzeMode();
8631     gameMode = nextGameMode;
8632     ModeHighlight();
8633     endingGame = 0;  /* [HGM] crash */
8634 }
8635
8636 /* Assumes program was just initialized (initString sent).
8637    Leaves program in force mode. */
8638 void
8639 FeedMovesToProgram(cps, upto) 
8640      ChessProgramState *cps;
8641      int upto;
8642 {
8643     int i;
8644     
8645     if (appData.debugMode)
8646       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8647               startedFromSetupPosition ? "position and " : "",
8648               backwardMostMove, upto, cps->which);
8649     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8650         // [HGM] variantswitch: make engine aware of new variant
8651         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8652                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8653         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8654         SendToProgram(buf, cps);
8655         currentlyInitializedVariant = gameInfo.variant;
8656     }
8657     SendToProgram("force\n", cps);
8658     if (startedFromSetupPosition) {
8659         SendBoard(cps, backwardMostMove);
8660     if (appData.debugMode) {
8661         fprintf(debugFP, "feedMoves\n");
8662     }
8663     }
8664     for (i = backwardMostMove; i < upto; i++) {
8665         SendMoveToProgram(i, cps);
8666     }
8667 }
8668
8669
8670 void
8671 ResurrectChessProgram()
8672 {
8673      /* The chess program may have exited.
8674         If so, restart it and feed it all the moves made so far. */
8675
8676     if (appData.noChessProgram || first.pr != NoProc) return;
8677     
8678     StartChessProgram(&first);
8679     InitChessProgram(&first, FALSE);
8680     FeedMovesToProgram(&first, currentMove);
8681
8682     if (!first.sendTime) {
8683         /* can't tell gnuchess what its clock should read,
8684            so we bow to its notion. */
8685         ResetClocks();
8686         timeRemaining[0][currentMove] = whiteTimeRemaining;
8687         timeRemaining[1][currentMove] = blackTimeRemaining;
8688     }
8689
8690     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8691                 appData.icsEngineAnalyze) && first.analysisSupport) {
8692       SendToProgram("analyze\n", &first);
8693       first.analyzing = TRUE;
8694     }
8695 }
8696
8697 /*
8698  * Button procedures
8699  */
8700 void
8701 Reset(redraw, init)
8702      int redraw, init;
8703 {
8704     int i;
8705
8706     if (appData.debugMode) {
8707         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8708                 redraw, init, gameMode);
8709     }
8710     CleanupTail(); // [HGM] vari: delete any stored variations
8711     pausing = pauseExamInvalid = FALSE;
8712     startedFromSetupPosition = blackPlaysFirst = FALSE;
8713     firstMove = TRUE;
8714     whiteFlag = blackFlag = FALSE;
8715     userOfferedDraw = FALSE;
8716     hintRequested = bookRequested = FALSE;
8717     first.maybeThinking = FALSE;
8718     second.maybeThinking = FALSE;
8719     first.bookSuspend = FALSE; // [HGM] book
8720     second.bookSuspend = FALSE;
8721     thinkOutput[0] = NULLCHAR;
8722     lastHint[0] = NULLCHAR;
8723     ClearGameInfo(&gameInfo);
8724     gameInfo.variant = StringToVariant(appData.variant);
8725     ics_user_moved = ics_clock_paused = FALSE;
8726     ics_getting_history = H_FALSE;
8727     ics_gamenum = -1;
8728     white_holding[0] = black_holding[0] = NULLCHAR;
8729     ClearProgramStats();
8730     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8731     
8732     ResetFrontEnd();
8733     ClearHighlights();
8734     flipView = appData.flipView;
8735     ClearPremoveHighlights();
8736     gotPremove = FALSE;
8737     alarmSounded = FALSE;
8738
8739     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8740     if(appData.serverMovesName != NULL) {
8741         /* [HGM] prepare to make moves file for broadcasting */
8742         clock_t t = clock();
8743         if(serverMoves != NULL) fclose(serverMoves);
8744         serverMoves = fopen(appData.serverMovesName, "r");
8745         if(serverMoves != NULL) {
8746             fclose(serverMoves);
8747             /* delay 15 sec before overwriting, so all clients can see end */
8748             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8749         }
8750         serverMoves = fopen(appData.serverMovesName, "w");
8751     }
8752
8753     ExitAnalyzeMode();
8754     gameMode = BeginningOfGame;
8755     ModeHighlight();
8756     if(appData.icsActive) gameInfo.variant = VariantNormal;
8757     currentMove = forwardMostMove = backwardMostMove = 0;
8758     InitPosition(redraw);
8759     for (i = 0; i < MAX_MOVES; i++) {
8760         if (commentList[i] != NULL) {
8761             free(commentList[i]);
8762             commentList[i] = NULL;
8763         }
8764     }
8765     ResetClocks();
8766     timeRemaining[0][0] = whiteTimeRemaining;
8767     timeRemaining[1][0] = blackTimeRemaining;
8768     if (first.pr == NULL) {
8769         StartChessProgram(&first);
8770     }
8771     if (init) {
8772             InitChessProgram(&first, startedFromSetupPosition);
8773     }
8774     DisplayTitle("");
8775     DisplayMessage("", "");
8776     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8777     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8778 }
8779
8780 void
8781 AutoPlayGameLoop()
8782 {
8783     for (;;) {
8784         if (!AutoPlayOneMove())
8785           return;
8786         if (matchMode || appData.timeDelay == 0)
8787           continue;
8788         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8789           return;
8790         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8791         break;
8792     }
8793 }
8794
8795
8796 int
8797 AutoPlayOneMove()
8798 {
8799     int fromX, fromY, toX, toY;
8800
8801     if (appData.debugMode) {
8802       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8803     }
8804
8805     if (gameMode != PlayFromGameFile)
8806       return FALSE;
8807
8808     if (currentMove >= forwardMostMove) {
8809       gameMode = EditGame;
8810       ModeHighlight();
8811
8812       /* [AS] Clear current move marker at the end of a game */
8813       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8814
8815       return FALSE;
8816     }
8817     
8818     toX = moveList[currentMove][2] - AAA;
8819     toY = moveList[currentMove][3] - ONE;
8820
8821     if (moveList[currentMove][1] == '@') {
8822         if (appData.highlightLastMove) {
8823             SetHighlights(-1, -1, toX, toY);
8824         }
8825     } else {
8826         fromX = moveList[currentMove][0] - AAA;
8827         fromY = moveList[currentMove][1] - ONE;
8828
8829         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8830
8831         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8832
8833         if (appData.highlightLastMove) {
8834             SetHighlights(fromX, fromY, toX, toY);
8835         }
8836     }
8837     DisplayMove(currentMove);
8838     SendMoveToProgram(currentMove++, &first);
8839     DisplayBothClocks();
8840     DrawPosition(FALSE, boards[currentMove]);
8841     // [HGM] PV info: always display, routine tests if empty
8842     DisplayComment(currentMove - 1, commentList[currentMove]);
8843     return TRUE;
8844 }
8845
8846
8847 int
8848 LoadGameOneMove(readAhead)
8849      ChessMove readAhead;
8850 {
8851     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8852     char promoChar = NULLCHAR;
8853     ChessMove moveType;
8854     char move[MSG_SIZ];
8855     char *p, *q;
8856     
8857     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8858         gameMode != AnalyzeMode && gameMode != Training) {
8859         gameFileFP = NULL;
8860         return FALSE;
8861     }
8862     
8863     yyboardindex = forwardMostMove;
8864     if (readAhead != (ChessMove)0) {
8865       moveType = readAhead;
8866     } else {
8867       if (gameFileFP == NULL)
8868           return FALSE;
8869       moveType = (ChessMove) yylex();
8870     }
8871     
8872     done = FALSE;
8873     switch (moveType) {
8874       case Comment:
8875         if (appData.debugMode) 
8876           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8877         p = yy_text;
8878
8879         /* append the comment but don't display it */
8880         AppendComment(currentMove, p, FALSE);
8881         return TRUE;
8882
8883       case WhiteCapturesEnPassant:
8884       case BlackCapturesEnPassant:
8885       case WhitePromotionChancellor:
8886       case BlackPromotionChancellor:
8887       case WhitePromotionArchbishop:
8888       case BlackPromotionArchbishop:
8889       case WhitePromotionCentaur:
8890       case BlackPromotionCentaur:
8891       case WhitePromotionQueen:
8892       case BlackPromotionQueen:
8893       case WhitePromotionRook:
8894       case BlackPromotionRook:
8895       case WhitePromotionBishop:
8896       case BlackPromotionBishop:
8897       case WhitePromotionKnight:
8898       case BlackPromotionKnight:
8899       case WhitePromotionKing:
8900       case BlackPromotionKing:
8901       case NormalMove:
8902       case WhiteKingSideCastle:
8903       case WhiteQueenSideCastle:
8904       case BlackKingSideCastle:
8905       case BlackQueenSideCastle:
8906       case WhiteKingSideCastleWild:
8907       case WhiteQueenSideCastleWild:
8908       case BlackKingSideCastleWild:
8909       case BlackQueenSideCastleWild:
8910       /* PUSH Fabien */
8911       case WhiteHSideCastleFR:
8912       case WhiteASideCastleFR:
8913       case BlackHSideCastleFR:
8914       case BlackASideCastleFR:
8915       /* POP Fabien */
8916         if (appData.debugMode)
8917           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8918         fromX = currentMoveString[0] - AAA;
8919         fromY = currentMoveString[1] - ONE;
8920         toX = currentMoveString[2] - AAA;
8921         toY = currentMoveString[3] - ONE;
8922         promoChar = currentMoveString[4];
8923         break;
8924
8925       case WhiteDrop:
8926       case BlackDrop:
8927         if (appData.debugMode)
8928           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8929         fromX = moveType == WhiteDrop ?
8930           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8931         (int) CharToPiece(ToLower(currentMoveString[0]));
8932         fromY = DROP_RANK;
8933         toX = currentMoveString[2] - AAA;
8934         toY = currentMoveString[3] - ONE;
8935         break;
8936
8937       case WhiteWins:
8938       case BlackWins:
8939       case GameIsDrawn:
8940       case GameUnfinished:
8941         if (appData.debugMode)
8942           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8943         p = strchr(yy_text, '{');
8944         if (p == NULL) p = strchr(yy_text, '(');
8945         if (p == NULL) {
8946             p = yy_text;
8947             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8948         } else {
8949             q = strchr(p, *p == '{' ? '}' : ')');
8950             if (q != NULL) *q = NULLCHAR;
8951             p++;
8952         }
8953         GameEnds(moveType, p, GE_FILE);
8954         done = TRUE;
8955         if (cmailMsgLoaded) {
8956             ClearHighlights();
8957             flipView = WhiteOnMove(currentMove);
8958             if (moveType == GameUnfinished) flipView = !flipView;
8959             if (appData.debugMode)
8960               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8961         }
8962         break;
8963
8964       case (ChessMove) 0:       /* end of file */
8965         if (appData.debugMode)
8966           fprintf(debugFP, "Parser hit end of file\n");
8967         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8968           case MT_NONE:
8969           case MT_CHECK:
8970             break;
8971           case MT_CHECKMATE:
8972           case MT_STAINMATE:
8973             if (WhiteOnMove(currentMove)) {
8974                 GameEnds(BlackWins, "Black mates", GE_FILE);
8975             } else {
8976                 GameEnds(WhiteWins, "White mates", GE_FILE);
8977             }
8978             break;
8979           case MT_STALEMATE:
8980             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8981             break;
8982         }
8983         done = TRUE;
8984         break;
8985
8986       case MoveNumberOne:
8987         if (lastLoadGameStart == GNUChessGame) {
8988             /* GNUChessGames have numbers, but they aren't move numbers */
8989             if (appData.debugMode)
8990               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8991                       yy_text, (int) moveType);
8992             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8993         }
8994         /* else fall thru */
8995
8996       case XBoardGame:
8997       case GNUChessGame:
8998       case PGNTag:
8999         /* Reached start of next game in file */
9000         if (appData.debugMode)
9001           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9002         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9003           case MT_NONE:
9004           case MT_CHECK:
9005             break;
9006           case MT_CHECKMATE:
9007           case MT_STAINMATE:
9008             if (WhiteOnMove(currentMove)) {
9009                 GameEnds(BlackWins, "Black mates", GE_FILE);
9010             } else {
9011                 GameEnds(WhiteWins, "White mates", GE_FILE);
9012             }
9013             break;
9014           case MT_STALEMATE:
9015             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9016             break;
9017         }
9018         done = TRUE;
9019         break;
9020
9021       case PositionDiagram:     /* should not happen; ignore */
9022       case ElapsedTime:         /* ignore */
9023       case NAG:                 /* ignore */
9024         if (appData.debugMode)
9025           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9026                   yy_text, (int) moveType);
9027         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9028
9029       case IllegalMove:
9030         if (appData.testLegality) {
9031             if (appData.debugMode)
9032               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9033             sprintf(move, _("Illegal move: %d.%s%s"),
9034                     (forwardMostMove / 2) + 1,
9035                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9036             DisplayError(move, 0);
9037             done = TRUE;
9038         } else {
9039             if (appData.debugMode)
9040               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9041                       yy_text, currentMoveString);
9042             fromX = currentMoveString[0] - AAA;
9043             fromY = currentMoveString[1] - ONE;
9044             toX = currentMoveString[2] - AAA;
9045             toY = currentMoveString[3] - ONE;
9046             promoChar = currentMoveString[4];
9047         }
9048         break;
9049
9050       case AmbiguousMove:
9051         if (appData.debugMode)
9052           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9053         sprintf(move, _("Ambiguous move: %d.%s%s"),
9054                 (forwardMostMove / 2) + 1,
9055                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9056         DisplayError(move, 0);
9057         done = TRUE;
9058         break;
9059
9060       default:
9061       case ImpossibleMove:
9062         if (appData.debugMode)
9063           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9064         sprintf(move, _("Illegal move: %d.%s%s"),
9065                 (forwardMostMove / 2) + 1,
9066                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9067         DisplayError(move, 0);
9068         done = TRUE;
9069         break;
9070     }
9071
9072     if (done) {
9073         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9074             DrawPosition(FALSE, boards[currentMove]);
9075             DisplayBothClocks();
9076             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9077               DisplayComment(currentMove - 1, commentList[currentMove]);
9078         }
9079         (void) StopLoadGameTimer();
9080         gameFileFP = NULL;
9081         cmailOldMove = forwardMostMove;
9082         return FALSE;
9083     } else {
9084         /* currentMoveString is set as a side-effect of yylex */
9085         strcat(currentMoveString, "\n");
9086         strcpy(moveList[forwardMostMove], currentMoveString);
9087         
9088         thinkOutput[0] = NULLCHAR;
9089         MakeMove(fromX, fromY, toX, toY, promoChar);
9090         currentMove = forwardMostMove;
9091         return TRUE;
9092     }
9093 }
9094
9095 /* Load the nth game from the given file */
9096 int
9097 LoadGameFromFile(filename, n, title, useList)
9098      char *filename;
9099      int n;
9100      char *title;
9101      /*Boolean*/ int useList;
9102 {
9103     FILE *f;
9104     char buf[MSG_SIZ];
9105
9106     if (strcmp(filename, "-") == 0) {
9107         f = stdin;
9108         title = "stdin";
9109     } else {
9110         f = fopen(filename, "rb");
9111         if (f == NULL) {
9112           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9113             DisplayError(buf, errno);
9114             return FALSE;
9115         }
9116     }
9117     if (fseek(f, 0, 0) == -1) {
9118         /* f is not seekable; probably a pipe */
9119         useList = FALSE;
9120     }
9121     if (useList && n == 0) {
9122         int error = GameListBuild(f);
9123         if (error) {
9124             DisplayError(_("Cannot build game list"), error);
9125         } else if (!ListEmpty(&gameList) &&
9126                    ((ListGame *) gameList.tailPred)->number > 1) {
9127             GameListPopUp(f, title);
9128             return TRUE;
9129         }
9130         GameListDestroy();
9131         n = 1;
9132     }
9133     if (n == 0) n = 1;
9134     return LoadGame(f, n, title, FALSE);
9135 }
9136
9137
9138 void
9139 MakeRegisteredMove()
9140 {
9141     int fromX, fromY, toX, toY;
9142     char promoChar;
9143     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9144         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9145           case CMAIL_MOVE:
9146           case CMAIL_DRAW:
9147             if (appData.debugMode)
9148               fprintf(debugFP, "Restoring %s for game %d\n",
9149                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9150     
9151             thinkOutput[0] = NULLCHAR;
9152             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9153             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9154             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9155             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9156             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9157             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9158             MakeMove(fromX, fromY, toX, toY, promoChar);
9159             ShowMove(fromX, fromY, toX, toY);
9160               
9161             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9162               case MT_NONE:
9163               case MT_CHECK:
9164                 break;
9165                 
9166               case MT_CHECKMATE:
9167               case MT_STAINMATE:
9168                 if (WhiteOnMove(currentMove)) {
9169                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9170                 } else {
9171                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9172                 }
9173                 break;
9174                 
9175               case MT_STALEMATE:
9176                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9177                 break;
9178             }
9179
9180             break;
9181             
9182           case CMAIL_RESIGN:
9183             if (WhiteOnMove(currentMove)) {
9184                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9185             } else {
9186                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9187             }
9188             break;
9189             
9190           case CMAIL_ACCEPT:
9191             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9192             break;
9193               
9194           default:
9195             break;
9196         }
9197     }
9198
9199     return;
9200 }
9201
9202 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9203 int
9204 CmailLoadGame(f, gameNumber, title, useList)
9205      FILE *f;
9206      int gameNumber;
9207      char *title;
9208      int useList;
9209 {
9210     int retVal;
9211
9212     if (gameNumber > nCmailGames) {
9213         DisplayError(_("No more games in this message"), 0);
9214         return FALSE;
9215     }
9216     if (f == lastLoadGameFP) {
9217         int offset = gameNumber - lastLoadGameNumber;
9218         if (offset == 0) {
9219             cmailMsg[0] = NULLCHAR;
9220             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9221                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9222                 nCmailMovesRegistered--;
9223             }
9224             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9225             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9226                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9227             }
9228         } else {
9229             if (! RegisterMove()) return FALSE;
9230         }
9231     }
9232
9233     retVal = LoadGame(f, gameNumber, title, useList);
9234
9235     /* Make move registered during previous look at this game, if any */
9236     MakeRegisteredMove();
9237
9238     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9239         commentList[currentMove]
9240           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9241         DisplayComment(currentMove - 1, commentList[currentMove]);
9242     }
9243
9244     return retVal;
9245 }
9246
9247 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9248 int
9249 ReloadGame(offset)
9250      int offset;
9251 {
9252     int gameNumber = lastLoadGameNumber + offset;
9253     if (lastLoadGameFP == NULL) {
9254         DisplayError(_("No game has been loaded yet"), 0);
9255         return FALSE;
9256     }
9257     if (gameNumber <= 0) {
9258         DisplayError(_("Can't back up any further"), 0);
9259         return FALSE;
9260     }
9261     if (cmailMsgLoaded) {
9262         return CmailLoadGame(lastLoadGameFP, gameNumber,
9263                              lastLoadGameTitle, lastLoadGameUseList);
9264     } else {
9265         return LoadGame(lastLoadGameFP, gameNumber,
9266                         lastLoadGameTitle, lastLoadGameUseList);
9267     }
9268 }
9269
9270
9271
9272 /* Load the nth game from open file f */
9273 int
9274 LoadGame(f, gameNumber, title, useList)
9275      FILE *f;
9276      int gameNumber;
9277      char *title;
9278      int useList;
9279 {
9280     ChessMove cm;
9281     char buf[MSG_SIZ];
9282     int gn = gameNumber;
9283     ListGame *lg = NULL;
9284     int numPGNTags = 0;
9285     int err;
9286     GameMode oldGameMode;
9287     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9288
9289     if (appData.debugMode) 
9290         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9291
9292     if (gameMode == Training )
9293         SetTrainingModeOff();
9294
9295     oldGameMode = gameMode;
9296     if (gameMode != BeginningOfGame) {
9297       Reset(FALSE, TRUE);
9298     }
9299
9300     gameFileFP = f;
9301     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9302         fclose(lastLoadGameFP);
9303     }
9304
9305     if (useList) {
9306         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9307         
9308         if (lg) {
9309             fseek(f, lg->offset, 0);
9310             GameListHighlight(gameNumber);
9311             gn = 1;
9312         }
9313         else {
9314             DisplayError(_("Game number out of range"), 0);
9315             return FALSE;
9316         }
9317     } else {
9318         GameListDestroy();
9319         if (fseek(f, 0, 0) == -1) {
9320             if (f == lastLoadGameFP ?
9321                 gameNumber == lastLoadGameNumber + 1 :
9322                 gameNumber == 1) {
9323                 gn = 1;
9324             } else {
9325                 DisplayError(_("Can't seek on game file"), 0);
9326                 return FALSE;
9327             }
9328         }
9329     }
9330     lastLoadGameFP = f;
9331     lastLoadGameNumber = gameNumber;
9332     strcpy(lastLoadGameTitle, title);
9333     lastLoadGameUseList = useList;
9334
9335     yynewfile(f);
9336
9337     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9338       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9339                 lg->gameInfo.black);
9340             DisplayTitle(buf);
9341     } else if (*title != NULLCHAR) {
9342         if (gameNumber > 1) {
9343             sprintf(buf, "%s %d", title, gameNumber);
9344             DisplayTitle(buf);
9345         } else {
9346             DisplayTitle(title);
9347         }
9348     }
9349
9350     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9351         gameMode = PlayFromGameFile;
9352         ModeHighlight();
9353     }
9354
9355     currentMove = forwardMostMove = backwardMostMove = 0;
9356     CopyBoard(boards[0], initialPosition);
9357     StopClocks();
9358
9359     /*
9360      * Skip the first gn-1 games in the file.
9361      * Also skip over anything that precedes an identifiable 
9362      * start of game marker, to avoid being confused by 
9363      * garbage at the start of the file.  Currently 
9364      * recognized start of game markers are the move number "1",
9365      * the pattern "gnuchess .* game", the pattern
9366      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9367      * A game that starts with one of the latter two patterns
9368      * will also have a move number 1, possibly
9369      * following a position diagram.
9370      * 5-4-02: Let's try being more lenient and allowing a game to
9371      * start with an unnumbered move.  Does that break anything?
9372      */
9373     cm = lastLoadGameStart = (ChessMove) 0;
9374     while (gn > 0) {
9375         yyboardindex = forwardMostMove;
9376         cm = (ChessMove) yylex();
9377         switch (cm) {
9378           case (ChessMove) 0:
9379             if (cmailMsgLoaded) {
9380                 nCmailGames = CMAIL_MAX_GAMES - gn;
9381             } else {
9382                 Reset(TRUE, TRUE);
9383                 DisplayError(_("Game not found in file"), 0);
9384             }
9385             return FALSE;
9386
9387           case GNUChessGame:
9388           case XBoardGame:
9389             gn--;
9390             lastLoadGameStart = cm;
9391             break;
9392             
9393           case MoveNumberOne:
9394             switch (lastLoadGameStart) {
9395               case GNUChessGame:
9396               case XBoardGame:
9397               case PGNTag:
9398                 break;
9399               case MoveNumberOne:
9400               case (ChessMove) 0:
9401                 gn--;           /* count this game */
9402                 lastLoadGameStart = cm;
9403                 break;
9404               default:
9405                 /* impossible */
9406                 break;
9407             }
9408             break;
9409
9410           case PGNTag:
9411             switch (lastLoadGameStart) {
9412               case GNUChessGame:
9413               case PGNTag:
9414               case MoveNumberOne:
9415               case (ChessMove) 0:
9416                 gn--;           /* count this game */
9417                 lastLoadGameStart = cm;
9418                 break;
9419               case XBoardGame:
9420                 lastLoadGameStart = cm; /* game counted already */
9421                 break;
9422               default:
9423                 /* impossible */
9424                 break;
9425             }
9426             if (gn > 0) {
9427                 do {
9428                     yyboardindex = forwardMostMove;
9429                     cm = (ChessMove) yylex();
9430                 } while (cm == PGNTag || cm == Comment);
9431             }
9432             break;
9433
9434           case WhiteWins:
9435           case BlackWins:
9436           case GameIsDrawn:
9437             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9438                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9439                     != CMAIL_OLD_RESULT) {
9440                     nCmailResults ++ ;
9441                     cmailResult[  CMAIL_MAX_GAMES
9442                                 - gn - 1] = CMAIL_OLD_RESULT;
9443                 }
9444             }
9445             break;
9446
9447           case NormalMove:
9448             /* Only a NormalMove can be at the start of a game
9449              * without a position diagram. */
9450             if (lastLoadGameStart == (ChessMove) 0) {
9451               gn--;
9452               lastLoadGameStart = MoveNumberOne;
9453             }
9454             break;
9455
9456           default:
9457             break;
9458         }
9459     }
9460     
9461     if (appData.debugMode)
9462       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9463
9464     if (cm == XBoardGame) {
9465         /* Skip any header junk before position diagram and/or move 1 */
9466         for (;;) {
9467             yyboardindex = forwardMostMove;
9468             cm = (ChessMove) yylex();
9469
9470             if (cm == (ChessMove) 0 ||
9471                 cm == GNUChessGame || cm == XBoardGame) {
9472                 /* Empty game; pretend end-of-file and handle later */
9473                 cm = (ChessMove) 0;
9474                 break;
9475             }
9476
9477             if (cm == MoveNumberOne || cm == PositionDiagram ||
9478                 cm == PGNTag || cm == Comment)
9479               break;
9480         }
9481     } else if (cm == GNUChessGame) {
9482         if (gameInfo.event != NULL) {
9483             free(gameInfo.event);
9484         }
9485         gameInfo.event = StrSave(yy_text);
9486     }   
9487
9488     startedFromSetupPosition = FALSE;
9489     while (cm == PGNTag) {
9490         if (appData.debugMode) 
9491           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9492         err = ParsePGNTag(yy_text, &gameInfo);
9493         if (!err) numPGNTags++;
9494
9495         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9496         if(gameInfo.variant != oldVariant) {
9497             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9498             InitPosition(TRUE);
9499             oldVariant = gameInfo.variant;
9500             if (appData.debugMode) 
9501               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9502         }
9503
9504
9505         if (gameInfo.fen != NULL) {
9506           Board initial_position;
9507           startedFromSetupPosition = TRUE;
9508           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9509             Reset(TRUE, TRUE);
9510             DisplayError(_("Bad FEN position in file"), 0);
9511             return FALSE;
9512           }
9513           CopyBoard(boards[0], initial_position);
9514           if (blackPlaysFirst) {
9515             currentMove = forwardMostMove = backwardMostMove = 1;
9516             CopyBoard(boards[1], initial_position);
9517             strcpy(moveList[0], "");
9518             strcpy(parseList[0], "");
9519             timeRemaining[0][1] = whiteTimeRemaining;
9520             timeRemaining[1][1] = blackTimeRemaining;
9521             if (commentList[0] != NULL) {
9522               commentList[1] = commentList[0];
9523               commentList[0] = NULL;
9524             }
9525           } else {
9526             currentMove = forwardMostMove = backwardMostMove = 0;
9527           }
9528           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9529           {   int i;
9530               initialRulePlies = FENrulePlies;
9531               for( i=0; i< nrCastlingRights; i++ )
9532                   initialRights[i] = initial_position[CASTLING][i];
9533           }
9534           yyboardindex = forwardMostMove;
9535           free(gameInfo.fen);
9536           gameInfo.fen = NULL;
9537         }
9538
9539         yyboardindex = forwardMostMove;
9540         cm = (ChessMove) yylex();
9541
9542         /* Handle comments interspersed among the tags */
9543         while (cm == Comment) {
9544             char *p;
9545             if (appData.debugMode) 
9546               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9547             p = yy_text;
9548             AppendComment(currentMove, p, FALSE);
9549             yyboardindex = forwardMostMove;
9550             cm = (ChessMove) yylex();
9551         }
9552     }
9553
9554     /* don't rely on existence of Event tag since if game was
9555      * pasted from clipboard the Event tag may not exist
9556      */
9557     if (numPGNTags > 0){
9558         char *tags;
9559         if (gameInfo.variant == VariantNormal) {
9560           gameInfo.variant = StringToVariant(gameInfo.event);
9561         }
9562         if (!matchMode) {
9563           if( appData.autoDisplayTags ) {
9564             tags = PGNTags(&gameInfo);
9565             TagsPopUp(tags, CmailMsg());
9566             free(tags);
9567           }
9568         }
9569     } else {
9570         /* Make something up, but don't display it now */
9571         SetGameInfo();
9572         TagsPopDown();
9573     }
9574
9575     if (cm == PositionDiagram) {
9576         int i, j;
9577         char *p;
9578         Board initial_position;
9579
9580         if (appData.debugMode)
9581           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9582
9583         if (!startedFromSetupPosition) {
9584             p = yy_text;
9585             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9586               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9587                 switch (*p) {
9588                   case '[':
9589                   case '-':
9590                   case ' ':
9591                   case '\t':
9592                   case '\n':
9593                   case '\r':
9594                     break;
9595                   default:
9596                     initial_position[i][j++] = CharToPiece(*p);
9597                     break;
9598                 }
9599             while (*p == ' ' || *p == '\t' ||
9600                    *p == '\n' || *p == '\r') p++;
9601         
9602             if (strncmp(p, "black", strlen("black"))==0)
9603               blackPlaysFirst = TRUE;
9604             else
9605               blackPlaysFirst = FALSE;
9606             startedFromSetupPosition = TRUE;
9607         
9608             CopyBoard(boards[0], initial_position);
9609             if (blackPlaysFirst) {
9610                 currentMove = forwardMostMove = backwardMostMove = 1;
9611                 CopyBoard(boards[1], initial_position);
9612                 strcpy(moveList[0], "");
9613                 strcpy(parseList[0], "");
9614                 timeRemaining[0][1] = whiteTimeRemaining;
9615                 timeRemaining[1][1] = blackTimeRemaining;
9616                 if (commentList[0] != NULL) {
9617                     commentList[1] = commentList[0];
9618                     commentList[0] = NULL;
9619                 }
9620             } else {
9621                 currentMove = forwardMostMove = backwardMostMove = 0;
9622             }
9623         }
9624         yyboardindex = forwardMostMove;
9625         cm = (ChessMove) yylex();
9626     }
9627
9628     if (first.pr == NoProc) {
9629         StartChessProgram(&first);
9630     }
9631     InitChessProgram(&first, FALSE);
9632     SendToProgram("force\n", &first);
9633     if (startedFromSetupPosition) {
9634         SendBoard(&first, forwardMostMove);
9635     if (appData.debugMode) {
9636         fprintf(debugFP, "Load Game\n");
9637     }
9638         DisplayBothClocks();
9639     }      
9640
9641     /* [HGM] server: flag to write setup moves in broadcast file as one */
9642     loadFlag = appData.suppressLoadMoves;
9643
9644     while (cm == Comment) {
9645         char *p;
9646         if (appData.debugMode) 
9647           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9648         p = yy_text;
9649         AppendComment(currentMove, p, FALSE);
9650         yyboardindex = forwardMostMove;
9651         cm = (ChessMove) yylex();
9652     }
9653
9654     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9655         cm == WhiteWins || cm == BlackWins ||
9656         cm == GameIsDrawn || cm == GameUnfinished) {
9657         DisplayMessage("", _("No moves in game"));
9658         if (cmailMsgLoaded) {
9659             if (appData.debugMode)
9660               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9661             ClearHighlights();
9662             flipView = FALSE;
9663         }
9664         DrawPosition(FALSE, boards[currentMove]);
9665         DisplayBothClocks();
9666         gameMode = EditGame;
9667         ModeHighlight();
9668         gameFileFP = NULL;
9669         cmailOldMove = 0;
9670         return TRUE;
9671     }
9672
9673     // [HGM] PV info: routine tests if comment empty
9674     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9675         DisplayComment(currentMove - 1, commentList[currentMove]);
9676     }
9677     if (!matchMode && appData.timeDelay != 0) 
9678       DrawPosition(FALSE, boards[currentMove]);
9679
9680     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9681       programStats.ok_to_send = 1;
9682     }
9683
9684     /* if the first token after the PGN tags is a move
9685      * and not move number 1, retrieve it from the parser 
9686      */
9687     if (cm != MoveNumberOne)
9688         LoadGameOneMove(cm);
9689
9690     /* load the remaining moves from the file */
9691     while (LoadGameOneMove((ChessMove)0)) {
9692       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9693       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9694     }
9695
9696     /* rewind to the start of the game */
9697     currentMove = backwardMostMove;
9698
9699     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9700
9701     if (oldGameMode == AnalyzeFile ||
9702         oldGameMode == AnalyzeMode) {
9703       AnalyzeFileEvent();
9704     }
9705
9706     if (matchMode || appData.timeDelay == 0) {
9707       ToEndEvent();
9708       gameMode = EditGame;
9709       ModeHighlight();
9710     } else if (appData.timeDelay > 0) {
9711       AutoPlayGameLoop();
9712     }
9713
9714     if (appData.debugMode) 
9715         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9716
9717     loadFlag = 0; /* [HGM] true game starts */
9718     return TRUE;
9719 }
9720
9721 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9722 int
9723 ReloadPosition(offset)
9724      int offset;
9725 {
9726     int positionNumber = lastLoadPositionNumber + offset;
9727     if (lastLoadPositionFP == NULL) {
9728         DisplayError(_("No position has been loaded yet"), 0);
9729         return FALSE;
9730     }
9731     if (positionNumber <= 0) {
9732         DisplayError(_("Can't back up any further"), 0);
9733         return FALSE;
9734     }
9735     return LoadPosition(lastLoadPositionFP, positionNumber,
9736                         lastLoadPositionTitle);
9737 }
9738
9739 /* Load the nth position from the given file */
9740 int
9741 LoadPositionFromFile(filename, n, title)
9742      char *filename;
9743      int n;
9744      char *title;
9745 {
9746     FILE *f;
9747     char buf[MSG_SIZ];
9748
9749     if (strcmp(filename, "-") == 0) {
9750         return LoadPosition(stdin, n, "stdin");
9751     } else {
9752         f = fopen(filename, "rb");
9753         if (f == NULL) {
9754             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9755             DisplayError(buf, errno);
9756             return FALSE;
9757         } else {
9758             return LoadPosition(f, n, title);
9759         }
9760     }
9761 }
9762
9763 /* Load the nth position from the given open file, and close it */
9764 int
9765 LoadPosition(f, positionNumber, title)
9766      FILE *f;
9767      int positionNumber;
9768      char *title;
9769 {
9770     char *p, line[MSG_SIZ];
9771     Board initial_position;
9772     int i, j, fenMode, pn;
9773     
9774     if (gameMode == Training )
9775         SetTrainingModeOff();
9776
9777     if (gameMode != BeginningOfGame) {
9778         Reset(FALSE, TRUE);
9779     }
9780     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9781         fclose(lastLoadPositionFP);
9782     }
9783     if (positionNumber == 0) positionNumber = 1;
9784     lastLoadPositionFP = f;
9785     lastLoadPositionNumber = positionNumber;
9786     strcpy(lastLoadPositionTitle, title);
9787     if (first.pr == NoProc) {
9788       StartChessProgram(&first);
9789       InitChessProgram(&first, FALSE);
9790     }    
9791     pn = positionNumber;
9792     if (positionNumber < 0) {
9793         /* Negative position number means to seek to that byte offset */
9794         if (fseek(f, -positionNumber, 0) == -1) {
9795             DisplayError(_("Can't seek on position file"), 0);
9796             return FALSE;
9797         };
9798         pn = 1;
9799     } else {
9800         if (fseek(f, 0, 0) == -1) {
9801             if (f == lastLoadPositionFP ?
9802                 positionNumber == lastLoadPositionNumber + 1 :
9803                 positionNumber == 1) {
9804                 pn = 1;
9805             } else {
9806                 DisplayError(_("Can't seek on position file"), 0);
9807                 return FALSE;
9808             }
9809         }
9810     }
9811     /* See if this file is FEN or old-style xboard */
9812     if (fgets(line, MSG_SIZ, f) == NULL) {
9813         DisplayError(_("Position not found in file"), 0);
9814         return FALSE;
9815     }
9816     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9817     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9818
9819     if (pn >= 2) {
9820         if (fenMode || line[0] == '#') pn--;
9821         while (pn > 0) {
9822             /* skip positions before number pn */
9823             if (fgets(line, MSG_SIZ, f) == NULL) {
9824                 Reset(TRUE, TRUE);
9825                 DisplayError(_("Position not found in file"), 0);
9826                 return FALSE;
9827             }
9828             if (fenMode || line[0] == '#') pn--;
9829         }
9830     }
9831
9832     if (fenMode) {
9833         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9834             DisplayError(_("Bad FEN position in file"), 0);
9835             return FALSE;
9836         }
9837     } else {
9838         (void) fgets(line, MSG_SIZ, f);
9839         (void) fgets(line, MSG_SIZ, f);
9840     
9841         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9842             (void) fgets(line, MSG_SIZ, f);
9843             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9844                 if (*p == ' ')
9845                   continue;
9846                 initial_position[i][j++] = CharToPiece(*p);
9847             }
9848         }
9849     
9850         blackPlaysFirst = FALSE;
9851         if (!feof(f)) {
9852             (void) fgets(line, MSG_SIZ, f);
9853             if (strncmp(line, "black", strlen("black"))==0)
9854               blackPlaysFirst = TRUE;
9855         }
9856     }
9857     startedFromSetupPosition = TRUE;
9858     
9859     SendToProgram("force\n", &first);
9860     CopyBoard(boards[0], initial_position);
9861     if (blackPlaysFirst) {
9862         currentMove = forwardMostMove = backwardMostMove = 1;
9863         strcpy(moveList[0], "");
9864         strcpy(parseList[0], "");
9865         CopyBoard(boards[1], initial_position);
9866         DisplayMessage("", _("Black to play"));
9867     } else {
9868         currentMove = forwardMostMove = backwardMostMove = 0;
9869         DisplayMessage("", _("White to play"));
9870     }
9871     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9872     SendBoard(&first, forwardMostMove);
9873     if (appData.debugMode) {
9874 int i, j;
9875   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9876   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9877         fprintf(debugFP, "Load Position\n");
9878     }
9879
9880     if (positionNumber > 1) {
9881         sprintf(line, "%s %d", title, positionNumber);
9882         DisplayTitle(line);
9883     } else {
9884         DisplayTitle(title);
9885     }
9886     gameMode = EditGame;
9887     ModeHighlight();
9888     ResetClocks();
9889     timeRemaining[0][1] = whiteTimeRemaining;
9890     timeRemaining[1][1] = blackTimeRemaining;
9891     DrawPosition(FALSE, boards[currentMove]);
9892    
9893     return TRUE;
9894 }
9895
9896
9897 void
9898 CopyPlayerNameIntoFileName(dest, src)
9899      char **dest, *src;
9900 {
9901     while (*src != NULLCHAR && *src != ',') {
9902         if (*src == ' ') {
9903             *(*dest)++ = '_';
9904             src++;
9905         } else {
9906             *(*dest)++ = *src++;
9907         }
9908     }
9909 }
9910
9911 char *DefaultFileName(ext)
9912      char *ext;
9913 {
9914     static char def[MSG_SIZ];
9915     char *p;
9916
9917     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9918         p = def;
9919         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9920         *p++ = '-';
9921         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9922         *p++ = '.';
9923         strcpy(p, ext);
9924     } else {
9925         def[0] = NULLCHAR;
9926     }
9927     return def;
9928 }
9929
9930 /* Save the current game to the given file */
9931 int
9932 SaveGameToFile(filename, append)
9933      char *filename;
9934      int append;
9935 {
9936     FILE *f;
9937     char buf[MSG_SIZ];
9938
9939     if (strcmp(filename, "-") == 0) {
9940         return SaveGame(stdout, 0, NULL);
9941     } else {
9942         f = fopen(filename, append ? "a" : "w");
9943         if (f == NULL) {
9944             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9945             DisplayError(buf, errno);
9946             return FALSE;
9947         } else {
9948             return SaveGame(f, 0, NULL);
9949         }
9950     }
9951 }
9952
9953 char *
9954 SavePart(str)
9955      char *str;
9956 {
9957     static char buf[MSG_SIZ];
9958     char *p;
9959     
9960     p = strchr(str, ' ');
9961     if (p == NULL) return str;
9962     strncpy(buf, str, p - str);
9963     buf[p - str] = NULLCHAR;
9964     return buf;
9965 }
9966
9967 #define PGN_MAX_LINE 75
9968
9969 #define PGN_SIDE_WHITE  0
9970 #define PGN_SIDE_BLACK  1
9971
9972 /* [AS] */
9973 static int FindFirstMoveOutOfBook( int side )
9974 {
9975     int result = -1;
9976
9977     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9978         int index = backwardMostMove;
9979         int has_book_hit = 0;
9980
9981         if( (index % 2) != side ) {
9982             index++;
9983         }
9984
9985         while( index < forwardMostMove ) {
9986             /* Check to see if engine is in book */
9987             int depth = pvInfoList[index].depth;
9988             int score = pvInfoList[index].score;
9989             int in_book = 0;
9990
9991             if( depth <= 2 ) {
9992                 in_book = 1;
9993             }
9994             else if( score == 0 && depth == 63 ) {
9995                 in_book = 1; /* Zappa */
9996             }
9997             else if( score == 2 && depth == 99 ) {
9998                 in_book = 1; /* Abrok */
9999             }
10000
10001             has_book_hit += in_book;
10002
10003             if( ! in_book ) {
10004                 result = index;
10005
10006                 break;
10007             }
10008
10009             index += 2;
10010         }
10011     }
10012
10013     return result;
10014 }
10015
10016 /* [AS] */
10017 void GetOutOfBookInfo( char * buf )
10018 {
10019     int oob[2];
10020     int i;
10021     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10022
10023     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10024     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10025
10026     *buf = '\0';
10027
10028     if( oob[0] >= 0 || oob[1] >= 0 ) {
10029         for( i=0; i<2; i++ ) {
10030             int idx = oob[i];
10031
10032             if( idx >= 0 ) {
10033                 if( i > 0 && oob[0] >= 0 ) {
10034                     strcat( buf, "   " );
10035                 }
10036
10037                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10038                 sprintf( buf+strlen(buf), "%s%.2f", 
10039                     pvInfoList[idx].score >= 0 ? "+" : "",
10040                     pvInfoList[idx].score / 100.0 );
10041             }
10042         }
10043     }
10044 }
10045
10046 /* Save game in PGN style and close the file */
10047 int
10048 SaveGamePGN(f)
10049      FILE *f;
10050 {
10051     int i, offset, linelen, newblock;
10052     time_t tm;
10053 //    char *movetext;
10054     char numtext[32];
10055     int movelen, numlen, blank;
10056     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10057
10058     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10059     
10060     tm = time((time_t *) NULL);
10061     
10062     PrintPGNTags(f, &gameInfo);
10063     
10064     if (backwardMostMove > 0 || startedFromSetupPosition) {
10065         char *fen = PositionToFEN(backwardMostMove, NULL);
10066         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10067         fprintf(f, "\n{--------------\n");
10068         PrintPosition(f, backwardMostMove);
10069         fprintf(f, "--------------}\n");
10070         free(fen);
10071     }
10072     else {
10073         /* [AS] Out of book annotation */
10074         if( appData.saveOutOfBookInfo ) {
10075             char buf[64];
10076
10077             GetOutOfBookInfo( buf );
10078
10079             if( buf[0] != '\0' ) {
10080                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10081             }
10082         }
10083
10084         fprintf(f, "\n");
10085     }
10086
10087     i = backwardMostMove;
10088     linelen = 0;
10089     newblock = TRUE;
10090
10091     while (i < forwardMostMove) {
10092         /* Print comments preceding this move */
10093         if (commentList[i] != NULL) {
10094             if (linelen > 0) fprintf(f, "\n");
10095             fprintf(f, "%s", commentList[i]);
10096             linelen = 0;
10097             newblock = TRUE;
10098         }
10099
10100         /* Format move number */
10101         if ((i % 2) == 0) {
10102             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10103         } else {
10104             if (newblock) {
10105                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10106             } else {
10107                 numtext[0] = NULLCHAR;
10108             }
10109         }
10110         numlen = strlen(numtext);
10111         newblock = FALSE;
10112
10113         /* Print move number */
10114         blank = linelen > 0 && numlen > 0;
10115         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10116             fprintf(f, "\n");
10117             linelen = 0;
10118             blank = 0;
10119         }
10120         if (blank) {
10121             fprintf(f, " ");
10122             linelen++;
10123         }
10124         fprintf(f, "%s", numtext);
10125         linelen += numlen;
10126
10127         /* Get move */
10128         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10129         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10130
10131         /* Print move */
10132         blank = linelen > 0 && movelen > 0;
10133         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10134             fprintf(f, "\n");
10135             linelen = 0;
10136             blank = 0;
10137         }
10138         if (blank) {
10139             fprintf(f, " ");
10140             linelen++;
10141         }
10142         fprintf(f, "%s", move_buffer);
10143         linelen += movelen;
10144
10145         /* [AS] Add PV info if present */
10146         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10147             /* [HGM] add time */
10148             char buf[MSG_SIZ]; int seconds;
10149
10150             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10151
10152             if( seconds <= 0) buf[0] = 0; else
10153             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10154                 seconds = (seconds + 4)/10; // round to full seconds
10155                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10156                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10157             }
10158
10159             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10160                 pvInfoList[i].score >= 0 ? "+" : "",
10161                 pvInfoList[i].score / 100.0,
10162                 pvInfoList[i].depth,
10163                 buf );
10164
10165             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10166
10167             /* Print score/depth */
10168             blank = linelen > 0 && movelen > 0;
10169             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10170                 fprintf(f, "\n");
10171                 linelen = 0;
10172                 blank = 0;
10173             }
10174             if (blank) {
10175                 fprintf(f, " ");
10176                 linelen++;
10177             }
10178             fprintf(f, "%s", move_buffer);
10179             linelen += movelen;
10180         }
10181
10182         i++;
10183     }
10184     
10185     /* Start a new line */
10186     if (linelen > 0) fprintf(f, "\n");
10187
10188     /* Print comments after last move */
10189     if (commentList[i] != NULL) {
10190         fprintf(f, "%s\n", commentList[i]);
10191     }
10192
10193     /* Print result */
10194     if (gameInfo.resultDetails != NULL &&
10195         gameInfo.resultDetails[0] != NULLCHAR) {
10196         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10197                 PGNResult(gameInfo.result));
10198     } else {
10199         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10200     }
10201
10202     fclose(f);
10203     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10204     return TRUE;
10205 }
10206
10207 /* Save game in old style and close the file */
10208 int
10209 SaveGameOldStyle(f)
10210      FILE *f;
10211 {
10212     int i, offset;
10213     time_t tm;
10214     
10215     tm = time((time_t *) NULL);
10216     
10217     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10218     PrintOpponents(f);
10219     
10220     if (backwardMostMove > 0 || startedFromSetupPosition) {
10221         fprintf(f, "\n[--------------\n");
10222         PrintPosition(f, backwardMostMove);
10223         fprintf(f, "--------------]\n");
10224     } else {
10225         fprintf(f, "\n");
10226     }
10227
10228     i = backwardMostMove;
10229     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10230
10231     while (i < forwardMostMove) {
10232         if (commentList[i] != NULL) {
10233             fprintf(f, "[%s]\n", commentList[i]);
10234         }
10235
10236         if ((i % 2) == 1) {
10237             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10238             i++;
10239         } else {
10240             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10241             i++;
10242             if (commentList[i] != NULL) {
10243                 fprintf(f, "\n");
10244                 continue;
10245             }
10246             if (i >= forwardMostMove) {
10247                 fprintf(f, "\n");
10248                 break;
10249             }
10250             fprintf(f, "%s\n", parseList[i]);
10251             i++;
10252         }
10253     }
10254     
10255     if (commentList[i] != NULL) {
10256         fprintf(f, "[%s]\n", commentList[i]);
10257     }
10258
10259     /* This isn't really the old style, but it's close enough */
10260     if (gameInfo.resultDetails != NULL &&
10261         gameInfo.resultDetails[0] != NULLCHAR) {
10262         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10263                 gameInfo.resultDetails);
10264     } else {
10265         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10266     }
10267
10268     fclose(f);
10269     return TRUE;
10270 }
10271
10272 /* Save the current game to open file f and close the file */
10273 int
10274 SaveGame(f, dummy, dummy2)
10275      FILE *f;
10276      int dummy;
10277      char *dummy2;
10278 {
10279     if (gameMode == EditPosition) EditPositionDone(TRUE);
10280     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10281     if (appData.oldSaveStyle)
10282       return SaveGameOldStyle(f);
10283     else
10284       return SaveGamePGN(f);
10285 }
10286
10287 /* Save the current position to the given file */
10288 int
10289 SavePositionToFile(filename)
10290      char *filename;
10291 {
10292     FILE *f;
10293     char buf[MSG_SIZ];
10294
10295     if (strcmp(filename, "-") == 0) {
10296         return SavePosition(stdout, 0, NULL);
10297     } else {
10298         f = fopen(filename, "a");
10299         if (f == NULL) {
10300             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10301             DisplayError(buf, errno);
10302             return FALSE;
10303         } else {
10304             SavePosition(f, 0, NULL);
10305             return TRUE;
10306         }
10307     }
10308 }
10309
10310 /* Save the current position to the given open file and close the file */
10311 int
10312 SavePosition(f, dummy, dummy2)
10313      FILE *f;
10314      int dummy;
10315      char *dummy2;
10316 {
10317     time_t tm;
10318     char *fen;
10319     
10320     if (gameMode == EditPosition) EditPositionDone(TRUE);
10321     if (appData.oldSaveStyle) {
10322         tm = time((time_t *) NULL);
10323     
10324         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10325         PrintOpponents(f);
10326         fprintf(f, "[--------------\n");
10327         PrintPosition(f, currentMove);
10328         fprintf(f, "--------------]\n");
10329     } else {
10330         fen = PositionToFEN(currentMove, NULL);
10331         fprintf(f, "%s\n", fen);
10332         free(fen);
10333     }
10334     fclose(f);
10335     return TRUE;
10336 }
10337
10338 void
10339 ReloadCmailMsgEvent(unregister)
10340      int unregister;
10341 {
10342 #if !WIN32
10343     static char *inFilename = NULL;
10344     static char *outFilename;
10345     int i;
10346     struct stat inbuf, outbuf;
10347     int status;
10348     
10349     /* Any registered moves are unregistered if unregister is set, */
10350     /* i.e. invoked by the signal handler */
10351     if (unregister) {
10352         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10353             cmailMoveRegistered[i] = FALSE;
10354             if (cmailCommentList[i] != NULL) {
10355                 free(cmailCommentList[i]);
10356                 cmailCommentList[i] = NULL;
10357             }
10358         }
10359         nCmailMovesRegistered = 0;
10360     }
10361
10362     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10363         cmailResult[i] = CMAIL_NOT_RESULT;
10364     }
10365     nCmailResults = 0;
10366
10367     if (inFilename == NULL) {
10368         /* Because the filenames are static they only get malloced once  */
10369         /* and they never get freed                                      */
10370         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10371         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10372
10373         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10374         sprintf(outFilename, "%s.out", appData.cmailGameName);
10375     }
10376     
10377     status = stat(outFilename, &outbuf);
10378     if (status < 0) {
10379         cmailMailedMove = FALSE;
10380     } else {
10381         status = stat(inFilename, &inbuf);
10382         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10383     }
10384     
10385     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10386        counts the games, notes how each one terminated, etc.
10387        
10388        It would be nice to remove this kludge and instead gather all
10389        the information while building the game list.  (And to keep it
10390        in the game list nodes instead of having a bunch of fixed-size
10391        parallel arrays.)  Note this will require getting each game's
10392        termination from the PGN tags, as the game list builder does
10393        not process the game moves.  --mann
10394        */
10395     cmailMsgLoaded = TRUE;
10396     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10397     
10398     /* Load first game in the file or popup game menu */
10399     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10400
10401 #endif /* !WIN32 */
10402     return;
10403 }
10404
10405 int
10406 RegisterMove()
10407 {
10408     FILE *f;
10409     char string[MSG_SIZ];
10410
10411     if (   cmailMailedMove
10412         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10413         return TRUE;            /* Allow free viewing  */
10414     }
10415
10416     /* Unregister move to ensure that we don't leave RegisterMove        */
10417     /* with the move registered when the conditions for registering no   */
10418     /* longer hold                                                       */
10419     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10420         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10421         nCmailMovesRegistered --;
10422
10423         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10424           {
10425               free(cmailCommentList[lastLoadGameNumber - 1]);
10426               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10427           }
10428     }
10429
10430     if (cmailOldMove == -1) {
10431         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10432         return FALSE;
10433     }
10434
10435     if (currentMove > cmailOldMove + 1) {
10436         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10437         return FALSE;
10438     }
10439
10440     if (currentMove < cmailOldMove) {
10441         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10442         return FALSE;
10443     }
10444
10445     if (forwardMostMove > currentMove) {
10446         /* Silently truncate extra moves */
10447         TruncateGame();
10448     }
10449
10450     if (   (currentMove == cmailOldMove + 1)
10451         || (   (currentMove == cmailOldMove)
10452             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10453                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10454         if (gameInfo.result != GameUnfinished) {
10455             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10456         }
10457
10458         if (commentList[currentMove] != NULL) {
10459             cmailCommentList[lastLoadGameNumber - 1]
10460               = StrSave(commentList[currentMove]);
10461         }
10462         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10463
10464         if (appData.debugMode)
10465           fprintf(debugFP, "Saving %s for game %d\n",
10466                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10467
10468         sprintf(string,
10469                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10470         
10471         f = fopen(string, "w");
10472         if (appData.oldSaveStyle) {
10473             SaveGameOldStyle(f); /* also closes the file */
10474             
10475             sprintf(string, "%s.pos.out", appData.cmailGameName);
10476             f = fopen(string, "w");
10477             SavePosition(f, 0, NULL); /* also closes the file */
10478         } else {
10479             fprintf(f, "{--------------\n");
10480             PrintPosition(f, currentMove);
10481             fprintf(f, "--------------}\n\n");
10482             
10483             SaveGame(f, 0, NULL); /* also closes the file*/
10484         }
10485         
10486         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10487         nCmailMovesRegistered ++;
10488     } else if (nCmailGames == 1) {
10489         DisplayError(_("You have not made a move yet"), 0);
10490         return FALSE;
10491     }
10492
10493     return TRUE;
10494 }
10495
10496 void
10497 MailMoveEvent()
10498 {
10499 #if !WIN32
10500     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10501     FILE *commandOutput;
10502     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10503     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10504     int nBuffers;
10505     int i;
10506     int archived;
10507     char *arcDir;
10508
10509     if (! cmailMsgLoaded) {
10510         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10511         return;
10512     }
10513
10514     if (nCmailGames == nCmailResults) {
10515         DisplayError(_("No unfinished games"), 0);
10516         return;
10517     }
10518
10519 #if CMAIL_PROHIBIT_REMAIL
10520     if (cmailMailedMove) {
10521         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);
10522         DisplayError(msg, 0);
10523         return;
10524     }
10525 #endif
10526
10527     if (! (cmailMailedMove || RegisterMove())) return;
10528     
10529     if (   cmailMailedMove
10530         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10531         sprintf(string, partCommandString,
10532                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10533         commandOutput = popen(string, "r");
10534
10535         if (commandOutput == NULL) {
10536             DisplayError(_("Failed to invoke cmail"), 0);
10537         } else {
10538             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10539                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10540             }
10541             if (nBuffers > 1) {
10542                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10543                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10544                 nBytes = MSG_SIZ - 1;
10545             } else {
10546                 (void) memcpy(msg, buffer, nBytes);
10547             }
10548             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10549
10550             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10551                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10552
10553                 archived = TRUE;
10554                 for (i = 0; i < nCmailGames; i ++) {
10555                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10556                         archived = FALSE;
10557                     }
10558                 }
10559                 if (   archived
10560                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10561                         != NULL)) {
10562                     sprintf(buffer, "%s/%s.%s.archive",
10563                             arcDir,
10564                             appData.cmailGameName,
10565                             gameInfo.date);
10566                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10567                     cmailMsgLoaded = FALSE;
10568                 }
10569             }
10570
10571             DisplayInformation(msg);
10572             pclose(commandOutput);
10573         }
10574     } else {
10575         if ((*cmailMsg) != '\0') {
10576             DisplayInformation(cmailMsg);
10577         }
10578     }
10579
10580     return;
10581 #endif /* !WIN32 */
10582 }
10583
10584 char *
10585 CmailMsg()
10586 {
10587 #if WIN32
10588     return NULL;
10589 #else
10590     int  prependComma = 0;
10591     char number[5];
10592     char string[MSG_SIZ];       /* Space for game-list */
10593     int  i;
10594     
10595     if (!cmailMsgLoaded) return "";
10596
10597     if (cmailMailedMove) {
10598         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10599     } else {
10600         /* Create a list of games left */
10601         sprintf(string, "[");
10602         for (i = 0; i < nCmailGames; i ++) {
10603             if (! (   cmailMoveRegistered[i]
10604                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10605                 if (prependComma) {
10606                     sprintf(number, ",%d", i + 1);
10607                 } else {
10608                     sprintf(number, "%d", i + 1);
10609                     prependComma = 1;
10610                 }
10611                 
10612                 strcat(string, number);
10613             }
10614         }
10615         strcat(string, "]");
10616
10617         if (nCmailMovesRegistered + nCmailResults == 0) {
10618             switch (nCmailGames) {
10619               case 1:
10620                 sprintf(cmailMsg,
10621                         _("Still need to make move for game\n"));
10622                 break;
10623                 
10624               case 2:
10625                 sprintf(cmailMsg,
10626                         _("Still need to make moves for both games\n"));
10627                 break;
10628                 
10629               default:
10630                 sprintf(cmailMsg,
10631                         _("Still need to make moves for all %d games\n"),
10632                         nCmailGames);
10633                 break;
10634             }
10635         } else {
10636             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10637               case 1:
10638                 sprintf(cmailMsg,
10639                         _("Still need to make a move for game %s\n"),
10640                         string);
10641                 break;
10642                 
10643               case 0:
10644                 if (nCmailResults == nCmailGames) {
10645                     sprintf(cmailMsg, _("No unfinished games\n"));
10646                 } else {
10647                     sprintf(cmailMsg, _("Ready to send mail\n"));
10648                 }
10649                 break;
10650                 
10651               default:
10652                 sprintf(cmailMsg,
10653                         _("Still need to make moves for games %s\n"),
10654                         string);
10655             }
10656         }
10657     }
10658     return cmailMsg;
10659 #endif /* WIN32 */
10660 }
10661
10662 void
10663 ResetGameEvent()
10664 {
10665     if (gameMode == Training)
10666       SetTrainingModeOff();
10667
10668     Reset(TRUE, TRUE);
10669     cmailMsgLoaded = FALSE;
10670     if (appData.icsActive) {
10671       SendToICS(ics_prefix);
10672       SendToICS("refresh\n");
10673     }
10674 }
10675
10676 void
10677 ExitEvent(status)
10678      int status;
10679 {
10680     exiting++;
10681     if (exiting > 2) {
10682       /* Give up on clean exit */
10683       exit(status);
10684     }
10685     if (exiting > 1) {
10686       /* Keep trying for clean exit */
10687       return;
10688     }
10689
10690     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10691
10692     if (telnetISR != NULL) {
10693       RemoveInputSource(telnetISR);
10694     }
10695     if (icsPR != NoProc) {
10696       DestroyChildProcess(icsPR, TRUE);
10697     }
10698
10699     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10700     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10701
10702     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10703     /* make sure this other one finishes before killing it!                  */
10704     if(endingGame) { int count = 0;
10705         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10706         while(endingGame && count++ < 10) DoSleep(1);
10707         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10708     }
10709
10710     /* Kill off chess programs */
10711     if (first.pr != NoProc) {
10712         ExitAnalyzeMode();
10713         
10714         DoSleep( appData.delayBeforeQuit );
10715         SendToProgram("quit\n", &first);
10716         DoSleep( appData.delayAfterQuit );
10717         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10718     }
10719     if (second.pr != NoProc) {
10720         DoSleep( appData.delayBeforeQuit );
10721         SendToProgram("quit\n", &second);
10722         DoSleep( appData.delayAfterQuit );
10723         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10724     }
10725     if (first.isr != NULL) {
10726         RemoveInputSource(first.isr);
10727     }
10728     if (second.isr != NULL) {
10729         RemoveInputSource(second.isr);
10730     }
10731
10732     ShutDownFrontEnd();
10733     exit(status);
10734 }
10735
10736 void
10737 PauseEvent()
10738 {
10739     if (appData.debugMode)
10740         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10741     if (pausing) {
10742         pausing = FALSE;
10743         ModeHighlight();
10744         if (gameMode == MachinePlaysWhite ||
10745             gameMode == MachinePlaysBlack) {
10746             StartClocks();
10747         } else {
10748             DisplayBothClocks();
10749         }
10750         if (gameMode == PlayFromGameFile) {
10751             if (appData.timeDelay >= 0) 
10752                 AutoPlayGameLoop();
10753         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10754             Reset(FALSE, TRUE);
10755             SendToICS(ics_prefix);
10756             SendToICS("refresh\n");
10757         } else if (currentMove < forwardMostMove) {
10758             ForwardInner(forwardMostMove);
10759         }
10760         pauseExamInvalid = FALSE;
10761     } else {
10762         switch (gameMode) {
10763           default:
10764             return;
10765           case IcsExamining:
10766             pauseExamForwardMostMove = forwardMostMove;
10767             pauseExamInvalid = FALSE;
10768             /* fall through */
10769           case IcsObserving:
10770           case IcsPlayingWhite:
10771           case IcsPlayingBlack:
10772             pausing = TRUE;
10773             ModeHighlight();
10774             return;
10775           case PlayFromGameFile:
10776             (void) StopLoadGameTimer();
10777             pausing = TRUE;
10778             ModeHighlight();
10779             break;
10780           case BeginningOfGame:
10781             if (appData.icsActive) return;
10782             /* else fall through */
10783           case MachinePlaysWhite:
10784           case MachinePlaysBlack:
10785           case TwoMachinesPlay:
10786             if (forwardMostMove == 0)
10787               return;           /* don't pause if no one has moved */
10788             if ((gameMode == MachinePlaysWhite &&
10789                  !WhiteOnMove(forwardMostMove)) ||
10790                 (gameMode == MachinePlaysBlack &&
10791                  WhiteOnMove(forwardMostMove))) {
10792                 StopClocks();
10793             }
10794             pausing = TRUE;
10795             ModeHighlight();
10796             break;
10797         }
10798     }
10799 }
10800
10801 void
10802 EditCommentEvent()
10803 {
10804     char title[MSG_SIZ];
10805
10806     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10807         strcpy(title, _("Edit comment"));
10808     } else {
10809         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10810                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10811                 parseList[currentMove - 1]);
10812     }
10813
10814     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10815 }
10816
10817
10818 void
10819 EditTagsEvent()
10820 {
10821     char *tags = PGNTags(&gameInfo);
10822     EditTagsPopUp(tags);
10823     free(tags);
10824 }
10825
10826 void
10827 AnalyzeModeEvent()
10828 {
10829     if (appData.noChessProgram || gameMode == AnalyzeMode)
10830       return;
10831
10832     if (gameMode != AnalyzeFile) {
10833         if (!appData.icsEngineAnalyze) {
10834                EditGameEvent();
10835                if (gameMode != EditGame) return;
10836         }
10837         ResurrectChessProgram();
10838         SendToProgram("analyze\n", &first);
10839         first.analyzing = TRUE;
10840         /*first.maybeThinking = TRUE;*/
10841         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10842         EngineOutputPopUp();
10843     }
10844     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10845     pausing = FALSE;
10846     ModeHighlight();
10847     SetGameInfo();
10848
10849     StartAnalysisClock();
10850     GetTimeMark(&lastNodeCountTime);
10851     lastNodeCount = 0;
10852 }
10853
10854 void
10855 AnalyzeFileEvent()
10856 {
10857     if (appData.noChessProgram || gameMode == AnalyzeFile)
10858       return;
10859
10860     if (gameMode != AnalyzeMode) {
10861         EditGameEvent();
10862         if (gameMode != EditGame) return;
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     gameMode = AnalyzeFile;
10871     pausing = FALSE;
10872     ModeHighlight();
10873     SetGameInfo();
10874
10875     StartAnalysisClock();
10876     GetTimeMark(&lastNodeCountTime);
10877     lastNodeCount = 0;
10878 }
10879
10880 void
10881 MachineWhiteEvent()
10882 {
10883     char buf[MSG_SIZ];
10884     char *bookHit = NULL;
10885
10886     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10887       return;
10888
10889
10890     if (gameMode == PlayFromGameFile || 
10891         gameMode == TwoMachinesPlay  || 
10892         gameMode == Training         || 
10893         gameMode == AnalyzeMode      || 
10894         gameMode == EndOfGame)
10895         EditGameEvent();
10896
10897     if (gameMode == EditPosition) 
10898         EditPositionDone(TRUE);
10899
10900     if (!WhiteOnMove(currentMove)) {
10901         DisplayError(_("It is not White's turn"), 0);
10902         return;
10903     }
10904   
10905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10906       ExitAnalyzeMode();
10907
10908     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10909         gameMode == AnalyzeFile)
10910         TruncateGame();
10911
10912     ResurrectChessProgram();    /* in case it isn't running */
10913     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10914         gameMode = MachinePlaysWhite;
10915         ResetClocks();
10916     } else
10917     gameMode = MachinePlaysWhite;
10918     pausing = FALSE;
10919     ModeHighlight();
10920     SetGameInfo();
10921     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10922     DisplayTitle(buf);
10923     if (first.sendName) {
10924       sprintf(buf, "name %s\n", gameInfo.black);
10925       SendToProgram(buf, &first);
10926     }
10927     if (first.sendTime) {
10928       if (first.useColors) {
10929         SendToProgram("black\n", &first); /*gnu kludge*/
10930       }
10931       SendTimeRemaining(&first, TRUE);
10932     }
10933     if (first.useColors) {
10934       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10935     }
10936     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10937     SetMachineThinkingEnables();
10938     first.maybeThinking = TRUE;
10939     StartClocks();
10940     firstMove = FALSE;
10941
10942     if (appData.autoFlipView && !flipView) {
10943       flipView = !flipView;
10944       DrawPosition(FALSE, NULL);
10945       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10946     }
10947
10948     if(bookHit) { // [HGM] book: simulate book reply
10949         static char bookMove[MSG_SIZ]; // a bit generous?
10950
10951         programStats.nodes = programStats.depth = programStats.time = 
10952         programStats.score = programStats.got_only_move = 0;
10953         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10954
10955         strcpy(bookMove, "move ");
10956         strcat(bookMove, bookHit);
10957         HandleMachineMove(bookMove, &first);
10958     }
10959 }
10960
10961 void
10962 MachineBlackEvent()
10963 {
10964     char buf[MSG_SIZ];
10965    char *bookHit = NULL;
10966
10967     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10968         return;
10969
10970
10971     if (gameMode == PlayFromGameFile || 
10972         gameMode == TwoMachinesPlay  || 
10973         gameMode == Training         || 
10974         gameMode == AnalyzeMode      || 
10975         gameMode == EndOfGame)
10976         EditGameEvent();
10977
10978     if (gameMode == EditPosition) 
10979         EditPositionDone(TRUE);
10980
10981     if (WhiteOnMove(currentMove)) {
10982         DisplayError(_("It is not Black's turn"), 0);
10983         return;
10984     }
10985     
10986     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10987       ExitAnalyzeMode();
10988
10989     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10990         gameMode == AnalyzeFile)
10991         TruncateGame();
10992
10993     ResurrectChessProgram();    /* in case it isn't running */
10994     gameMode = MachinePlaysBlack;
10995     pausing = FALSE;
10996     ModeHighlight();
10997     SetGameInfo();
10998     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10999     DisplayTitle(buf);
11000     if (first.sendName) {
11001       sprintf(buf, "name %s\n", gameInfo.white);
11002       SendToProgram(buf, &first);
11003     }
11004     if (first.sendTime) {
11005       if (first.useColors) {
11006         SendToProgram("white\n", &first); /*gnu kludge*/
11007       }
11008       SendTimeRemaining(&first, FALSE);
11009     }
11010     if (first.useColors) {
11011       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11012     }
11013     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11014     SetMachineThinkingEnables();
11015     first.maybeThinking = TRUE;
11016     StartClocks();
11017
11018     if (appData.autoFlipView && flipView) {
11019       flipView = !flipView;
11020       DrawPosition(FALSE, NULL);
11021       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11022     }
11023     if(bookHit) { // [HGM] book: simulate book reply
11024         static char bookMove[MSG_SIZ]; // a bit generous?
11025
11026         programStats.nodes = programStats.depth = programStats.time = 
11027         programStats.score = programStats.got_only_move = 0;
11028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11029
11030         strcpy(bookMove, "move ");
11031         strcat(bookMove, bookHit);
11032         HandleMachineMove(bookMove, &first);
11033     }
11034 }
11035
11036
11037 void
11038 DisplayTwoMachinesTitle()
11039 {
11040     char buf[MSG_SIZ];
11041     if (appData.matchGames > 0) {
11042         if (first.twoMachinesColor[0] == 'w') {
11043             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11044                     gameInfo.white, gameInfo.black,
11045                     first.matchWins, second.matchWins,
11046                     matchGame - 1 - (first.matchWins + second.matchWins));
11047         } else {
11048             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11049                     gameInfo.white, gameInfo.black,
11050                     second.matchWins, first.matchWins,
11051                     matchGame - 1 - (first.matchWins + second.matchWins));
11052         }
11053     } else {
11054         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11055     }
11056     DisplayTitle(buf);
11057 }
11058
11059 void
11060 TwoMachinesEvent P((void))
11061 {
11062     int i;
11063     char buf[MSG_SIZ];
11064     ChessProgramState *onmove;
11065     char *bookHit = NULL;
11066     
11067     if (appData.noChessProgram) return;
11068
11069     switch (gameMode) {
11070       case TwoMachinesPlay:
11071         return;
11072       case MachinePlaysWhite:
11073       case MachinePlaysBlack:
11074         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11075             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11076             return;
11077         }
11078         /* fall through */
11079       case BeginningOfGame:
11080       case PlayFromGameFile:
11081       case EndOfGame:
11082         EditGameEvent();
11083         if (gameMode != EditGame) return;
11084         break;
11085       case EditPosition:
11086         EditPositionDone(TRUE);
11087         break;
11088       case AnalyzeMode:
11089       case AnalyzeFile:
11090         ExitAnalyzeMode();
11091         break;
11092       case EditGame:
11093       default:
11094         break;
11095     }
11096
11097 //    forwardMostMove = currentMove;
11098     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11099     ResurrectChessProgram();    /* in case first program isn't running */
11100
11101     if (second.pr == NULL) {
11102         StartChessProgram(&second);
11103         if (second.protocolVersion == 1) {
11104           TwoMachinesEventIfReady();
11105         } else {
11106           /* kludge: allow timeout for initial "feature" command */
11107           FreezeUI();
11108           DisplayMessage("", _("Starting second chess program"));
11109           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11110         }
11111         return;
11112     }
11113     DisplayMessage("", "");
11114     InitChessProgram(&second, FALSE);
11115     SendToProgram("force\n", &second);
11116     if (startedFromSetupPosition) {
11117         SendBoard(&second, backwardMostMove);
11118     if (appData.debugMode) {
11119         fprintf(debugFP, "Two Machines\n");
11120     }
11121     }
11122     for (i = backwardMostMove; i < forwardMostMove; i++) {
11123         SendMoveToProgram(i, &second);
11124     }
11125
11126     gameMode = TwoMachinesPlay;
11127     pausing = FALSE;
11128     ModeHighlight();
11129     SetGameInfo();
11130     DisplayTwoMachinesTitle();
11131     firstMove = TRUE;
11132     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11133         onmove = &first;
11134     } else {
11135         onmove = &second;
11136     }
11137
11138     SendToProgram(first.computerString, &first);
11139     if (first.sendName) {
11140       sprintf(buf, "name %s\n", second.tidy);
11141       SendToProgram(buf, &first);
11142     }
11143     SendToProgram(second.computerString, &second);
11144     if (second.sendName) {
11145       sprintf(buf, "name %s\n", first.tidy);
11146       SendToProgram(buf, &second);
11147     }
11148
11149     ResetClocks();
11150     if (!first.sendTime || !second.sendTime) {
11151         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11152         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11153     }
11154     if (onmove->sendTime) {
11155       if (onmove->useColors) {
11156         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11157       }
11158       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11159     }
11160     if (onmove->useColors) {
11161       SendToProgram(onmove->twoMachinesColor, onmove);
11162     }
11163     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11164 //    SendToProgram("go\n", onmove);
11165     onmove->maybeThinking = TRUE;
11166     SetMachineThinkingEnables();
11167
11168     StartClocks();
11169
11170     if(bookHit) { // [HGM] book: simulate book reply
11171         static char bookMove[MSG_SIZ]; // a bit generous?
11172
11173         programStats.nodes = programStats.depth = programStats.time = 
11174         programStats.score = programStats.got_only_move = 0;
11175         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11176
11177         strcpy(bookMove, "move ");
11178         strcat(bookMove, bookHit);
11179         savedMessage = bookMove; // args for deferred call
11180         savedState = onmove;
11181         ScheduleDelayedEvent(DeferredBookMove, 1);
11182     }
11183 }
11184
11185 void
11186 TrainingEvent()
11187 {
11188     if (gameMode == Training) {
11189       SetTrainingModeOff();
11190       gameMode = PlayFromGameFile;
11191       DisplayMessage("", _("Training mode off"));
11192     } else {
11193       gameMode = Training;
11194       animateTraining = appData.animate;
11195
11196       /* make sure we are not already at the end of the game */
11197       if (currentMove < forwardMostMove) {
11198         SetTrainingModeOn();
11199         DisplayMessage("", _("Training mode on"));
11200       } else {
11201         gameMode = PlayFromGameFile;
11202         DisplayError(_("Already at end of game"), 0);
11203       }
11204     }
11205     ModeHighlight();
11206 }
11207
11208 void
11209 IcsClientEvent()
11210 {
11211     if (!appData.icsActive) return;
11212     switch (gameMode) {
11213       case IcsPlayingWhite:
11214       case IcsPlayingBlack:
11215       case IcsObserving:
11216       case IcsIdle:
11217       case BeginningOfGame:
11218       case IcsExamining:
11219         return;
11220
11221       case EditGame:
11222         break;
11223
11224       case EditPosition:
11225         EditPositionDone(TRUE);
11226         break;
11227
11228       case AnalyzeMode:
11229       case AnalyzeFile:
11230         ExitAnalyzeMode();
11231         break;
11232         
11233       default:
11234         EditGameEvent();
11235         break;
11236     }
11237
11238     gameMode = IcsIdle;
11239     ModeHighlight();
11240     return;
11241 }
11242
11243
11244 void
11245 EditGameEvent()
11246 {
11247     int i;
11248
11249     switch (gameMode) {
11250       case Training:
11251         SetTrainingModeOff();
11252         break;
11253       case MachinePlaysWhite:
11254       case MachinePlaysBlack:
11255       case BeginningOfGame:
11256         SendToProgram("force\n", &first);
11257         SetUserThinkingEnables();
11258         break;
11259       case PlayFromGameFile:
11260         (void) StopLoadGameTimer();
11261         if (gameFileFP != NULL) {
11262             gameFileFP = NULL;
11263         }
11264         break;
11265       case EditPosition:
11266         EditPositionDone(TRUE);
11267         break;
11268       case AnalyzeMode:
11269       case AnalyzeFile:
11270         ExitAnalyzeMode();
11271         SendToProgram("force\n", &first);
11272         break;
11273       case TwoMachinesPlay:
11274         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11275         ResurrectChessProgram();
11276         SetUserThinkingEnables();
11277         break;
11278       case EndOfGame:
11279         ResurrectChessProgram();
11280         break;
11281       case IcsPlayingBlack:
11282       case IcsPlayingWhite:
11283         DisplayError(_("Warning: You are still playing a game"), 0);
11284         break;
11285       case IcsObserving:
11286         DisplayError(_("Warning: You are still observing a game"), 0);
11287         break;
11288       case IcsExamining:
11289         DisplayError(_("Warning: You are still examining a game"), 0);
11290         break;
11291       case IcsIdle:
11292         break;
11293       case EditGame:
11294       default:
11295         return;
11296     }
11297     
11298     pausing = FALSE;
11299     StopClocks();
11300     first.offeredDraw = second.offeredDraw = 0;
11301
11302     if (gameMode == PlayFromGameFile) {
11303         whiteTimeRemaining = timeRemaining[0][currentMove];
11304         blackTimeRemaining = timeRemaining[1][currentMove];
11305         DisplayTitle("");
11306     }
11307
11308     if (gameMode == MachinePlaysWhite ||
11309         gameMode == MachinePlaysBlack ||
11310         gameMode == TwoMachinesPlay ||
11311         gameMode == EndOfGame) {
11312         i = forwardMostMove;
11313         while (i > currentMove) {
11314             SendToProgram("undo\n", &first);
11315             i--;
11316         }
11317         whiteTimeRemaining = timeRemaining[0][currentMove];
11318         blackTimeRemaining = timeRemaining[1][currentMove];
11319         DisplayBothClocks();
11320         if (whiteFlag || blackFlag) {
11321             whiteFlag = blackFlag = 0;
11322         }
11323         DisplayTitle("");
11324     }           
11325     
11326     gameMode = EditGame;
11327     ModeHighlight();
11328     SetGameInfo();
11329 }
11330
11331
11332 void
11333 EditPositionEvent()
11334 {
11335     if (gameMode == EditPosition) {
11336         EditGameEvent();
11337         return;
11338     }
11339     
11340     EditGameEvent();
11341     if (gameMode != EditGame) return;
11342     
11343     gameMode = EditPosition;
11344     ModeHighlight();
11345     SetGameInfo();
11346     if (currentMove > 0)
11347       CopyBoard(boards[0], boards[currentMove]);
11348     
11349     blackPlaysFirst = !WhiteOnMove(currentMove);
11350     ResetClocks();
11351     currentMove = forwardMostMove = backwardMostMove = 0;
11352     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11353     DisplayMove(-1);
11354 }
11355
11356 void
11357 ExitAnalyzeMode()
11358 {
11359     /* [DM] icsEngineAnalyze - possible call from other functions */
11360     if (appData.icsEngineAnalyze) {
11361         appData.icsEngineAnalyze = FALSE;
11362
11363         DisplayMessage("",_("Close ICS engine analyze..."));
11364     }
11365     if (first.analysisSupport && first.analyzing) {
11366       SendToProgram("exit\n", &first);
11367       first.analyzing = FALSE;
11368     }
11369     thinkOutput[0] = NULLCHAR;
11370 }
11371
11372 void
11373 EditPositionDone(Boolean fakeRights)
11374 {
11375     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11376
11377     startedFromSetupPosition = TRUE;
11378     InitChessProgram(&first, FALSE);
11379     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11380       boards[0][EP_STATUS] = EP_NONE;
11381       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11382     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11383         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11384         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11385       } else boards[0][CASTLING][2] = NoRights;
11386     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11387         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11388         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11389       } else boards[0][CASTLING][5] = NoRights;
11390     }
11391     SendToProgram("force\n", &first);
11392     if (blackPlaysFirst) {
11393         strcpy(moveList[0], "");
11394         strcpy(parseList[0], "");
11395         currentMove = forwardMostMove = backwardMostMove = 1;
11396         CopyBoard(boards[1], boards[0]);
11397     } else {
11398         currentMove = forwardMostMove = backwardMostMove = 0;
11399     }
11400     SendBoard(&first, forwardMostMove);
11401     if (appData.debugMode) {
11402         fprintf(debugFP, "EditPosDone\n");
11403     }
11404     DisplayTitle("");
11405     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11406     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11407     gameMode = EditGame;
11408     ModeHighlight();
11409     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11410     ClearHighlights(); /* [AS] */
11411 }
11412
11413 /* Pause for `ms' milliseconds */
11414 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11415 void
11416 TimeDelay(ms)
11417      long ms;
11418 {
11419     TimeMark m1, m2;
11420
11421     GetTimeMark(&m1);
11422     do {
11423         GetTimeMark(&m2);
11424     } while (SubtractTimeMarks(&m2, &m1) < ms);
11425 }
11426
11427 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11428 void
11429 SendMultiLineToICS(buf)
11430      char *buf;
11431 {
11432     char temp[MSG_SIZ+1], *p;
11433     int len;
11434
11435     len = strlen(buf);
11436     if (len > MSG_SIZ)
11437       len = MSG_SIZ;
11438   
11439     strncpy(temp, buf, len);
11440     temp[len] = 0;
11441
11442     p = temp;
11443     while (*p) {
11444         if (*p == '\n' || *p == '\r')
11445           *p = ' ';
11446         ++p;
11447     }
11448
11449     strcat(temp, "\n");
11450     SendToICS(temp);
11451     SendToPlayer(temp, strlen(temp));
11452 }
11453
11454 void
11455 SetWhiteToPlayEvent()
11456 {
11457     if (gameMode == EditPosition) {
11458         blackPlaysFirst = FALSE;
11459         DisplayBothClocks();    /* works because currentMove is 0 */
11460     } else if (gameMode == IcsExamining) {
11461         SendToICS(ics_prefix);
11462         SendToICS("tomove white\n");
11463     }
11464 }
11465
11466 void
11467 SetBlackToPlayEvent()
11468 {
11469     if (gameMode == EditPosition) {
11470         blackPlaysFirst = TRUE;
11471         currentMove = 1;        /* kludge */
11472         DisplayBothClocks();
11473         currentMove = 0;
11474     } else if (gameMode == IcsExamining) {
11475         SendToICS(ics_prefix);
11476         SendToICS("tomove black\n");
11477     }
11478 }
11479
11480 void
11481 EditPositionMenuEvent(selection, x, y)
11482      ChessSquare selection;
11483      int x, y;
11484 {
11485     char buf[MSG_SIZ];
11486     ChessSquare piece = boards[0][y][x];
11487
11488     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11489
11490     switch (selection) {
11491       case ClearBoard:
11492         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11493             SendToICS(ics_prefix);
11494             SendToICS("bsetup clear\n");
11495         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11496             SendToICS(ics_prefix);
11497             SendToICS("clearboard\n");
11498         } else {
11499             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11500                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11501                 for (y = 0; y < BOARD_HEIGHT; y++) {
11502                     if (gameMode == IcsExamining) {
11503                         if (boards[currentMove][y][x] != EmptySquare) {
11504                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11505                                     AAA + x, ONE + y);
11506                             SendToICS(buf);
11507                         }
11508                     } else {
11509                         boards[0][y][x] = p;
11510                     }
11511                 }
11512             }
11513         }
11514         if (gameMode == EditPosition) {
11515             DrawPosition(FALSE, boards[0]);
11516         }
11517         break;
11518
11519       case WhitePlay:
11520         SetWhiteToPlayEvent();
11521         break;
11522
11523       case BlackPlay:
11524         SetBlackToPlayEvent();
11525         break;
11526
11527       case EmptySquare:
11528         if (gameMode == IcsExamining) {
11529             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11530             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11531             SendToICS(buf);
11532         } else {
11533             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11534                 if(x == BOARD_LEFT-2) {
11535                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11536                     boards[0][y][1] = 0;
11537                 } else
11538                 if(x == BOARD_RGHT+1) {
11539                     if(y >= gameInfo.holdingsSize) break;
11540                     boards[0][y][BOARD_WIDTH-2] = 0;
11541                 } else break;
11542             }
11543             boards[0][y][x] = EmptySquare;
11544             DrawPosition(FALSE, boards[0]);
11545         }
11546         break;
11547
11548       case PromotePiece:
11549         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11550            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11551             selection = (ChessSquare) (PROMOTED piece);
11552         } else if(piece == EmptySquare) selection = WhiteSilver;
11553         else selection = (ChessSquare)((int)piece - 1);
11554         goto defaultlabel;
11555
11556       case DemotePiece:
11557         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11558            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11559             selection = (ChessSquare) (DEMOTED piece);
11560         } else if(piece == EmptySquare) selection = BlackSilver;
11561         else selection = (ChessSquare)((int)piece + 1);       
11562         goto defaultlabel;
11563
11564       case WhiteQueen:
11565       case BlackQueen:
11566         if(gameInfo.variant == VariantShatranj ||
11567            gameInfo.variant == VariantXiangqi  ||
11568            gameInfo.variant == VariantCourier  ||
11569            gameInfo.variant == VariantMakruk     )
11570             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11571         goto defaultlabel;
11572
11573       case WhiteKing:
11574       case BlackKing:
11575         if(gameInfo.variant == VariantXiangqi)
11576             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11577         if(gameInfo.variant == VariantKnightmate)
11578             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11579       default:
11580         defaultlabel:
11581         if (gameMode == IcsExamining) {
11582             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11583             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11584                     PieceToChar(selection), AAA + x, ONE + y);
11585             SendToICS(buf);
11586         } else {
11587             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11588                 int n;
11589                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11590                     n = PieceToNumber(selection - BlackPawn);
11591                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11592                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11593                     boards[0][BOARD_HEIGHT-1-n][1]++;
11594                 } else
11595                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11596                     n = PieceToNumber(selection);
11597                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11598                     boards[0][n][BOARD_WIDTH-1] = selection;
11599                     boards[0][n][BOARD_WIDTH-2]++;
11600                 }
11601             } else
11602             boards[0][y][x] = selection;
11603             DrawPosition(TRUE, boards[0]);
11604         }
11605         break;
11606     }
11607 }
11608
11609
11610 void
11611 DropMenuEvent(selection, x, y)
11612      ChessSquare selection;
11613      int x, y;
11614 {
11615     ChessMove moveType;
11616
11617     switch (gameMode) {
11618       case IcsPlayingWhite:
11619       case MachinePlaysBlack:
11620         if (!WhiteOnMove(currentMove)) {
11621             DisplayMoveError(_("It is Black's turn"));
11622             return;
11623         }
11624         moveType = WhiteDrop;
11625         break;
11626       case IcsPlayingBlack:
11627       case MachinePlaysWhite:
11628         if (WhiteOnMove(currentMove)) {
11629             DisplayMoveError(_("It is White's turn"));
11630             return;
11631         }
11632         moveType = BlackDrop;
11633         break;
11634       case EditGame:
11635         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11636         break;
11637       default:
11638         return;
11639     }
11640
11641     if (moveType == BlackDrop && selection < BlackPawn) {
11642       selection = (ChessSquare) ((int) selection
11643                                  + (int) BlackPawn - (int) WhitePawn);
11644     }
11645     if (boards[currentMove][y][x] != EmptySquare) {
11646         DisplayMoveError(_("That square is occupied"));
11647         return;
11648     }
11649
11650     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11651 }
11652
11653 void
11654 AcceptEvent()
11655 {
11656     /* Accept a pending offer of any kind from opponent */
11657     
11658     if (appData.icsActive) {
11659         SendToICS(ics_prefix);
11660         SendToICS("accept\n");
11661     } else if (cmailMsgLoaded) {
11662         if (currentMove == cmailOldMove &&
11663             commentList[cmailOldMove] != NULL &&
11664             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11665                    "Black offers a draw" : "White offers a draw")) {
11666             TruncateGame();
11667             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11668             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11669         } else {
11670             DisplayError(_("There is no pending offer on this move"), 0);
11671             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11672         }
11673     } else {
11674         /* Not used for offers from chess program */
11675     }
11676 }
11677
11678 void
11679 DeclineEvent()
11680 {
11681     /* Decline a pending offer of any kind from opponent */
11682     
11683     if (appData.icsActive) {
11684         SendToICS(ics_prefix);
11685         SendToICS("decline\n");
11686     } else if (cmailMsgLoaded) {
11687         if (currentMove == cmailOldMove &&
11688             commentList[cmailOldMove] != NULL &&
11689             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11690                    "Black offers a draw" : "White offers a draw")) {
11691 #ifdef NOTDEF
11692             AppendComment(cmailOldMove, "Draw declined", TRUE);
11693             DisplayComment(cmailOldMove - 1, "Draw declined");
11694 #endif /*NOTDEF*/
11695         } else {
11696             DisplayError(_("There is no pending offer on this move"), 0);
11697         }
11698     } else {
11699         /* Not used for offers from chess program */
11700     }
11701 }
11702
11703 void
11704 RematchEvent()
11705 {
11706     /* Issue ICS rematch command */
11707     if (appData.icsActive) {
11708         SendToICS(ics_prefix);
11709         SendToICS("rematch\n");
11710     }
11711 }
11712
11713 void
11714 CallFlagEvent()
11715 {
11716     /* Call your opponent's flag (claim a win on time) */
11717     if (appData.icsActive) {
11718         SendToICS(ics_prefix);
11719         SendToICS("flag\n");
11720     } else {
11721         switch (gameMode) {
11722           default:
11723             return;
11724           case MachinePlaysWhite:
11725             if (whiteFlag) {
11726                 if (blackFlag)
11727                   GameEnds(GameIsDrawn, "Both players ran out of time",
11728                            GE_PLAYER);
11729                 else
11730                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11731             } else {
11732                 DisplayError(_("Your opponent is not out of time"), 0);
11733             }
11734             break;
11735           case MachinePlaysBlack:
11736             if (blackFlag) {
11737                 if (whiteFlag)
11738                   GameEnds(GameIsDrawn, "Both players ran out of time",
11739                            GE_PLAYER);
11740                 else
11741                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11742             } else {
11743                 DisplayError(_("Your opponent is not out of time"), 0);
11744             }
11745             break;
11746         }
11747     }
11748 }
11749
11750 void
11751 DrawEvent()
11752 {
11753     /* Offer draw or accept pending draw offer from opponent */
11754     
11755     if (appData.icsActive) {
11756         /* Note: tournament rules require draw offers to be
11757            made after you make your move but before you punch
11758            your clock.  Currently ICS doesn't let you do that;
11759            instead, you immediately punch your clock after making
11760            a move, but you can offer a draw at any time. */
11761         
11762         SendToICS(ics_prefix);
11763         SendToICS("draw\n");
11764     } else if (cmailMsgLoaded) {
11765         if (currentMove == cmailOldMove &&
11766             commentList[cmailOldMove] != NULL &&
11767             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11768                    "Black offers a draw" : "White offers a draw")) {
11769             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11770             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11771         } else if (currentMove == cmailOldMove + 1) {
11772             char *offer = WhiteOnMove(cmailOldMove) ?
11773               "White offers a draw" : "Black offers a draw";
11774             AppendComment(currentMove, offer, TRUE);
11775             DisplayComment(currentMove - 1, offer);
11776             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11777         } else {
11778             DisplayError(_("You must make your move before offering a draw"), 0);
11779             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11780         }
11781     } else if (first.offeredDraw) {
11782         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11783     } else {
11784         if (first.sendDrawOffers) {
11785             SendToProgram("draw\n", &first);
11786             userOfferedDraw = TRUE;
11787         }
11788     }
11789 }
11790
11791 void
11792 AdjournEvent()
11793 {
11794     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11795     
11796     if (appData.icsActive) {
11797         SendToICS(ics_prefix);
11798         SendToICS("adjourn\n");
11799     } else {
11800         /* Currently GNU Chess doesn't offer or accept Adjourns */
11801     }
11802 }
11803
11804
11805 void
11806 AbortEvent()
11807 {
11808     /* Offer Abort or accept pending Abort offer from opponent */
11809     
11810     if (appData.icsActive) {
11811         SendToICS(ics_prefix);
11812         SendToICS("abort\n");
11813     } else {
11814         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11815     }
11816 }
11817
11818 void
11819 ResignEvent()
11820 {
11821     /* Resign.  You can do this even if it's not your turn. */
11822     
11823     if (appData.icsActive) {
11824         SendToICS(ics_prefix);
11825         SendToICS("resign\n");
11826     } else {
11827         switch (gameMode) {
11828           case MachinePlaysWhite:
11829             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11830             break;
11831           case MachinePlaysBlack:
11832             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11833             break;
11834           case EditGame:
11835             if (cmailMsgLoaded) {
11836                 TruncateGame();
11837                 if (WhiteOnMove(cmailOldMove)) {
11838                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11839                 } else {
11840                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11841                 }
11842                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11843             }
11844             break;
11845           default:
11846             break;
11847         }
11848     }
11849 }
11850
11851
11852 void
11853 StopObservingEvent()
11854 {
11855     /* Stop observing current games */
11856     SendToICS(ics_prefix);
11857     SendToICS("unobserve\n");
11858 }
11859
11860 void
11861 StopExaminingEvent()
11862 {
11863     /* Stop observing current game */
11864     SendToICS(ics_prefix);
11865     SendToICS("unexamine\n");
11866 }
11867
11868 void
11869 ForwardInner(target)
11870      int target;
11871 {
11872     int limit;
11873
11874     if (appData.debugMode)
11875         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11876                 target, currentMove, forwardMostMove);
11877
11878     if (gameMode == EditPosition)
11879       return;
11880
11881     if (gameMode == PlayFromGameFile && !pausing)
11882       PauseEvent();
11883     
11884     if (gameMode == IcsExamining && pausing)
11885       limit = pauseExamForwardMostMove;
11886     else
11887       limit = forwardMostMove;
11888     
11889     if (target > limit) target = limit;
11890
11891     if (target > 0 && moveList[target - 1][0]) {
11892         int fromX, fromY, toX, toY;
11893         toX = moveList[target - 1][2] - AAA;
11894         toY = moveList[target - 1][3] - ONE;
11895         if (moveList[target - 1][1] == '@') {
11896             if (appData.highlightLastMove) {
11897                 SetHighlights(-1, -1, toX, toY);
11898             }
11899         } else {
11900             fromX = moveList[target - 1][0] - AAA;
11901             fromY = moveList[target - 1][1] - ONE;
11902             if (target == currentMove + 1) {
11903                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11904             }
11905             if (appData.highlightLastMove) {
11906                 SetHighlights(fromX, fromY, toX, toY);
11907             }
11908         }
11909     }
11910     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11911         gameMode == Training || gameMode == PlayFromGameFile || 
11912         gameMode == AnalyzeFile) {
11913         while (currentMove < target) {
11914             SendMoveToProgram(currentMove++, &first);
11915         }
11916     } else {
11917         currentMove = target;
11918     }
11919     
11920     if (gameMode == EditGame || gameMode == EndOfGame) {
11921         whiteTimeRemaining = timeRemaining[0][currentMove];
11922         blackTimeRemaining = timeRemaining[1][currentMove];
11923     }
11924     DisplayBothClocks();
11925     DisplayMove(currentMove - 1);
11926     DrawPosition(FALSE, boards[currentMove]);
11927     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11928     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11929         DisplayComment(currentMove - 1, commentList[currentMove]);
11930     }
11931 }
11932
11933
11934 void
11935 ForwardEvent()
11936 {
11937     if (gameMode == IcsExamining && !pausing) {
11938         SendToICS(ics_prefix);
11939         SendToICS("forward\n");
11940     } else {
11941         ForwardInner(currentMove + 1);
11942     }
11943 }
11944
11945 void
11946 ToEndEvent()
11947 {
11948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11949         /* to optimze, we temporarily turn off analysis mode while we feed
11950          * the remaining moves to the engine. Otherwise we get analysis output
11951          * after each move.
11952          */ 
11953         if (first.analysisSupport) {
11954           SendToProgram("exit\nforce\n", &first);
11955           first.analyzing = FALSE;
11956         }
11957     }
11958         
11959     if (gameMode == IcsExamining && !pausing) {
11960         SendToICS(ics_prefix);
11961         SendToICS("forward 999999\n");
11962     } else {
11963         ForwardInner(forwardMostMove);
11964     }
11965
11966     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11967         /* we have fed all the moves, so reactivate analysis mode */
11968         SendToProgram("analyze\n", &first);
11969         first.analyzing = TRUE;
11970         /*first.maybeThinking = TRUE;*/
11971         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11972     }
11973 }
11974
11975 void
11976 BackwardInner(target)
11977      int target;
11978 {
11979     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11980
11981     if (appData.debugMode)
11982         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11983                 target, currentMove, forwardMostMove);
11984
11985     if (gameMode == EditPosition) return;
11986     if (currentMove <= backwardMostMove) {
11987         ClearHighlights();
11988         DrawPosition(full_redraw, boards[currentMove]);
11989         return;
11990     }
11991     if (gameMode == PlayFromGameFile && !pausing)
11992       PauseEvent();
11993     
11994     if (moveList[target][0]) {
11995         int fromX, fromY, toX, toY;
11996         toX = moveList[target][2] - AAA;
11997         toY = moveList[target][3] - ONE;
11998         if (moveList[target][1] == '@') {
11999             if (appData.highlightLastMove) {
12000                 SetHighlights(-1, -1, toX, toY);
12001             }
12002         } else {
12003             fromX = moveList[target][0] - AAA;
12004             fromY = moveList[target][1] - ONE;
12005             if (target == currentMove - 1) {
12006                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12007             }
12008             if (appData.highlightLastMove) {
12009                 SetHighlights(fromX, fromY, toX, toY);
12010             }
12011         }
12012     }
12013     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12014         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12015         while (currentMove > target) {
12016             SendToProgram("undo\n", &first);
12017             currentMove--;
12018         }
12019     } else {
12020         currentMove = target;
12021     }
12022     
12023     if (gameMode == EditGame || gameMode == EndOfGame) {
12024         whiteTimeRemaining = timeRemaining[0][currentMove];
12025         blackTimeRemaining = timeRemaining[1][currentMove];
12026     }
12027     DisplayBothClocks();
12028     DisplayMove(currentMove - 1);
12029     DrawPosition(full_redraw, boards[currentMove]);
12030     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12031     // [HGM] PV info: routine tests if comment empty
12032     DisplayComment(currentMove - 1, commentList[currentMove]);
12033 }
12034
12035 void
12036 BackwardEvent()
12037 {
12038     if (gameMode == IcsExamining && !pausing) {
12039         SendToICS(ics_prefix);
12040         SendToICS("backward\n");
12041     } else {
12042         BackwardInner(currentMove - 1);
12043     }
12044 }
12045
12046 void
12047 ToStartEvent()
12048 {
12049     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12050         /* to optimize, we temporarily turn off analysis mode while we undo
12051          * all the moves. Otherwise we get analysis output after each undo.
12052          */ 
12053         if (first.analysisSupport) {
12054           SendToProgram("exit\nforce\n", &first);
12055           first.analyzing = FALSE;
12056         }
12057     }
12058
12059     if (gameMode == IcsExamining && !pausing) {
12060         SendToICS(ics_prefix);
12061         SendToICS("backward 999999\n");
12062     } else {
12063         BackwardInner(backwardMostMove);
12064     }
12065
12066     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12067         /* we have fed all the moves, so reactivate analysis mode */
12068         SendToProgram("analyze\n", &first);
12069         first.analyzing = TRUE;
12070         /*first.maybeThinking = TRUE;*/
12071         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12072     }
12073 }
12074
12075 void
12076 ToNrEvent(int to)
12077 {
12078   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12079   if (to >= forwardMostMove) to = forwardMostMove;
12080   if (to <= backwardMostMove) to = backwardMostMove;
12081   if (to < currentMove) {
12082     BackwardInner(to);
12083   } else {
12084     ForwardInner(to);
12085   }
12086 }
12087
12088 void
12089 RevertEvent()
12090 {
12091     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12092         return;
12093     }
12094     if (gameMode != IcsExamining) {
12095         DisplayError(_("You are not examining a game"), 0);
12096         return;
12097     }
12098     if (pausing) {
12099         DisplayError(_("You can't revert while pausing"), 0);
12100         return;
12101     }
12102     SendToICS(ics_prefix);
12103     SendToICS("revert\n");
12104 }
12105
12106 void
12107 RetractMoveEvent()
12108 {
12109     switch (gameMode) {
12110       case MachinePlaysWhite:
12111       case MachinePlaysBlack:
12112         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12113             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12114             return;
12115         }
12116         if (forwardMostMove < 2) return;
12117         currentMove = forwardMostMove = forwardMostMove - 2;
12118         whiteTimeRemaining = timeRemaining[0][currentMove];
12119         blackTimeRemaining = timeRemaining[1][currentMove];
12120         DisplayBothClocks();
12121         DisplayMove(currentMove - 1);
12122         ClearHighlights();/*!! could figure this out*/
12123         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12124         SendToProgram("remove\n", &first);
12125         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12126         break;
12127
12128       case BeginningOfGame:
12129       default:
12130         break;
12131
12132       case IcsPlayingWhite:
12133       case IcsPlayingBlack:
12134         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12135             SendToICS(ics_prefix);
12136             SendToICS("takeback 2\n");
12137         } else {
12138             SendToICS(ics_prefix);
12139             SendToICS("takeback 1\n");
12140         }
12141         break;
12142     }
12143 }
12144
12145 void
12146 MoveNowEvent()
12147 {
12148     ChessProgramState *cps;
12149
12150     switch (gameMode) {
12151       case MachinePlaysWhite:
12152         if (!WhiteOnMove(forwardMostMove)) {
12153             DisplayError(_("It is your turn"), 0);
12154             return;
12155         }
12156         cps = &first;
12157         break;
12158       case MachinePlaysBlack:
12159         if (WhiteOnMove(forwardMostMove)) {
12160             DisplayError(_("It is your turn"), 0);
12161             return;
12162         }
12163         cps = &first;
12164         break;
12165       case TwoMachinesPlay:
12166         if (WhiteOnMove(forwardMostMove) ==
12167             (first.twoMachinesColor[0] == 'w')) {
12168             cps = &first;
12169         } else {
12170             cps = &second;
12171         }
12172         break;
12173       case BeginningOfGame:
12174       default:
12175         return;
12176     }
12177     SendToProgram("?\n", cps);
12178 }
12179
12180 void
12181 TruncateGameEvent()
12182 {
12183     EditGameEvent();
12184     if (gameMode != EditGame) return;
12185     TruncateGame();
12186 }
12187
12188 void
12189 TruncateGame()
12190 {
12191     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12192     if (forwardMostMove > currentMove) {
12193         if (gameInfo.resultDetails != NULL) {
12194             free(gameInfo.resultDetails);
12195             gameInfo.resultDetails = NULL;
12196             gameInfo.result = GameUnfinished;
12197         }
12198         forwardMostMove = currentMove;
12199         HistorySet(parseList, backwardMostMove, forwardMostMove,
12200                    currentMove-1);
12201     }
12202 }
12203
12204 void
12205 HintEvent()
12206 {
12207     if (appData.noChessProgram) return;
12208     switch (gameMode) {
12209       case MachinePlaysWhite:
12210         if (WhiteOnMove(forwardMostMove)) {
12211             DisplayError(_("Wait until your turn"), 0);
12212             return;
12213         }
12214         break;
12215       case BeginningOfGame:
12216       case MachinePlaysBlack:
12217         if (!WhiteOnMove(forwardMostMove)) {
12218             DisplayError(_("Wait until your turn"), 0);
12219             return;
12220         }
12221         break;
12222       default:
12223         DisplayError(_("No hint available"), 0);
12224         return;
12225     }
12226     SendToProgram("hint\n", &first);
12227     hintRequested = TRUE;
12228 }
12229
12230 void
12231 BookEvent()
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       case EditPosition:
12249         EditPositionDone(TRUE);
12250         break;
12251       case TwoMachinesPlay:
12252         return;
12253       default:
12254         break;
12255     }
12256     SendToProgram("bk\n", &first);
12257     bookOutput[0] = NULLCHAR;
12258     bookRequested = TRUE;
12259 }
12260
12261 void
12262 AboutGameEvent()
12263 {
12264     char *tags = PGNTags(&gameInfo);
12265     TagsPopUp(tags, CmailMsg());
12266     free(tags);
12267 }
12268
12269 /* end button procedures */
12270
12271 void
12272 PrintPosition(fp, move)
12273      FILE *fp;
12274      int move;
12275 {
12276     int i, j;
12277     
12278     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12279         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12280             char c = PieceToChar(boards[move][i][j]);
12281             fputc(c == 'x' ? '.' : c, fp);
12282             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12283         }
12284     }
12285     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12286       fprintf(fp, "white to play\n");
12287     else
12288       fprintf(fp, "black to play\n");
12289 }
12290
12291 void
12292 PrintOpponents(fp)
12293      FILE *fp;
12294 {
12295     if (gameInfo.white != NULL) {
12296         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12297     } else {
12298         fprintf(fp, "\n");
12299     }
12300 }
12301
12302 /* Find last component of program's own name, using some heuristics */
12303 void
12304 TidyProgramName(prog, host, buf)
12305      char *prog, *host, buf[MSG_SIZ];
12306 {
12307     char *p, *q;
12308     int local = (strcmp(host, "localhost") == 0);
12309     while (!local && (p = strchr(prog, ';')) != NULL) {
12310         p++;
12311         while (*p == ' ') p++;
12312         prog = p;
12313     }
12314     if (*prog == '"' || *prog == '\'') {
12315         q = strchr(prog + 1, *prog);
12316     } else {
12317         q = strchr(prog, ' ');
12318     }
12319     if (q == NULL) q = prog + strlen(prog);
12320     p = q;
12321     while (p >= prog && *p != '/' && *p != '\\') p--;
12322     p++;
12323     if(p == prog && *p == '"') p++;
12324     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12325     memcpy(buf, p, q - p);
12326     buf[q - p] = NULLCHAR;
12327     if (!local) {
12328         strcat(buf, "@");
12329         strcat(buf, host);
12330     }
12331 }
12332
12333 char *
12334 TimeControlTagValue()
12335 {
12336     char buf[MSG_SIZ];
12337     if (!appData.clockMode) {
12338         strcpy(buf, "-");
12339     } else if (movesPerSession > 0) {
12340         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12341     } else if (timeIncrement == 0) {
12342         sprintf(buf, "%ld", timeControl/1000);
12343     } else {
12344         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12345     }
12346     return StrSave(buf);
12347 }
12348
12349 void
12350 SetGameInfo()
12351 {
12352     /* This routine is used only for certain modes */
12353     VariantClass v = gameInfo.variant;
12354     ChessMove r = GameUnfinished;
12355     char *p = NULL;
12356
12357     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12358         r = gameInfo.result; 
12359         p = gameInfo.resultDetails; 
12360         gameInfo.resultDetails = NULL;
12361     }
12362     ClearGameInfo(&gameInfo);
12363     gameInfo.variant = v;
12364
12365     switch (gameMode) {
12366       case MachinePlaysWhite:
12367         gameInfo.event = StrSave( appData.pgnEventHeader );
12368         gameInfo.site = StrSave(HostName());
12369         gameInfo.date = PGNDate();
12370         gameInfo.round = StrSave("-");
12371         gameInfo.white = StrSave(first.tidy);
12372         gameInfo.black = StrSave(UserName());
12373         gameInfo.timeControl = TimeControlTagValue();
12374         break;
12375
12376       case MachinePlaysBlack:
12377         gameInfo.event = StrSave( appData.pgnEventHeader );
12378         gameInfo.site = StrSave(HostName());
12379         gameInfo.date = PGNDate();
12380         gameInfo.round = StrSave("-");
12381         gameInfo.white = StrSave(UserName());
12382         gameInfo.black = StrSave(first.tidy);
12383         gameInfo.timeControl = TimeControlTagValue();
12384         break;
12385
12386       case TwoMachinesPlay:
12387         gameInfo.event = StrSave( appData.pgnEventHeader );
12388         gameInfo.site = StrSave(HostName());
12389         gameInfo.date = PGNDate();
12390         if (matchGame > 0) {
12391             char buf[MSG_SIZ];
12392             sprintf(buf, "%d", matchGame);
12393             gameInfo.round = StrSave(buf);
12394         } else {
12395             gameInfo.round = StrSave("-");
12396         }
12397         if (first.twoMachinesColor[0] == 'w') {
12398             gameInfo.white = StrSave(first.tidy);
12399             gameInfo.black = StrSave(second.tidy);
12400         } else {
12401             gameInfo.white = StrSave(second.tidy);
12402             gameInfo.black = StrSave(first.tidy);
12403         }
12404         gameInfo.timeControl = TimeControlTagValue();
12405         break;
12406
12407       case EditGame:
12408         gameInfo.event = StrSave("Edited game");
12409         gameInfo.site = StrSave(HostName());
12410         gameInfo.date = PGNDate();
12411         gameInfo.round = StrSave("-");
12412         gameInfo.white = StrSave("-");
12413         gameInfo.black = StrSave("-");
12414         gameInfo.result = r;
12415         gameInfo.resultDetails = p;
12416         break;
12417
12418       case EditPosition:
12419         gameInfo.event = StrSave("Edited position");
12420         gameInfo.site = StrSave(HostName());
12421         gameInfo.date = PGNDate();
12422         gameInfo.round = StrSave("-");
12423         gameInfo.white = StrSave("-");
12424         gameInfo.black = StrSave("-");
12425         break;
12426
12427       case IcsPlayingWhite:
12428       case IcsPlayingBlack:
12429       case IcsObserving:
12430       case IcsExamining:
12431         break;
12432
12433       case PlayFromGameFile:
12434         gameInfo.event = StrSave("Game from non-PGN file");
12435         gameInfo.site = StrSave(HostName());
12436         gameInfo.date = PGNDate();
12437         gameInfo.round = StrSave("-");
12438         gameInfo.white = StrSave("?");
12439         gameInfo.black = StrSave("?");
12440         break;
12441
12442       default:
12443         break;
12444     }
12445 }
12446
12447 void
12448 ReplaceComment(index, text)
12449      int index;
12450      char *text;
12451 {
12452     int len;
12453
12454     while (*text == '\n') text++;
12455     len = strlen(text);
12456     while (len > 0 && text[len - 1] == '\n') len--;
12457
12458     if (commentList[index] != NULL)
12459       free(commentList[index]);
12460
12461     if (len == 0) {
12462         commentList[index] = NULL;
12463         return;
12464     }
12465   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12466       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12467       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12468     commentList[index] = (char *) malloc(len + 2);
12469     strncpy(commentList[index], text, len);
12470     commentList[index][len] = '\n';
12471     commentList[index][len + 1] = NULLCHAR;
12472   } else { 
12473     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12474     char *p;
12475     commentList[index] = (char *) malloc(len + 6);
12476     strcpy(commentList[index], "{\n");
12477     strncpy(commentList[index]+2, text, len);
12478     commentList[index][len+2] = NULLCHAR;
12479     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12480     strcat(commentList[index], "\n}\n");
12481   }
12482 }
12483
12484 void
12485 CrushCRs(text)
12486      char *text;
12487 {
12488   char *p = text;
12489   char *q = text;
12490   char ch;
12491
12492   do {
12493     ch = *p++;
12494     if (ch == '\r') continue;
12495     *q++ = ch;
12496   } while (ch != '\0');
12497 }
12498
12499 void
12500 AppendComment(index, text, addBraces)
12501      int index;
12502      char *text;
12503      Boolean addBraces; // [HGM] braces: tells if we should add {}
12504 {
12505     int oldlen, len;
12506     char *old;
12507
12508 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12509     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12510
12511     CrushCRs(text);
12512     while (*text == '\n') text++;
12513     len = strlen(text);
12514     while (len > 0 && text[len - 1] == '\n') len--;
12515
12516     if (len == 0) return;
12517
12518     if (commentList[index] != NULL) {
12519         old = commentList[index];
12520         oldlen = strlen(old);
12521         while(commentList[index][oldlen-1] ==  '\n')
12522           commentList[index][--oldlen] = NULLCHAR;
12523         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12524         strcpy(commentList[index], old);
12525         free(old);
12526         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12527         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12528           if(addBraces) addBraces = FALSE; else { text++; len--; }
12529           while (*text == '\n') { text++; len--; }
12530           commentList[index][--oldlen] = NULLCHAR;
12531       }
12532         if(addBraces) strcat(commentList[index], "\n{\n");
12533         else          strcat(commentList[index], "\n");
12534         strcat(commentList[index], text);
12535         if(addBraces) strcat(commentList[index], "\n}\n");
12536         else          strcat(commentList[index], "\n");
12537     } else {
12538         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12539         if(addBraces)
12540              strcpy(commentList[index], "{\n");
12541         else commentList[index][0] = NULLCHAR;
12542         strcat(commentList[index], text);
12543         strcat(commentList[index], "\n");
12544         if(addBraces) strcat(commentList[index], "}\n");
12545     }
12546 }
12547
12548 static char * FindStr( char * text, char * sub_text )
12549 {
12550     char * result = strstr( text, sub_text );
12551
12552     if( result != NULL ) {
12553         result += strlen( sub_text );
12554     }
12555
12556     return result;
12557 }
12558
12559 /* [AS] Try to extract PV info from PGN comment */
12560 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12561 char *GetInfoFromComment( int index, char * text )
12562 {
12563     char * sep = text;
12564
12565     if( text != NULL && index > 0 ) {
12566         int score = 0;
12567         int depth = 0;
12568         int time = -1, sec = 0, deci;
12569         char * s_eval = FindStr( text, "[%eval " );
12570         char * s_emt = FindStr( text, "[%emt " );
12571
12572         if( s_eval != NULL || s_emt != NULL ) {
12573             /* New style */
12574             char delim;
12575
12576             if( s_eval != NULL ) {
12577                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12578                     return text;
12579                 }
12580
12581                 if( delim != ']' ) {
12582                     return text;
12583                 }
12584             }
12585
12586             if( s_emt != NULL ) {
12587             }
12588                 return text;
12589         }
12590         else {
12591             /* We expect something like: [+|-]nnn.nn/dd */
12592             int score_lo = 0;
12593
12594             if(*text != '{') return text; // [HGM] braces: must be normal comment
12595
12596             sep = strchr( text, '/' );
12597             if( sep == NULL || sep < (text+4) ) {
12598                 return text;
12599             }
12600
12601             time = -1; sec = -1; deci = -1;
12602             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12603                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12604                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12605                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12606                 return text;
12607             }
12608
12609             if( score_lo < 0 || score_lo >= 100 ) {
12610                 return text;
12611             }
12612
12613             if(sec >= 0) time = 600*time + 10*sec; else
12614             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12615
12616             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12617
12618             /* [HGM] PV time: now locate end of PV info */
12619             while( *++sep >= '0' && *sep <= '9'); // strip depth
12620             if(time >= 0)
12621             while( *++sep >= '0' && *sep <= '9'); // strip time
12622             if(sec >= 0)
12623             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12624             if(deci >= 0)
12625             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12626             while(*sep == ' ') sep++;
12627         }
12628
12629         if( depth <= 0 ) {
12630             return text;
12631         }
12632
12633         if( time < 0 ) {
12634             time = -1;
12635         }
12636
12637         pvInfoList[index-1].depth = depth;
12638         pvInfoList[index-1].score = score;
12639         pvInfoList[index-1].time  = 10*time; // centi-sec
12640         if(*sep == '}') *sep = 0; else *--sep = '{';
12641     }
12642     return sep;
12643 }
12644
12645 void
12646 SendToProgram(message, cps)
12647      char *message;
12648      ChessProgramState *cps;
12649 {
12650     int count, outCount, error;
12651     char buf[MSG_SIZ];
12652
12653     if (cps->pr == NULL) return;
12654     Attention(cps);
12655     
12656     if (appData.debugMode) {
12657         TimeMark now;
12658         GetTimeMark(&now);
12659         fprintf(debugFP, "%ld >%-6s: %s", 
12660                 SubtractTimeMarks(&now, &programStartTime),
12661                 cps->which, message);
12662     }
12663     
12664     count = strlen(message);
12665     outCount = OutputToProcess(cps->pr, message, count, &error);
12666     if (outCount < count && !exiting 
12667                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12668         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12669         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12670             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12671                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12672                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12673             } else {
12674                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12675             }
12676             gameInfo.resultDetails = StrSave(buf);
12677         }
12678         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12679     }
12680 }
12681
12682 void
12683 ReceiveFromProgram(isr, closure, message, count, error)
12684      InputSourceRef isr;
12685      VOIDSTAR closure;
12686      char *message;
12687      int count;
12688      int error;
12689 {
12690     char *end_str;
12691     char buf[MSG_SIZ];
12692     ChessProgramState *cps = (ChessProgramState *)closure;
12693
12694     if (isr != cps->isr) return; /* Killed intentionally */
12695     if (count <= 0) {
12696         if (count == 0) {
12697             sprintf(buf,
12698                     _("Error: %s chess program (%s) exited unexpectedly"),
12699                     cps->which, cps->program);
12700         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12701                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12702                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12703                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12704                 } else {
12705                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12706                 }
12707                 gameInfo.resultDetails = StrSave(buf);
12708             }
12709             RemoveInputSource(cps->isr);
12710             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12711         } else {
12712             sprintf(buf,
12713                     _("Error reading from %s chess program (%s)"),
12714                     cps->which, cps->program);
12715             RemoveInputSource(cps->isr);
12716
12717             /* [AS] Program is misbehaving badly... kill it */
12718             if( count == -2 ) {
12719                 DestroyChildProcess( cps->pr, 9 );
12720                 cps->pr = NoProc;
12721             }
12722
12723             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12724         }
12725         return;
12726     }
12727     
12728     if ((end_str = strchr(message, '\r')) != NULL)
12729       *end_str = NULLCHAR;
12730     if ((end_str = strchr(message, '\n')) != NULL)
12731       *end_str = NULLCHAR;
12732     
12733     if (appData.debugMode) {
12734         TimeMark now; int print = 1;
12735         char *quote = ""; char c; int i;
12736
12737         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12738                 char start = message[0];
12739                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12740                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12741                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12742                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12743                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12744                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12745                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12746                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12747                         { quote = "# "; print = (appData.engineComments == 2); }
12748                 message[0] = start; // restore original message
12749         }
12750         if(print) {
12751                 GetTimeMark(&now);
12752                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12753                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12754                         quote,
12755                         message);
12756         }
12757     }
12758
12759     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12760     if (appData.icsEngineAnalyze) {
12761         if (strstr(message, "whisper") != NULL ||
12762              strstr(message, "kibitz") != NULL || 
12763             strstr(message, "tellics") != NULL) return;
12764     }
12765
12766     HandleMachineMove(message, cps);
12767 }
12768
12769
12770 void
12771 SendTimeControl(cps, mps, tc, inc, sd, st)
12772      ChessProgramState *cps;
12773      int mps, inc, sd, st;
12774      long tc;
12775 {
12776     char buf[MSG_SIZ];
12777     int seconds;
12778
12779     if( timeControl_2 > 0 ) {
12780         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12781             tc = timeControl_2;
12782         }
12783     }
12784     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12785     inc /= cps->timeOdds;
12786     st  /= cps->timeOdds;
12787
12788     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12789
12790     if (st > 0) {
12791       /* Set exact time per move, normally using st command */
12792       if (cps->stKludge) {
12793         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12794         seconds = st % 60;
12795         if (seconds == 0) {
12796           sprintf(buf, "level 1 %d\n", st/60);
12797         } else {
12798           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12799         }
12800       } else {
12801         sprintf(buf, "st %d\n", st);
12802       }
12803     } else {
12804       /* Set conventional or incremental time control, using level command */
12805       if (seconds == 0) {
12806         /* Note old gnuchess bug -- minutes:seconds used to not work.
12807            Fixed in later versions, but still avoid :seconds
12808            when seconds is 0. */
12809         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12810       } else {
12811         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12812                 seconds, inc/1000);
12813       }
12814     }
12815     SendToProgram(buf, cps);
12816
12817     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12818     /* Orthogonally, limit search to given depth */
12819     if (sd > 0) {
12820       if (cps->sdKludge) {
12821         sprintf(buf, "depth\n%d\n", sd);
12822       } else {
12823         sprintf(buf, "sd %d\n", sd);
12824       }
12825       SendToProgram(buf, cps);
12826     }
12827
12828     if(cps->nps > 0) { /* [HGM] nps */
12829         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12830         else {
12831                 sprintf(buf, "nps %d\n", cps->nps);
12832               SendToProgram(buf, cps);
12833         }
12834     }
12835 }
12836
12837 ChessProgramState *WhitePlayer()
12838 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12839 {
12840     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12841        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12842         return &second;
12843     return &first;
12844 }
12845
12846 void
12847 SendTimeRemaining(cps, machineWhite)
12848      ChessProgramState *cps;
12849      int /*boolean*/ machineWhite;
12850 {
12851     char message[MSG_SIZ];
12852     long time, otime;
12853
12854     /* Note: this routine must be called when the clocks are stopped
12855        or when they have *just* been set or switched; otherwise
12856        it will be off by the time since the current tick started.
12857     */
12858     if (machineWhite) {
12859         time = whiteTimeRemaining / 10;
12860         otime = blackTimeRemaining / 10;
12861     } else {
12862         time = blackTimeRemaining / 10;
12863         otime = whiteTimeRemaining / 10;
12864     }
12865     /* [HGM] translate opponent's time by time-odds factor */
12866     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12867     if (appData.debugMode) {
12868         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12869     }
12870
12871     if (time <= 0) time = 1;
12872     if (otime <= 0) otime = 1;
12873     
12874     sprintf(message, "time %ld\n", time);
12875     SendToProgram(message, cps);
12876
12877     sprintf(message, "otim %ld\n", otime);
12878     SendToProgram(message, cps);
12879 }
12880
12881 int
12882 BoolFeature(p, name, loc, cps)
12883      char **p;
12884      char *name;
12885      int *loc;
12886      ChessProgramState *cps;
12887 {
12888   char buf[MSG_SIZ];
12889   int len = strlen(name);
12890   int val;
12891   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12892     (*p) += len + 1;
12893     sscanf(*p, "%d", &val);
12894     *loc = (val != 0);
12895     while (**p && **p != ' ') (*p)++;
12896     sprintf(buf, "accepted %s\n", name);
12897     SendToProgram(buf, cps);
12898     return TRUE;
12899   }
12900   return FALSE;
12901 }
12902
12903 int
12904 IntFeature(p, name, loc, cps)
12905      char **p;
12906      char *name;
12907      int *loc;
12908      ChessProgramState *cps;
12909 {
12910   char buf[MSG_SIZ];
12911   int len = strlen(name);
12912   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12913     (*p) += len + 1;
12914     sscanf(*p, "%d", loc);
12915     while (**p && **p != ' ') (*p)++;
12916     sprintf(buf, "accepted %s\n", name);
12917     SendToProgram(buf, cps);
12918     return TRUE;
12919   }
12920   return FALSE;
12921 }
12922
12923 int
12924 StringFeature(p, name, loc, cps)
12925      char **p;
12926      char *name;
12927      char loc[];
12928      ChessProgramState *cps;
12929 {
12930   char buf[MSG_SIZ];
12931   int len = strlen(name);
12932   if (strncmp((*p), name, len) == 0
12933       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12934     (*p) += len + 2;
12935     sscanf(*p, "%[^\"]", loc);
12936     while (**p && **p != '\"') (*p)++;
12937     if (**p == '\"') (*p)++;
12938     sprintf(buf, "accepted %s\n", name);
12939     SendToProgram(buf, cps);
12940     return TRUE;
12941   }
12942   return FALSE;
12943 }
12944
12945 int 
12946 ParseOption(Option *opt, ChessProgramState *cps)
12947 // [HGM] options: process the string that defines an engine option, and determine
12948 // name, type, default value, and allowed value range
12949 {
12950         char *p, *q, buf[MSG_SIZ];
12951         int n, min = (-1)<<31, max = 1<<31, def;
12952
12953         if(p = strstr(opt->name, " -spin ")) {
12954             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12955             if(max < min) max = min; // enforce consistency
12956             if(def < min) def = min;
12957             if(def > max) def = max;
12958             opt->value = def;
12959             opt->min = min;
12960             opt->max = max;
12961             opt->type = Spin;
12962         } else if((p = strstr(opt->name, " -slider "))) {
12963             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12964             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12965             if(max < min) max = min; // enforce consistency
12966             if(def < min) def = min;
12967             if(def > max) def = max;
12968             opt->value = def;
12969             opt->min = min;
12970             opt->max = max;
12971             opt->type = Spin; // Slider;
12972         } else if((p = strstr(opt->name, " -string "))) {
12973             opt->textValue = p+9;
12974             opt->type = TextBox;
12975         } else if((p = strstr(opt->name, " -file "))) {
12976             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12977             opt->textValue = p+7;
12978             opt->type = TextBox; // FileName;
12979         } else if((p = strstr(opt->name, " -path "))) {
12980             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12981             opt->textValue = p+7;
12982             opt->type = TextBox; // PathName;
12983         } else if(p = strstr(opt->name, " -check ")) {
12984             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12985             opt->value = (def != 0);
12986             opt->type = CheckBox;
12987         } else if(p = strstr(opt->name, " -combo ")) {
12988             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12989             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12990             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12991             opt->value = n = 0;
12992             while(q = StrStr(q, " /// ")) {
12993                 n++; *q = 0;    // count choices, and null-terminate each of them
12994                 q += 5;
12995                 if(*q == '*') { // remember default, which is marked with * prefix
12996                     q++;
12997                     opt->value = n;
12998                 }
12999                 cps->comboList[cps->comboCnt++] = q;
13000             }
13001             cps->comboList[cps->comboCnt++] = NULL;
13002             opt->max = n + 1;
13003             opt->type = ComboBox;
13004         } else if(p = strstr(opt->name, " -button")) {
13005             opt->type = Button;
13006         } else if(p = strstr(opt->name, " -save")) {
13007             opt->type = SaveButton;
13008         } else return FALSE;
13009         *p = 0; // terminate option name
13010         // now look if the command-line options define a setting for this engine option.
13011         if(cps->optionSettings && cps->optionSettings[0])
13012             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13013         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13014                 sprintf(buf, "option %s", p);
13015                 if(p = strstr(buf, ",")) *p = 0;
13016                 strcat(buf, "\n");
13017                 SendToProgram(buf, cps);
13018         }
13019         return TRUE;
13020 }
13021
13022 void
13023 FeatureDone(cps, val)
13024      ChessProgramState* cps;
13025      int val;
13026 {
13027   DelayedEventCallback cb = GetDelayedEvent();
13028   if ((cb == InitBackEnd3 && cps == &first) ||
13029       (cb == TwoMachinesEventIfReady && cps == &second)) {
13030     CancelDelayedEvent();
13031     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13032   }
13033   cps->initDone = val;
13034 }
13035
13036 /* Parse feature command from engine */
13037 void
13038 ParseFeatures(args, cps)
13039      char* args;
13040      ChessProgramState *cps;  
13041 {
13042   char *p = args;
13043   char *q;
13044   int val;
13045   char buf[MSG_SIZ];
13046
13047   for (;;) {
13048     while (*p == ' ') p++;
13049     if (*p == NULLCHAR) return;
13050
13051     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13052     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13053     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13054     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13055     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13056     if (BoolFeature(&p, "reuse", &val, cps)) {
13057       /* Engine can disable reuse, but can't enable it if user said no */
13058       if (!val) cps->reuse = FALSE;
13059       continue;
13060     }
13061     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13062     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13063       if (gameMode == TwoMachinesPlay) {
13064         DisplayTwoMachinesTitle();
13065       } else {
13066         DisplayTitle("");
13067       }
13068       continue;
13069     }
13070     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13071     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13072     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13073     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13074     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13075     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13076     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13077     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13078     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13079     if (IntFeature(&p, "done", &val, cps)) {
13080       FeatureDone(cps, val);
13081       continue;
13082     }
13083     /* Added by Tord: */
13084     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13085     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13086     /* End of additions by Tord */
13087
13088     /* [HGM] added features: */
13089     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13090     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13091     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13092     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13093     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13094     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13095     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13096         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13097             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13098             SendToProgram(buf, cps);
13099             continue;
13100         }
13101         if(cps->nrOptions >= MAX_OPTIONS) {
13102             cps->nrOptions--;
13103             sprintf(buf, "%s engine has too many options\n", cps->which);
13104             DisplayError(buf, 0);
13105         }
13106         continue;
13107     }
13108     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13109     /* End of additions by HGM */
13110
13111     /* unknown feature: complain and skip */
13112     q = p;
13113     while (*q && *q != '=') q++;
13114     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13115     SendToProgram(buf, cps);
13116     p = q;
13117     if (*p == '=') {
13118       p++;
13119       if (*p == '\"') {
13120         p++;
13121         while (*p && *p != '\"') p++;
13122         if (*p == '\"') p++;
13123       } else {
13124         while (*p && *p != ' ') p++;
13125       }
13126     }
13127   }
13128
13129 }
13130
13131 void
13132 PeriodicUpdatesEvent(newState)
13133      int newState;
13134 {
13135     if (newState == appData.periodicUpdates)
13136       return;
13137
13138     appData.periodicUpdates=newState;
13139
13140     /* Display type changes, so update it now */
13141 //    DisplayAnalysis();
13142
13143     /* Get the ball rolling again... */
13144     if (newState) {
13145         AnalysisPeriodicEvent(1);
13146         StartAnalysisClock();
13147     }
13148 }
13149
13150 void
13151 PonderNextMoveEvent(newState)
13152      int newState;
13153 {
13154     if (newState == appData.ponderNextMove) return;
13155     if (gameMode == EditPosition) EditPositionDone(TRUE);
13156     if (newState) {
13157         SendToProgram("hard\n", &first);
13158         if (gameMode == TwoMachinesPlay) {
13159             SendToProgram("hard\n", &second);
13160         }
13161     } else {
13162         SendToProgram("easy\n", &first);
13163         thinkOutput[0] = NULLCHAR;
13164         if (gameMode == TwoMachinesPlay) {
13165             SendToProgram("easy\n", &second);
13166         }
13167     }
13168     appData.ponderNextMove = newState;
13169 }
13170
13171 void
13172 NewSettingEvent(option, command, value)
13173      char *command;
13174      int option, value;
13175 {
13176     char buf[MSG_SIZ];
13177
13178     if (gameMode == EditPosition) EditPositionDone(TRUE);
13179     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13180     SendToProgram(buf, &first);
13181     if (gameMode == TwoMachinesPlay) {
13182         SendToProgram(buf, &second);
13183     }
13184 }
13185
13186 void
13187 ShowThinkingEvent()
13188 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13189 {
13190     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13191     int newState = appData.showThinking
13192         // [HGM] thinking: other features now need thinking output as well
13193         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13194     
13195     if (oldState == newState) return;
13196     oldState = newState;
13197     if (gameMode == EditPosition) EditPositionDone(TRUE);
13198     if (oldState) {
13199         SendToProgram("post\n", &first);
13200         if (gameMode == TwoMachinesPlay) {
13201             SendToProgram("post\n", &second);
13202         }
13203     } else {
13204         SendToProgram("nopost\n", &first);
13205         thinkOutput[0] = NULLCHAR;
13206         if (gameMode == TwoMachinesPlay) {
13207             SendToProgram("nopost\n", &second);
13208         }
13209     }
13210 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13211 }
13212
13213 void
13214 AskQuestionEvent(title, question, replyPrefix, which)
13215      char *title; char *question; char *replyPrefix; char *which;
13216 {
13217   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13218   if (pr == NoProc) return;
13219   AskQuestion(title, question, replyPrefix, pr);
13220 }
13221
13222 void
13223 DisplayMove(moveNumber)
13224      int moveNumber;
13225 {
13226     char message[MSG_SIZ];
13227     char res[MSG_SIZ];
13228     char cpThinkOutput[MSG_SIZ];
13229
13230     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13231     
13232     if (moveNumber == forwardMostMove - 1 || 
13233         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13234
13235         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13236
13237         if (strchr(cpThinkOutput, '\n')) {
13238             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13239         }
13240     } else {
13241         *cpThinkOutput = NULLCHAR;
13242     }
13243
13244     /* [AS] Hide thinking from human user */
13245     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13246         *cpThinkOutput = NULLCHAR;
13247         if( thinkOutput[0] != NULLCHAR ) {
13248             int i;
13249
13250             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13251                 cpThinkOutput[i] = '.';
13252             }
13253             cpThinkOutput[i] = NULLCHAR;
13254             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13255         }
13256     }
13257
13258     if (moveNumber == forwardMostMove - 1 &&
13259         gameInfo.resultDetails != NULL) {
13260         if (gameInfo.resultDetails[0] == NULLCHAR) {
13261             sprintf(res, " %s", PGNResult(gameInfo.result));
13262         } else {
13263             sprintf(res, " {%s} %s",
13264                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13265         }
13266     } else {
13267         res[0] = NULLCHAR;
13268     }
13269
13270     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13271         DisplayMessage(res, cpThinkOutput);
13272     } else {
13273         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13274                 WhiteOnMove(moveNumber) ? " " : ".. ",
13275                 parseList[moveNumber], res);
13276         DisplayMessage(message, cpThinkOutput);
13277     }
13278 }
13279
13280 void
13281 DisplayComment(moveNumber, text)
13282      int moveNumber;
13283      char *text;
13284 {
13285     char title[MSG_SIZ];
13286     char buf[8000]; // comment can be long!
13287     int score, depth;
13288     
13289     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13290       strcpy(title, "Comment");
13291     } else {
13292       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13293               WhiteOnMove(moveNumber) ? " " : ".. ",
13294               parseList[moveNumber]);
13295     }
13296     // [HGM] PV info: display PV info together with (or as) comment
13297     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13298       if(text == NULL) text = "";                                           
13299       score = pvInfoList[moveNumber].score;
13300       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13301               depth, (pvInfoList[moveNumber].time+50)/100, text);
13302       text = buf;
13303     }
13304     if (text != NULL && (appData.autoDisplayComment || commentUp))
13305         CommentPopUp(title, text);
13306 }
13307
13308 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13309  * might be busy thinking or pondering.  It can be omitted if your
13310  * gnuchess is configured to stop thinking immediately on any user
13311  * input.  However, that gnuchess feature depends on the FIONREAD
13312  * ioctl, which does not work properly on some flavors of Unix.
13313  */
13314 void
13315 Attention(cps)
13316      ChessProgramState *cps;
13317 {
13318 #if ATTENTION
13319     if (!cps->useSigint) return;
13320     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13321     switch (gameMode) {
13322       case MachinePlaysWhite:
13323       case MachinePlaysBlack:
13324       case TwoMachinesPlay:
13325       case IcsPlayingWhite:
13326       case IcsPlayingBlack:
13327       case AnalyzeMode:
13328       case AnalyzeFile:
13329         /* Skip if we know it isn't thinking */
13330         if (!cps->maybeThinking) return;
13331         if (appData.debugMode)
13332           fprintf(debugFP, "Interrupting %s\n", cps->which);
13333         InterruptChildProcess(cps->pr);
13334         cps->maybeThinking = FALSE;
13335         break;
13336       default:
13337         break;
13338     }
13339 #endif /*ATTENTION*/
13340 }
13341
13342 int
13343 CheckFlags()
13344 {
13345     if (whiteTimeRemaining <= 0) {
13346         if (!whiteFlag) {
13347             whiteFlag = TRUE;
13348             if (appData.icsActive) {
13349                 if (appData.autoCallFlag &&
13350                     gameMode == IcsPlayingBlack && !blackFlag) {
13351                   SendToICS(ics_prefix);
13352                   SendToICS("flag\n");
13353                 }
13354             } else {
13355                 if (blackFlag) {
13356                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13357                 } else {
13358                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13359                     if (appData.autoCallFlag) {
13360                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13361                         return TRUE;
13362                     }
13363                 }
13364             }
13365         }
13366     }
13367     if (blackTimeRemaining <= 0) {
13368         if (!blackFlag) {
13369             blackFlag = TRUE;
13370             if (appData.icsActive) {
13371                 if (appData.autoCallFlag &&
13372                     gameMode == IcsPlayingWhite && !whiteFlag) {
13373                   SendToICS(ics_prefix);
13374                   SendToICS("flag\n");
13375                 }
13376             } else {
13377                 if (whiteFlag) {
13378                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13379                 } else {
13380                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13381                     if (appData.autoCallFlag) {
13382                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13383                         return TRUE;
13384                     }
13385                 }
13386             }
13387         }
13388     }
13389     return FALSE;
13390 }
13391
13392 void
13393 CheckTimeControl()
13394 {
13395     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13396         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13397
13398     /*
13399      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13400      */
13401     if ( !WhiteOnMove(forwardMostMove) )
13402         /* White made time control */
13403         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13404         /* [HGM] time odds: correct new time quota for time odds! */
13405                                             / WhitePlayer()->timeOdds;
13406       else
13407         /* Black made time control */
13408         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13409                                             / WhitePlayer()->other->timeOdds;
13410 }
13411
13412 void
13413 DisplayBothClocks()
13414 {
13415     int wom = gameMode == EditPosition ?
13416       !blackPlaysFirst : WhiteOnMove(currentMove);
13417     DisplayWhiteClock(whiteTimeRemaining, wom);
13418     DisplayBlackClock(blackTimeRemaining, !wom);
13419 }
13420
13421
13422 /* Timekeeping seems to be a portability nightmare.  I think everyone
13423    has ftime(), but I'm really not sure, so I'm including some ifdefs
13424    to use other calls if you don't.  Clocks will be less accurate if
13425    you have neither ftime nor gettimeofday.
13426 */
13427
13428 /* VS 2008 requires the #include outside of the function */
13429 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13430 #include <sys/timeb.h>
13431 #endif
13432
13433 /* Get the current time as a TimeMark */
13434 void
13435 GetTimeMark(tm)
13436      TimeMark *tm;
13437 {
13438 #if HAVE_GETTIMEOFDAY
13439
13440     struct timeval timeVal;
13441     struct timezone timeZone;
13442
13443     gettimeofday(&timeVal, &timeZone);
13444     tm->sec = (long) timeVal.tv_sec; 
13445     tm->ms = (int) (timeVal.tv_usec / 1000L);
13446
13447 #else /*!HAVE_GETTIMEOFDAY*/
13448 #if HAVE_FTIME
13449
13450 // include <sys/timeb.h> / moved to just above start of function
13451     struct timeb timeB;
13452
13453     ftime(&timeB);
13454     tm->sec = (long) timeB.time;
13455     tm->ms = (int) timeB.millitm;
13456
13457 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13458     tm->sec = (long) time(NULL);
13459     tm->ms = 0;
13460 #endif
13461 #endif
13462 }
13463
13464 /* Return the difference in milliseconds between two
13465    time marks.  We assume the difference will fit in a long!
13466 */
13467 long
13468 SubtractTimeMarks(tm2, tm1)
13469      TimeMark *tm2, *tm1;
13470 {
13471     return 1000L*(tm2->sec - tm1->sec) +
13472            (long) (tm2->ms - tm1->ms);
13473 }
13474
13475
13476 /*
13477  * Code to manage the game clocks.
13478  *
13479  * In tournament play, black starts the clock and then white makes a move.
13480  * We give the human user a slight advantage if he is playing white---the
13481  * clocks don't run until he makes his first move, so it takes zero time.
13482  * Also, we don't account for network lag, so we could get out of sync
13483  * with GNU Chess's clock -- but then, referees are always right.  
13484  */
13485
13486 static TimeMark tickStartTM;
13487 static long intendedTickLength;
13488
13489 long
13490 NextTickLength(timeRemaining)
13491      long timeRemaining;
13492 {
13493     long nominalTickLength, nextTickLength;
13494
13495     if (timeRemaining > 0L && timeRemaining <= 10000L)
13496       nominalTickLength = 100L;
13497     else
13498       nominalTickLength = 1000L;
13499     nextTickLength = timeRemaining % nominalTickLength;
13500     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13501
13502     return nextTickLength;
13503 }
13504
13505 /* Adjust clock one minute up or down */
13506 void
13507 AdjustClock(Boolean which, int dir)
13508 {
13509     if(which) blackTimeRemaining += 60000*dir;
13510     else      whiteTimeRemaining += 60000*dir;
13511     DisplayBothClocks();
13512 }
13513
13514 /* Stop clocks and reset to a fresh time control */
13515 void
13516 ResetClocks() 
13517 {
13518     (void) StopClockTimer();
13519     if (appData.icsActive) {
13520         whiteTimeRemaining = blackTimeRemaining = 0;
13521     } else if (searchTime) {
13522         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13523         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13524     } else { /* [HGM] correct new time quote for time odds */
13525         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13526         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13527     }
13528     if (whiteFlag || blackFlag) {
13529         DisplayTitle("");
13530         whiteFlag = blackFlag = FALSE;
13531     }
13532     DisplayBothClocks();
13533 }
13534
13535 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13536
13537 /* Decrement running clock by amount of time that has passed */
13538 void
13539 DecrementClocks()
13540 {
13541     long timeRemaining;
13542     long lastTickLength, fudge;
13543     TimeMark now;
13544
13545     if (!appData.clockMode) return;
13546     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13547         
13548     GetTimeMark(&now);
13549
13550     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13551
13552     /* Fudge if we woke up a little too soon */
13553     fudge = intendedTickLength - lastTickLength;
13554     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13555
13556     if (WhiteOnMove(forwardMostMove)) {
13557         if(whiteNPS >= 0) lastTickLength = 0;
13558         timeRemaining = whiteTimeRemaining -= lastTickLength;
13559         DisplayWhiteClock(whiteTimeRemaining - fudge,
13560                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13561     } else {
13562         if(blackNPS >= 0) lastTickLength = 0;
13563         timeRemaining = blackTimeRemaining -= lastTickLength;
13564         DisplayBlackClock(blackTimeRemaining - fudge,
13565                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13566     }
13567
13568     if (CheckFlags()) return;
13569         
13570     tickStartTM = now;
13571     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13572     StartClockTimer(intendedTickLength);
13573
13574     /* if the time remaining has fallen below the alarm threshold, sound the
13575      * alarm. if the alarm has sounded and (due to a takeback or time control
13576      * with increment) the time remaining has increased to a level above the
13577      * threshold, reset the alarm so it can sound again. 
13578      */
13579     
13580     if (appData.icsActive && appData.icsAlarm) {
13581
13582         /* make sure we are dealing with the user's clock */
13583         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13584                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13585            )) return;
13586
13587         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13588             alarmSounded = FALSE;
13589         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13590             PlayAlarmSound();
13591             alarmSounded = TRUE;
13592         }
13593     }
13594 }
13595
13596
13597 /* A player has just moved, so stop the previously running
13598    clock and (if in clock mode) start the other one.
13599    We redisplay both clocks in case we're in ICS mode, because
13600    ICS gives us an update to both clocks after every move.
13601    Note that this routine is called *after* forwardMostMove
13602    is updated, so the last fractional tick must be subtracted
13603    from the color that is *not* on move now.
13604 */
13605 void
13606 SwitchClocks()
13607 {
13608     long lastTickLength;
13609     TimeMark now;
13610     int flagged = FALSE;
13611
13612     GetTimeMark(&now);
13613
13614     if (StopClockTimer() && appData.clockMode) {
13615         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13616         if (WhiteOnMove(forwardMostMove)) {
13617             if(blackNPS >= 0) lastTickLength = 0;
13618             blackTimeRemaining -= lastTickLength;
13619            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13620 //         if(pvInfoList[forwardMostMove-1].time == -1)
13621                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13622                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13623         } else {
13624            if(whiteNPS >= 0) lastTickLength = 0;
13625            whiteTimeRemaining -= lastTickLength;
13626            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13627 //         if(pvInfoList[forwardMostMove-1].time == -1)
13628                  pvInfoList[forwardMostMove-1].time = 
13629                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13630         }
13631         flagged = CheckFlags();
13632     }
13633     CheckTimeControl();
13634
13635     if (flagged || !appData.clockMode) return;
13636
13637     switch (gameMode) {
13638       case MachinePlaysBlack:
13639       case MachinePlaysWhite:
13640       case BeginningOfGame:
13641         if (pausing) return;
13642         break;
13643
13644       case EditGame:
13645       case PlayFromGameFile:
13646       case IcsExamining:
13647         return;
13648
13649       default:
13650         break;
13651     }
13652
13653     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13654         if(WhiteOnMove(forwardMostMove))
13655              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13656         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13657     }
13658
13659     tickStartTM = now;
13660     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13661       whiteTimeRemaining : blackTimeRemaining);
13662     StartClockTimer(intendedTickLength);
13663 }
13664         
13665
13666 /* Stop both clocks */
13667 void
13668 StopClocks()
13669 {       
13670     long lastTickLength;
13671     TimeMark now;
13672
13673     if (!StopClockTimer()) return;
13674     if (!appData.clockMode) return;
13675
13676     GetTimeMark(&now);
13677
13678     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13679     if (WhiteOnMove(forwardMostMove)) {
13680         if(whiteNPS >= 0) lastTickLength = 0;
13681         whiteTimeRemaining -= lastTickLength;
13682         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13683     } else {
13684         if(blackNPS >= 0) lastTickLength = 0;
13685         blackTimeRemaining -= lastTickLength;
13686         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13687     }
13688     CheckFlags();
13689 }
13690         
13691 /* Start clock of player on move.  Time may have been reset, so
13692    if clock is already running, stop and restart it. */
13693 void
13694 StartClocks()
13695 {
13696     (void) StopClockTimer(); /* in case it was running already */
13697     DisplayBothClocks();
13698     if (CheckFlags()) return;
13699
13700     if (!appData.clockMode) return;
13701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13702
13703     GetTimeMark(&tickStartTM);
13704     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13705       whiteTimeRemaining : blackTimeRemaining);
13706
13707    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13708     whiteNPS = blackNPS = -1; 
13709     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13710        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13711         whiteNPS = first.nps;
13712     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13713        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13714         blackNPS = first.nps;
13715     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13716         whiteNPS = second.nps;
13717     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13718         blackNPS = second.nps;
13719     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13720
13721     StartClockTimer(intendedTickLength);
13722 }
13723
13724 char *
13725 TimeString(ms)
13726      long ms;
13727 {
13728     long second, minute, hour, day;
13729     char *sign = "";
13730     static char buf[32];
13731     
13732     if (ms > 0 && ms <= 9900) {
13733       /* convert milliseconds to tenths, rounding up */
13734       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13735
13736       sprintf(buf, " %03.1f ", tenths/10.0);
13737       return buf;
13738     }
13739
13740     /* convert milliseconds to seconds, rounding up */
13741     /* use floating point to avoid strangeness of integer division
13742        with negative dividends on many machines */
13743     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13744
13745     if (second < 0) {
13746         sign = "-";
13747         second = -second;
13748     }
13749     
13750     day = second / (60 * 60 * 24);
13751     second = second % (60 * 60 * 24);
13752     hour = second / (60 * 60);
13753     second = second % (60 * 60);
13754     minute = second / 60;
13755     second = second % 60;
13756     
13757     if (day > 0)
13758       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13759               sign, day, hour, minute, second);
13760     else if (hour > 0)
13761       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13762     else
13763       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13764     
13765     return buf;
13766 }
13767
13768
13769 /*
13770  * This is necessary because some C libraries aren't ANSI C compliant yet.
13771  */
13772 char *
13773 StrStr(string, match)
13774      char *string, *match;
13775 {
13776     int i, length;
13777     
13778     length = strlen(match);
13779     
13780     for (i = strlen(string) - length; i >= 0; i--, string++)
13781       if (!strncmp(match, string, length))
13782         return string;
13783     
13784     return NULL;
13785 }
13786
13787 char *
13788 StrCaseStr(string, match)
13789      char *string, *match;
13790 {
13791     int i, j, length;
13792     
13793     length = strlen(match);
13794     
13795     for (i = strlen(string) - length; i >= 0; i--, string++) {
13796         for (j = 0; j < length; j++) {
13797             if (ToLower(match[j]) != ToLower(string[j]))
13798               break;
13799         }
13800         if (j == length) return string;
13801     }
13802
13803     return NULL;
13804 }
13805
13806 #ifndef _amigados
13807 int
13808 StrCaseCmp(s1, s2)
13809      char *s1, *s2;
13810 {
13811     char c1, c2;
13812     
13813     for (;;) {
13814         c1 = ToLower(*s1++);
13815         c2 = ToLower(*s2++);
13816         if (c1 > c2) return 1;
13817         if (c1 < c2) return -1;
13818         if (c1 == NULLCHAR) return 0;
13819     }
13820 }
13821
13822
13823 int
13824 ToLower(c)
13825      int c;
13826 {
13827     return isupper(c) ? tolower(c) : c;
13828 }
13829
13830
13831 int
13832 ToUpper(c)
13833      int c;
13834 {
13835     return islower(c) ? toupper(c) : c;
13836 }
13837 #endif /* !_amigados    */
13838
13839 char *
13840 StrSave(s)
13841      char *s;
13842 {
13843     char *ret;
13844
13845     if ((ret = (char *) malloc(strlen(s) + 1))) {
13846         strcpy(ret, s);
13847     }
13848     return ret;
13849 }
13850
13851 char *
13852 StrSavePtr(s, savePtr)
13853      char *s, **savePtr;
13854 {
13855     if (*savePtr) {
13856         free(*savePtr);
13857     }
13858     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13859         strcpy(*savePtr, s);
13860     }
13861     return(*savePtr);
13862 }
13863
13864 char *
13865 PGNDate()
13866 {
13867     time_t clock;
13868     struct tm *tm;
13869     char buf[MSG_SIZ];
13870
13871     clock = time((time_t *)NULL);
13872     tm = localtime(&clock);
13873     sprintf(buf, "%04d.%02d.%02d",
13874             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13875     return StrSave(buf);
13876 }
13877
13878
13879 char *
13880 PositionToFEN(move, overrideCastling)
13881      int move;
13882      char *overrideCastling;
13883 {
13884     int i, j, fromX, fromY, toX, toY;
13885     int whiteToPlay;
13886     char buf[128];
13887     char *p, *q;
13888     int emptycount;
13889     ChessSquare piece;
13890
13891     whiteToPlay = (gameMode == EditPosition) ?
13892       !blackPlaysFirst : (move % 2 == 0);
13893     p = buf;
13894
13895     /* Piece placement data */
13896     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13897         emptycount = 0;
13898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13899             if (boards[move][i][j] == EmptySquare) {
13900                 emptycount++;
13901             } else { ChessSquare piece = boards[move][i][j];
13902                 if (emptycount > 0) {
13903                     if(emptycount<10) /* [HGM] can be >= 10 */
13904                         *p++ = '0' + emptycount;
13905                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13906                     emptycount = 0;
13907                 }
13908                 if(PieceToChar(piece) == '+') {
13909                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13910                     *p++ = '+';
13911                     piece = (ChessSquare)(DEMOTED piece);
13912                 } 
13913                 *p++ = PieceToChar(piece);
13914                 if(p[-1] == '~') {
13915                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13916                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13917                     *p++ = '~';
13918                 }
13919             }
13920         }
13921         if (emptycount > 0) {
13922             if(emptycount<10) /* [HGM] can be >= 10 */
13923                 *p++ = '0' + emptycount;
13924             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13925             emptycount = 0;
13926         }
13927         *p++ = '/';
13928     }
13929     *(p - 1) = ' ';
13930
13931     /* [HGM] print Crazyhouse or Shogi holdings */
13932     if( gameInfo.holdingsWidth ) {
13933         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13934         q = p;
13935         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13936             piece = boards[move][i][BOARD_WIDTH-1];
13937             if( piece != EmptySquare )
13938               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13939                   *p++ = PieceToChar(piece);
13940         }
13941         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13942             piece = boards[move][BOARD_HEIGHT-i-1][0];
13943             if( piece != EmptySquare )
13944               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13945                   *p++ = PieceToChar(piece);
13946         }
13947
13948         if( q == p ) *p++ = '-';
13949         *p++ = ']';
13950         *p++ = ' ';
13951     }
13952
13953     /* Active color */
13954     *p++ = whiteToPlay ? 'w' : 'b';
13955     *p++ = ' ';
13956
13957   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13958     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13959   } else {
13960   if(nrCastlingRights) {
13961      q = p;
13962      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13963        /* [HGM] write directly from rights */
13964            if(boards[move][CASTLING][2] != NoRights &&
13965               boards[move][CASTLING][0] != NoRights   )
13966                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13967            if(boards[move][CASTLING][2] != NoRights &&
13968               boards[move][CASTLING][1] != NoRights   )
13969                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13970            if(boards[move][CASTLING][5] != NoRights &&
13971               boards[move][CASTLING][3] != NoRights   )
13972                 *p++ = boards[move][CASTLING][3] + AAA;
13973            if(boards[move][CASTLING][5] != NoRights &&
13974               boards[move][CASTLING][4] != NoRights   )
13975                 *p++ = boards[move][CASTLING][4] + AAA;
13976      } else {
13977
13978         /* [HGM] write true castling rights */
13979         if( nrCastlingRights == 6 ) {
13980             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13981                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13982             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13983                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13984             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13985                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13986             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13987                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13988         }
13989      }
13990      if (q == p) *p++ = '-'; /* No castling rights */
13991      *p++ = ' ';
13992   }
13993
13994   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13995      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13996     /* En passant target square */
13997     if (move > backwardMostMove) {
13998         fromX = moveList[move - 1][0] - AAA;
13999         fromY = moveList[move - 1][1] - ONE;
14000         toX = moveList[move - 1][2] - AAA;
14001         toY = moveList[move - 1][3] - ONE;
14002         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14003             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14004             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14005             fromX == toX) {
14006             /* 2-square pawn move just happened */
14007             *p++ = toX + AAA;
14008             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14009         } else {
14010             *p++ = '-';
14011         }
14012     } else if(move == backwardMostMove) {
14013         // [HGM] perhaps we should always do it like this, and forget the above?
14014         if((signed char)boards[move][EP_STATUS] >= 0) {
14015             *p++ = boards[move][EP_STATUS] + AAA;
14016             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14017         } else {
14018             *p++ = '-';
14019         }
14020     } else {
14021         *p++ = '-';
14022     }
14023     *p++ = ' ';
14024   }
14025   }
14026
14027     /* [HGM] find reversible plies */
14028     {   int i = 0, j=move;
14029
14030         if (appData.debugMode) { int k;
14031             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14032             for(k=backwardMostMove; k<=forwardMostMove; k++)
14033                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14034
14035         }
14036
14037         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14038         if( j == backwardMostMove ) i += initialRulePlies;
14039         sprintf(p, "%d ", i);
14040         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14041     }
14042     /* Fullmove number */
14043     sprintf(p, "%d", (move / 2) + 1);
14044     
14045     return StrSave(buf);
14046 }
14047
14048 Boolean
14049 ParseFEN(board, blackPlaysFirst, fen)
14050     Board board;
14051      int *blackPlaysFirst;
14052      char *fen;
14053 {
14054     int i, j;
14055     char *p;
14056     int emptycount;
14057     ChessSquare piece;
14058
14059     p = fen;
14060
14061     /* [HGM] by default clear Crazyhouse holdings, if present */
14062     if(gameInfo.holdingsWidth) {
14063        for(i=0; i<BOARD_HEIGHT; i++) {
14064            board[i][0]             = EmptySquare; /* black holdings */
14065            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14066            board[i][1]             = (ChessSquare) 0; /* black counts */
14067            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14068        }
14069     }
14070
14071     /* Piece placement data */
14072     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14073         j = 0;
14074         for (;;) {
14075             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14076                 if (*p == '/') p++;
14077                 emptycount = gameInfo.boardWidth - j;
14078                 while (emptycount--)
14079                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14080                 break;
14081 #if(BOARD_FILES >= 10)
14082             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14083                 p++; emptycount=10;
14084                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14085                 while (emptycount--)
14086                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14087 #endif
14088             } else if (isdigit(*p)) {
14089                 emptycount = *p++ - '0';
14090                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14091                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14092                 while (emptycount--)
14093                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14094             } else if (*p == '+' || isalpha(*p)) {
14095                 if (j >= gameInfo.boardWidth) return FALSE;
14096                 if(*p=='+') {
14097                     piece = CharToPiece(*++p);
14098                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14099                     piece = (ChessSquare) (PROMOTED piece ); p++;
14100                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14101                 } else piece = CharToPiece(*p++);
14102
14103                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14104                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14105                     piece = (ChessSquare) (PROMOTED piece);
14106                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14107                     p++;
14108                 }
14109                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14110             } else {
14111                 return FALSE;
14112             }
14113         }
14114     }
14115     while (*p == '/' || *p == ' ') p++;
14116
14117     /* [HGM] look for Crazyhouse holdings here */
14118     while(*p==' ') p++;
14119     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14120         if(*p == '[') p++;
14121         if(*p == '-' ) *p++; /* empty holdings */ else {
14122             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14123             /* if we would allow FEN reading to set board size, we would   */
14124             /* have to add holdings and shift the board read so far here   */
14125             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14126                 *p++;
14127                 if((int) piece >= (int) BlackPawn ) {
14128                     i = (int)piece - (int)BlackPawn;
14129                     i = PieceToNumber((ChessSquare)i);
14130                     if( i >= gameInfo.holdingsSize ) return FALSE;
14131                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14132                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14133                 } else {
14134                     i = (int)piece - (int)WhitePawn;
14135                     i = PieceToNumber((ChessSquare)i);
14136                     if( i >= gameInfo.holdingsSize ) return FALSE;
14137                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14138                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14139                 }
14140             }
14141         }
14142         if(*p == ']') *p++;
14143     }
14144
14145     while(*p == ' ') p++;
14146
14147     /* Active color */
14148     switch (*p++) {
14149       case 'w':
14150         *blackPlaysFirst = FALSE;
14151         break;
14152       case 'b': 
14153         *blackPlaysFirst = TRUE;
14154         break;
14155       default:
14156         return FALSE;
14157     }
14158
14159     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14160     /* return the extra info in global variiables             */
14161
14162     /* set defaults in case FEN is incomplete */
14163     board[EP_STATUS] = EP_UNKNOWN;
14164     for(i=0; i<nrCastlingRights; i++ ) {
14165         board[CASTLING][i] =
14166             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14167     }   /* assume possible unless obviously impossible */
14168     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14169     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14170     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14171                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14172     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14173     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14174     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14175                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14176     FENrulePlies = 0;
14177
14178     while(*p==' ') p++;
14179     if(nrCastlingRights) {
14180       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14181           /* castling indicator present, so default becomes no castlings */
14182           for(i=0; i<nrCastlingRights; i++ ) {
14183                  board[CASTLING][i] = NoRights;
14184           }
14185       }
14186       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14187              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14188              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14189              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14190         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14191
14192         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14193             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14194             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14195         }
14196         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14197             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14198         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14199                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14200         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14201                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14202         switch(c) {
14203           case'K':
14204               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14205               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14206               board[CASTLING][2] = whiteKingFile;
14207               break;
14208           case'Q':
14209               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14210               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14211               board[CASTLING][2] = whiteKingFile;
14212               break;
14213           case'k':
14214               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14215               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14216               board[CASTLING][5] = blackKingFile;
14217               break;
14218           case'q':
14219               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14220               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14221               board[CASTLING][5] = blackKingFile;
14222           case '-':
14223               break;
14224           default: /* FRC castlings */
14225               if(c >= 'a') { /* black rights */
14226                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14227                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14228                   if(i == BOARD_RGHT) break;
14229                   board[CASTLING][5] = i;
14230                   c -= AAA;
14231                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14232                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14233                   if(c > i)
14234                       board[CASTLING][3] = c;
14235                   else
14236                       board[CASTLING][4] = c;
14237               } else { /* white rights */
14238                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14239                     if(board[0][i] == WhiteKing) break;
14240                   if(i == BOARD_RGHT) break;
14241                   board[CASTLING][2] = i;
14242                   c -= AAA - 'a' + 'A';
14243                   if(board[0][c] >= WhiteKing) break;
14244                   if(c > i)
14245                       board[CASTLING][0] = c;
14246                   else
14247                       board[CASTLING][1] = c;
14248               }
14249         }
14250       }
14251       for(i=0; i<nrCastlingRights; i++)
14252         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14253     if (appData.debugMode) {
14254         fprintf(debugFP, "FEN castling rights:");
14255         for(i=0; i<nrCastlingRights; i++)
14256         fprintf(debugFP, " %d", board[CASTLING][i]);
14257         fprintf(debugFP, "\n");
14258     }
14259
14260       while(*p==' ') p++;
14261     }
14262
14263     /* read e.p. field in games that know e.p. capture */
14264     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14265        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14266       if(*p=='-') {
14267         p++; board[EP_STATUS] = EP_NONE;
14268       } else {
14269          char c = *p++ - AAA;
14270
14271          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14272          if(*p >= '0' && *p <='9') *p++;
14273          board[EP_STATUS] = c;
14274       }
14275     }
14276
14277
14278     if(sscanf(p, "%d", &i) == 1) {
14279         FENrulePlies = i; /* 50-move ply counter */
14280         /* (The move number is still ignored)    */
14281     }
14282
14283     return TRUE;
14284 }
14285       
14286 void
14287 EditPositionPasteFEN(char *fen)
14288 {
14289   if (fen != NULL) {
14290     Board initial_position;
14291
14292     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14293       DisplayError(_("Bad FEN position in clipboard"), 0);
14294       return ;
14295     } else {
14296       int savedBlackPlaysFirst = blackPlaysFirst;
14297       EditPositionEvent();
14298       blackPlaysFirst = savedBlackPlaysFirst;
14299       CopyBoard(boards[0], initial_position);
14300       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14301       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14302       DisplayBothClocks();
14303       DrawPosition(FALSE, boards[currentMove]);
14304     }
14305   }
14306 }
14307
14308 static char cseq[12] = "\\   ";
14309
14310 Boolean set_cont_sequence(char *new_seq)
14311 {
14312     int len;
14313     Boolean ret;
14314
14315     // handle bad attempts to set the sequence
14316         if (!new_seq)
14317                 return 0; // acceptable error - no debug
14318
14319     len = strlen(new_seq);
14320     ret = (len > 0) && (len < sizeof(cseq));
14321     if (ret)
14322         strcpy(cseq, new_seq);
14323     else if (appData.debugMode)
14324         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14325     return ret;
14326 }
14327
14328 /*
14329     reformat a source message so words don't cross the width boundary.  internal
14330     newlines are not removed.  returns the wrapped size (no null character unless
14331     included in source message).  If dest is NULL, only calculate the size required
14332     for the dest buffer.  lp argument indicats line position upon entry, and it's
14333     passed back upon exit.
14334 */
14335 int wrap(char *dest, char *src, int count, int width, int *lp)
14336 {
14337     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14338
14339     cseq_len = strlen(cseq);
14340     old_line = line = *lp;
14341     ansi = len = clen = 0;
14342
14343     for (i=0; i < count; i++)
14344     {
14345         if (src[i] == '\033')
14346             ansi = 1;
14347
14348         // if we hit the width, back up
14349         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14350         {
14351             // store i & len in case the word is too long
14352             old_i = i, old_len = len;
14353
14354             // find the end of the last word
14355             while (i && src[i] != ' ' && src[i] != '\n')
14356             {
14357                 i--;
14358                 len--;
14359             }
14360
14361             // word too long?  restore i & len before splitting it
14362             if ((old_i-i+clen) >= width)
14363             {
14364                 i = old_i;
14365                 len = old_len;
14366             }
14367
14368             // extra space?
14369             if (i && src[i-1] == ' ')
14370                 len--;
14371
14372             if (src[i] != ' ' && src[i] != '\n')
14373             {
14374                 i--;
14375                 if (len)
14376                     len--;
14377             }
14378
14379             // now append the newline and continuation sequence
14380             if (dest)
14381                 dest[len] = '\n';
14382             len++;
14383             if (dest)
14384                 strncpy(dest+len, cseq, cseq_len);
14385             len += cseq_len;
14386             line = cseq_len;
14387             clen = cseq_len;
14388             continue;
14389         }
14390
14391         if (dest)
14392             dest[len] = src[i];
14393         len++;
14394         if (!ansi)
14395             line++;
14396         if (src[i] == '\n')
14397             line = 0;
14398         if (src[i] == 'm')
14399             ansi = 0;
14400     }
14401     if (dest && appData.debugMode)
14402     {
14403         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14404             count, width, line, len, *lp);
14405         show_bytes(debugFP, src, count);
14406         fprintf(debugFP, "\ndest: ");
14407         show_bytes(debugFP, dest, len);
14408         fprintf(debugFP, "\n");
14409     }
14410     *lp = dest ? line : old_line;
14411
14412     return len;
14413 }
14414
14415 // [HGM] vari: routines for shelving variations
14416
14417 void 
14418 PushTail(int firstMove, int lastMove)
14419 {
14420         int i, j, nrMoves = lastMove - firstMove;
14421
14422         if(appData.icsActive) { // only in local mode
14423                 forwardMostMove = currentMove; // mimic old ICS behavior
14424                 return;
14425         }
14426         if(storedGames >= MAX_VARIATIONS-1) return;
14427
14428         // push current tail of game on stack
14429         savedResult[storedGames] = gameInfo.result;
14430         savedDetails[storedGames] = gameInfo.resultDetails;
14431         gameInfo.resultDetails = NULL;
14432         savedFirst[storedGames] = firstMove;
14433         savedLast [storedGames] = lastMove;
14434         savedFramePtr[storedGames] = framePtr;
14435         framePtr -= nrMoves; // reserve space for the boards
14436         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14437             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14438             for(j=0; j<MOVE_LEN; j++)
14439                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14440             for(j=0; j<2*MOVE_LEN; j++)
14441                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14442             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14443             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14444             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14445             pvInfoList[firstMove+i-1].depth = 0;
14446             commentList[framePtr+i] = commentList[firstMove+i];
14447             commentList[firstMove+i] = NULL;
14448         }
14449
14450         storedGames++;
14451         forwardMostMove = currentMove; // truncte game so we can start variation
14452         if(storedGames == 1) GreyRevert(FALSE);
14453 }
14454
14455 Boolean
14456 PopTail(Boolean annotate)
14457 {
14458         int i, j, nrMoves;
14459         char buf[8000], moveBuf[20];
14460
14461         if(appData.icsActive) return FALSE; // only in local mode
14462         if(!storedGames) return FALSE; // sanity
14463
14464         storedGames--;
14465         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14466         nrMoves = savedLast[storedGames] - currentMove;
14467         if(annotate) {
14468                 int cnt = 10;
14469                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14470                 else strcpy(buf, "(");
14471                 for(i=currentMove; i<forwardMostMove; i++) {
14472                         if(WhiteOnMove(i))
14473                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14474                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14475                         strcat(buf, moveBuf);
14476                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14477                 }
14478                 strcat(buf, ")");
14479         }
14480         for(i=1; i<nrMoves; i++) { // copy last variation back
14481             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14482             for(j=0; j<MOVE_LEN; j++)
14483                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14484             for(j=0; j<2*MOVE_LEN; j++)
14485                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14486             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14487             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14488             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14489             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14490             commentList[currentMove+i] = commentList[framePtr+i];
14491             commentList[framePtr+i] = NULL;
14492         }
14493         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14494         framePtr = savedFramePtr[storedGames];
14495         gameInfo.result = savedResult[storedGames];
14496         if(gameInfo.resultDetails != NULL) {
14497             free(gameInfo.resultDetails);
14498       }
14499         gameInfo.resultDetails = savedDetails[storedGames];
14500         forwardMostMove = currentMove + nrMoves;
14501         if(storedGames == 0) GreyRevert(TRUE);
14502         return TRUE;
14503 }
14504
14505 void 
14506 CleanupTail()
14507 {       // remove all shelved variations
14508         int i;
14509         for(i=0; i<storedGames; i++) {
14510             if(savedDetails[i])
14511                 free(savedDetails[i]);
14512             savedDetails[i] = NULL;
14513         }
14514         for(i=framePtr; i<MAX_MOVES; i++) {
14515                 if(commentList[i]) free(commentList[i]);
14516                 commentList[i] = NULL;
14517         }
14518         framePtr = MAX_MOVES-1;
14519         storedGames = 0;
14520 }